diff options
28 files changed, 2606 insertions, 863 deletions
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java index 05276661..b02efa07 100644 --- a/src/android/net/apf/ApfCounterTracker.java +++ b/src/android/net/apf/ApfCounterTracker.java @@ -40,7 +40,10 @@ public class ApfCounterTracker { @VisibleForTesting public enum Counter { RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds) - TOTAL_PACKETS, + ENDIANNESS, // APFv6 interpreter stores 0x12345678 here + TOTAL_PACKETS, // hardcoded in APFv6 interpreter + PASSED_ALLOCATE_FAILURE, // hardcoded in APFv6 interpreter + PASSED_TRANSMIT_FAILURE, // hardcoded in APFv6 interpreter PASSED_ARP, PASSED_DHCP, PASSED_IPV4, diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index ee2990b8..9a3d58b7 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java @@ -46,8 +46,8 @@ import android.net.LinkProperties; import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCounterTracker.Counter; -import android.net.apf.ApfGenerator.IllegalInstructionException; -import android.net.apf.ApfGenerator.Register; +import android.net.apf.ApfV4Generator.IllegalInstructionException; +import android.net.apf.ApfV4Generator.Register; import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.os.PowerManager; import android.os.SystemClock; @@ -139,7 +139,7 @@ public class ApfFilter implements AndroidPacketFilter { /** * When APFv4 is supported, loads R1 with the offset of the specified counter. */ - private void maybeSetupCounter(ApfGenerator gen, Counter c) { + private void maybeSetupCounter(ApfV4Generator gen, Counter c) { if (mApfCapabilities.hasDataAccess()) { gen.addLoadImmediate(Register.R1, c.offset()); } @@ -413,8 +413,8 @@ public class ApfFilter implements AndroidPacketFilter { } else { // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP, // preserving the original pre-APFv4 behavior. - mCountAndPassLabel = ApfGenerator.PASS_LABEL; - mCountAndDropLabel = ApfGenerator.DROP_LABEL; + mCountAndPassLabel = ApfV4Generator.PASS_LABEL; + mCountAndDropLabel = ApfV4Generator.DROP_LABEL; } // Now fill the black list from the passed array @@ -490,7 +490,6 @@ public class ApfFilter implements AndroidPacketFilter { return mUniqueCounter++; } - @GuardedBy("this") private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) { ArrayList<Integer> bl = new ArrayList<Integer>(); @@ -1135,7 +1134,7 @@ public class ApfFilter implements AndroidPacketFilter { // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped. // Jump to the next filter if packet doesn't match this RA. @GuardedBy("ApfFilter.this") - void generateFilterLocked(ApfGenerator gen, int timeSeconds) + void generateFilterLocked(ApfV4Generator gen, int timeSeconds) throws IllegalInstructionException { String nextFilterLabel = "Ra" + getUniqueNumberLocked(); // Skip if packet is not the right size @@ -1236,7 +1235,7 @@ public class ApfFilter implements AndroidPacketFilter { // Append a filter for this keepalive ack to {@code gen}. // Jump to drop if it matches the keepalive ack. // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException; } // A class to hold NAT-T keepalive ack information. @@ -1279,7 +1278,8 @@ public class ApfFilter implements AndroidPacketFilter { } @Override - void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("ApfFilter.this") + void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); @@ -1381,7 +1381,7 @@ public class ApfFilter implements AndroidPacketFilter { // Append a filter for this keepalive ack to {@code gen}. // Jump to drop if it matches the keepalive ack. // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException; } private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { @@ -1394,7 +1394,8 @@ public class ApfFilter implements AndroidPacketFilter { } @Override - void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("ApfFilter.this") + void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); @@ -1437,7 +1438,7 @@ public class ApfFilter implements AndroidPacketFilter { } @Override - void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); } } @@ -1498,7 +1499,7 @@ public class ApfFilter implements AndroidPacketFilter { * - Packet being filtered is ARP */ @GuardedBy("this") - private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + private void generateArpFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { // Here's a basic summary of what the ARP filter program does: // // if not ARP IPv4 @@ -1566,7 +1567,7 @@ public class ApfFilter implements AndroidPacketFilter { * - Packet being filtered is IPv4 */ @GuardedBy("this") - private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException { + private void generateIPv4FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv4 filter program does: // // if filtering multicast (i.e. multicast lock not held): @@ -1649,7 +1650,8 @@ public class ApfFilter implements AndroidPacketFilter { gen.addJump(mCountAndPassLabel); } - private void generateKeepaliveFilters(ApfGenerator gen, Class<?> filterType, int proto, + @GuardedBy("this") + private void generateKeepaliveFilters(ApfV4Generator gen, Class<?> filterType, int proto, int offset, String label) throws IllegalInstructionException { final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets, ack -> filterType.isInstance(ack)); @@ -1670,12 +1672,14 @@ public class ApfFilter implements AndroidPacketFilter { gen.defineLabel(label); } - private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("this") + private void generateV4KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, "skip_v4_keepalive_filter"); } - private void generateV4NattKeepaliveFilters(ApfGenerator gen) + @GuardedBy("this") + private void generateV4NattKeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, NattKeepaliveResponse.class, IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter"); @@ -1688,7 +1692,7 @@ public class ApfFilter implements AndroidPacketFilter { * - Packet being filtered is IPv6 */ @GuardedBy("this") - private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException { + private void generateIPv6FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv6 filter program does: // // if there is a hop-by-hop option present (e.g. MLD query) @@ -1788,7 +1792,7 @@ public class ApfFilter implements AndroidPacketFilter { * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check. */ @GuardedBy("this") - private void generateMdnsFilterLocked(ApfGenerator gen) + private void generateMdnsFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String skipMdnsv4Filter = "skip_mdns_v4_filter"; final String skipMdnsFilter = "skip_mdns_filter"; @@ -1900,7 +1904,7 @@ public class ApfFilter implements AndroidPacketFilter { * R0/R1 have nothing useful in them, and can be clobbered. */ @GuardedBy("this") - private void generateV4TcpPort7FilterLocked(ApfGenerator gen) + private void generateV4TcpPort7FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String skipPort7V4Filter = "skip_port7_v4_filter"; @@ -1925,7 +1929,8 @@ public class ApfFilter implements AndroidPacketFilter { gen.defineLabel(skipPort7V4Filter); } - private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("this") + private void generateV6KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, "skip_v6_keepalive_filter"); } @@ -1952,9 +1957,9 @@ public class ApfFilter implements AndroidPacketFilter { */ @GuardedBy("this") @VisibleForTesting - protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. - ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported); + ApfV4Generator gen = new ApfV4Generator(mApfCapabilities.apfVersionSupported); if (mApfCapabilities.hasDataAccess()) { // Increment TOTAL_PACKETS @@ -2037,7 +2042,7 @@ public class ApfFilter implements AndroidPacketFilter { * before jumping to the actual PASS and DROP labels. */ @GuardedBy("this") - private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException { + private void emitEpilogue(ApfV4Generator gen) throws IllegalInstructionException { // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it // will just fall-through to the PASS label. if (!mApfCapabilities.hasDataAccess()) return; @@ -2067,6 +2072,7 @@ public class ApfFilter implements AndroidPacketFilter { * Generate and install a new filter program. */ @GuardedBy("this") + @SuppressWarnings("GuardedBy") // errorprone false positive on ra#generateFilterLocked @VisibleForTesting public void installNewProgramLocked() { ArrayList<Ra> rasToFilter = new ArrayList<>(); @@ -2082,7 +2088,7 @@ public class ApfFilter implements AndroidPacketFilter { int timeSeconds = secondsSinceBoot(); try { // Step 1: Determine how many RA filters we can fit in the program. - ApfGenerator gen = emitPrologueLocked(); + ApfV4Generator gen = emitPrologueLocked(); // The epilogue normally goes after the RA filters, but add it early to include its // length when estimating the total. @@ -2251,7 +2257,7 @@ public class ApfFilter implements AndroidPacketFilter { // For now only support generating programs for Ethernet frames. If this restriction is // lifted the program generator will need its offsets adjusted. if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null; - if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) { + if (!ApfV4Generator.supportsVersion(apfCapabilities.apfVersionSupported)) { Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); return null; } diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfV4Generator.java index c6ff4416..a7986e61 100644 --- a/src/android/net/apf/ApfGenerator.java +++ b/src/android/net/apf/ApfV4Generator.java @@ -16,12 +16,13 @@ package android.net.apf; -import static android.net.apf.ApfGenerator.Register.R0; -import static android.net.apf.ApfGenerator.Register.R1; +import static android.net.apf.ApfV4Generator.Register.R0; +import static android.net.apf.ApfV4Generator.Register.R1; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.HexDump; import java.util.ArrayList; import java.util.HashMap; @@ -31,11 +32,11 @@ import java.util.List; * APF assembler/generator. A tool for generating an APF program. * * Call add*() functions to add instructions to the program, then call - * {@link ApfGenerator#generate} to get the APF bytecode for the program. + * {@link ApfV4Generator#generate} to get the APF bytecode for the program. * * @hide */ -public class ApfGenerator { +public class ApfV4Generator { /** * This exception is thrown when an attempt is made to generate an illegal instruction. */ @@ -53,8 +54,7 @@ public class ApfGenerator { // It is a U32 big-endian value and is always incremented by 1. // This is more or less equivalent to: lddw R0, -N4; add R0,1; stdw R0, -N4; {pass,drop} // e.g. "pass", "pass 1", "drop", "drop 1" - PASS(0), - DROP(0), + PASSDROP(0), LDB(1), // Load 1 byte from immediate offset, e.g. "ldb R0, [5]" LDH(2), // Load 2 bytes from immediate offset, e.g. "ldh R0, [5]" LDW(3), // Load 4 bytes from immediate offset, e.g. "ldw R0, [5]" @@ -117,10 +117,9 @@ public class ApfGenerator { ALLOCATE(36), // Transmit and deallocate the buffer (transmission can be delayed until the program // terminates). R=0 means discard the buffer, R=1 means transmit the buffer. - // "e.g. trans" + // "e.g. transmit" // "e.g. discard" - TRANSMIT(37), - DISCARD(37), + TRANSMITDISCARD(37), // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the // output buffer pointer. // e.g. "ewrite1 r0" @@ -134,7 +133,27 @@ public class ApfGenerator { // when R=1, the copy length is stored in R1. // e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1" EPKTCOPY(41), - EDATACOPY(42); + EDATACOPY(42), + // Jumps if the UDP payload content (starting at R0) does not contain one + // of the specified QNAMEs, applying case insensitivity. + // R0: Offset to UDP payload content + // R=0/1 meaning 'does not match'/'matches' + // imm1: Opcode + // imm2: Label offset + // imm3(u8): Question type (PTR/SRV/TXT/A/AAAA) + // imm4(bytes): TLV-encoded QNAME list (null-terminated) + // e.g.: "jdnsqmatch R0,label,0x0c,\002aa\005local\0\0" + JDNSQMATCH(43), + // Jumps if the UDP payload content (starting at R0) does not contain one + // of the specified NAMEs in answers/authority/additional records, applying + // case insensitivity. + // R=0/1 meaning 'does not match'/'matches' + // R0: Offset to UDP payload content + // imm1: Opcode + // imm2: Label offset + // imm3(bytes): TLV-encoded QNAME list (null-terminated) + // e.g.: "jdnsamatch R0,label,0x0c,\002aa\005local\0\0" + JDNSAMATCH(44); final int value; @@ -493,12 +512,19 @@ public class ApfGenerator { int writingOffset = offset; bytecode[writingOffset++] = generateInstructionByte(); int indeterminateSize = calculateRequiredIndeterminateSize(); + int startOffset = 0; + if (mOpcode == Opcodes.EXT.value) { + // For extend opcode, always write the actual opcode first. + writingOffset = mIntImms.get(startOffset++).writeValue(bytecode, writingOffset, + indeterminateSize); + } if (mTargetLabel != null) { writingOffset = writeValue(calculateTargetLabelOffset(), bytecode, writingOffset, indeterminateSize); } - for (IntImmediate imm : mIntImms) { - writingOffset = imm.writeValue(bytecode, writingOffset, indeterminateSize); + for (int i = startOffset; i < mIntImms.size(); ++i) { + writingOffset = mIntImms.get(i).writeValue(bytecode, writingOffset, + indeterminateSize); } if (mBytesImm != null) { System.arraycopy(mBytesImm, 0, bytecode, writingOffset, mBytesImm.length); @@ -615,7 +641,7 @@ public class ApfGenerator { * the requested version is unsupported. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public ApfGenerator(int version) throws IllegalInstructionException { + public ApfV4Generator(int version) throws IllegalInstructionException { mVersion = version; requireApfVersion(MIN_APF_VERSION); } @@ -633,7 +659,7 @@ public class ApfGenerator { } } - private ApfGenerator append(Instruction instruction) { + private ApfV4Generator append(Instruction instruction) { if (mGenerated) { throw new IllegalStateException("Program already generated"); } @@ -645,7 +671,7 @@ public class ApfGenerator { * Define a label at the current end of the program. Jumps can jump to this label. Labels are * their own separate instructions, though with size 0. This facilitates having labels with * no corresponding code to execute, for example a label at the end of a program. For example - * an {@link ApfGenerator} might be passed to a function that adds a filter like so: + * an {@link ApfV4Generator} might be passed to a function that adds a filter like so: * <pre> * load from packet * compare loaded data, jump if not equal to "next_filter" @@ -656,14 +682,14 @@ public class ApfGenerator { * </pre> * In this case "next_filter" may not have any generated code associated with it. */ - public ApfGenerator defineLabel(String name) throws IllegalInstructionException { + public ApfV4Generator defineLabel(String name) throws IllegalInstructionException { return append(new Instruction(Opcodes.LABEL).setLabel(name)); } /** * Add an unconditional jump instruction to the end of the program. */ - public ApfGenerator addJump(String target) { + public ApfV4Generator addJump(String target) { return append(new Instruction(Opcodes.JMP).setTargetLabel(target)); } @@ -671,7 +697,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to load the byte at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad8(Register r, int ofs) { + public ApfV4Generator addLoad8(Register r, int ofs) { return append(new Instruction(Opcodes.LDB, r).addUnsigned(ofs)); } @@ -679,7 +705,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to load 16-bits at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad16(Register r, int ofs) { + public ApfV4Generator addLoad16(Register r, int ofs) { return append(new Instruction(Opcodes.LDH, r).addUnsigned(ofs)); } @@ -687,7 +713,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to load 32-bits at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad32(Register r, int ofs) { + public ApfV4Generator addLoad32(Register r, int ofs) { return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs)); } @@ -696,7 +722,7 @@ public class ApfGenerator { * {@code register}. The offset of the loaded byte from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad8Indexed(Register r, int ofs) { + public ApfV4Generator addLoad8Indexed(Register r, int ofs) { return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs)); } @@ -705,7 +731,7 @@ public class ApfGenerator { * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad16Indexed(Register r, int ofs) { + public ApfV4Generator addLoad16Indexed(Register r, int ofs) { return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs)); } @@ -714,42 +740,42 @@ public class ApfGenerator { * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad32Indexed(Register r, int ofs) { + public ApfV4Generator addLoad32Indexed(Register r, int ofs) { return append(new Instruction(Opcodes.LDWX, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to add {@code value} to register R0. */ - public ApfGenerator addAdd(int val) { + public ApfV4Generator addAdd(int val) { return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to multiply register R0 by {@code value}. */ - public ApfGenerator addMul(int val) { + public ApfV4Generator addMul(int val) { return append(new Instruction(Opcodes.MUL).addUnsigned(val)); } /** * Add an instruction to the end of the program to divide register R0 by {@code value}. */ - public ApfGenerator addDiv(int val) { + public ApfV4Generator addDiv(int val) { return append(new Instruction(Opcodes.DIV).addUnsigned(val)); } /** * Add an instruction to the end of the program to logically and register R0 with {@code value}. */ - public ApfGenerator addAnd(int val) { + public ApfV4Generator addAnd(int val) { return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to logically or register R0 with {@code value}. */ - public ApfGenerator addOr(int val) { + public ApfV4Generator addOr(int val) { return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val)); } @@ -757,7 +783,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to shift left register R0 by {@code value} bits. */ // TODO: consider whether should change the argument type to byte - public ApfGenerator addLeftShift(int val) { + public ApfV4Generator addLeftShift(int val) { return append(new Instruction(Opcodes.SH).addSigned(val)); } @@ -766,28 +792,28 @@ public class ApfGenerator { * bits. */ // TODO: consider whether should change the argument type to byte - public ApfGenerator addRightShift(int val) { + public ApfV4Generator addRightShift(int val) { return append(new Instruction(Opcodes.SH).addSigned(-val)); } /** * Add an instruction to the end of the program to add register R1 to register R0. */ - public ApfGenerator addAddR1() { + public ApfV4Generator addAddR1() { return append(new Instruction(Opcodes.ADD, R1)); } /** * Add an instruction to the end of the program to multiply register R0 by register R1. */ - public ApfGenerator addMulR1() { + public ApfV4Generator addMulR1() { return append(new Instruction(Opcodes.MUL, R1)); } /** * Add an instruction to the end of the program to divide register R0 by register R1. */ - public ApfGenerator addDivR1() { + public ApfV4Generator addDivR1() { return append(new Instruction(Opcodes.DIV, R1)); } @@ -795,7 +821,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to logically and register R0 with register R1 * and store the result back into register R0. */ - public ApfGenerator addAndR1() { + public ApfV4Generator addAndR1() { return append(new Instruction(Opcodes.AND, R1)); } @@ -803,7 +829,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to logically or register R0 with register R1 * and store the result back into register R0. */ - public ApfGenerator addOrR1() { + public ApfV4Generator addOrR1() { return append(new Instruction(Opcodes.OR, R1)); } @@ -811,14 +837,14 @@ public class ApfGenerator { * Add an instruction to the end of the program to shift register R0 left by the value in * register R1. */ - public ApfGenerator addLeftShiftR1() { + public ApfV4Generator addLeftShiftR1() { return append(new Instruction(Opcodes.SH, R1)); } /** * Add an instruction to the end of the program to move {@code value} into {@code register}. */ - public ApfGenerator addLoadImmediate(Register register, int value) { + public ApfV4Generator addLoadImmediate(Register register, int value) { return append(new Instruction(Opcodes.LI, register).addSigned(value)); } @@ -826,7 +852,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value equals {@code value}. */ - public ApfGenerator addJumpIfR0Equals(int val, String tgt) { + public ApfV4Generator addJumpIfR0Equals(int val, String tgt) { return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt)); } @@ -834,7 +860,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value does not equal {@code value}. */ - public ApfGenerator addJumpIfR0NotEquals(int val, String tgt) { + public ApfV4Generator addJumpIfR0NotEquals(int val, String tgt) { return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt)); } @@ -842,7 +868,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is greater than {@code value}. */ - public ApfGenerator addJumpIfR0GreaterThan(int val, String tgt) { + public ApfV4Generator addJumpIfR0GreaterThan(int val, String tgt) { return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt)); } @@ -850,7 +876,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is less than {@code value}. */ - public ApfGenerator addJumpIfR0LessThan(int val, String tgt) { + public ApfV4Generator addJumpIfR0LessThan(int val, String tgt) { return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt)); } @@ -858,14 +884,14 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value has any bits set that are also set in {@code value}. */ - public ApfGenerator addJumpIfR0AnyBitsSet(int val, String tgt) { + public ApfV4Generator addJumpIfR0AnyBitsSet(int val, String tgt) { return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value equals register R1's value. */ - public ApfGenerator addJumpIfR0EqualsR1(String tgt) { + public ApfV4Generator addJumpIfR0EqualsR1(String tgt) { return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt)); } @@ -873,7 +899,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value does not equal register R1's value. */ - public ApfGenerator addJumpIfR0NotEqualsR1(String tgt) { + public ApfV4Generator addJumpIfR0NotEqualsR1(String tgt) { return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt)); } @@ -881,7 +907,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is greater than register R1's value. */ - public ApfGenerator addJumpIfR0GreaterThanR1(String tgt) { + public ApfV4Generator addJumpIfR0GreaterThanR1(String tgt) { return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt)); } @@ -889,7 +915,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is less than register R1's value. */ - public ApfGenerator addJumpIfR0LessThanR1(String target) { + public ApfV4Generator addJumpIfR0LessThanR1(String target) { return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target)); } @@ -897,24 +923,37 @@ public class ApfGenerator { * Add an instruction to the end of the program to jump to {@code target} if register R0's * value has any bits set that are also set in R1's value. */ - public ApfGenerator addJumpIfR0AnyBitsSetR1(String tgt) { + public ApfV4Generator addJumpIfR0AnyBitsSetR1(String tgt) { return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt)); } /** - * Add an instruction to the end of the program to jump to {@code target} if the bytes of the + * 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 register} don't match {@code bytes} + * R=0 means check for not equal */ - public ApfGenerator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) { + public ApfV4Generator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) { return append(new Instruction(Opcodes.JNEBS).addUnsigned( bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); } /** + * 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 register} match {@code bytes} + * R=1 means check for equal. + */ + public ApfV4Generator addJumpIfBytesAtR0Equal(byte[] bytes, String tgt) + throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.JNEBS, R1).addUnsigned( + bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); + } + + /** * Add an instruction to the end of the program to load memory slot {@code slot} into * {@code register}. */ - public ApfGenerator addLoadFromMemory(Register r, int slot) + public ApfV4Generator addLoadFromMemory(Register r, int slot) throws IllegalInstructionException { return append(new Instruction(ExtendedOpcodes.LDM, slot, r)); } @@ -923,7 +962,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to store {@code register} into memory slot * {@code slot}. */ - public ApfGenerator addStoreToMemory(Register r, int slot) + public ApfV4Generator addStoreToMemory(Register r, int slot) throws IllegalInstructionException { return append(new Instruction(ExtendedOpcodes.STM, slot, r)); } @@ -931,21 +970,21 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to logically not {@code register}. */ - public ApfGenerator addNot(Register r) { + public ApfV4Generator addNot(Register r) { return append(new Instruction(ExtendedOpcodes.NOT, r)); } /** * Add an instruction to the end of the program to negate {@code register}. */ - public ApfGenerator addNeg(Register r) { + public ApfV4Generator addNeg(Register r) { return append(new Instruction(ExtendedOpcodes.NEG, r)); } /** * Add an instruction to swap the values in register R0 and register R1. */ - public ApfGenerator addSwap() { + public ApfV4Generator addSwap() { return append(new Instruction(ExtendedOpcodes.SWAP)); } @@ -953,56 +992,56 @@ public class ApfGenerator { * Add an instruction to the end of the program to move the value into * {@code register} from the other register. */ - public ApfGenerator addMove(Register r) { + public ApfV4Generator addMove(Register r) { return append(new Instruction(ExtendedOpcodes.MOVE, r)); } /** * Add an instruction to the end of the program to let the program immediately return PASS. */ - public ApfGenerator addPass() { + public ApfV4Generator addPass() { // PASS requires using R0 because it shares opcode with DROP - return append(new Instruction(Opcodes.PASS)); + return append(new Instruction(Opcodes.PASSDROP)); } /** * Add an instruction to the end of the program to increment the counter value and * immediately return PASS. */ - public ApfGenerator addCountAndPass(int cnt) throws IllegalInstructionException { + public ApfV4Generator addCountAndPass(int cnt) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, 1000 /* upperBound */); // PASS requires using R0 because it shares opcode with DROP - return append(new Instruction(Opcodes.PASS).addUnsigned(cnt)); + return append(new Instruction(Opcodes.PASSDROP).addUnsigned(cnt)); } /** * Add an instruction to the end of the program to let the program immediately return DROP. */ - public ApfGenerator addDrop() throws IllegalInstructionException { + public ApfV4Generator addDrop() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); // DROP requires using R1 because it shares opcode with PASS - return append(new Instruction(Opcodes.DROP, R1)); + return append(new Instruction(Opcodes.PASSDROP, R1)); } /** * Add an instruction to the end of the program to increment the counter value and * immediately return DROP. */ - public ApfGenerator addCountAndDrop(int cnt) throws IllegalInstructionException { + public ApfV4Generator addCountAndDrop(int cnt) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, 1000 /* upperBound */); // DROP requires using R1 because it shares opcode with PASS - return append(new Instruction(Opcodes.DROP, R1).addUnsigned(cnt)); + return append(new Instruction(Opcodes.PASSDROP, R1).addUnsigned(cnt)); } /** * Add an instruction to the end of the program to call the apf_allocate_buffer() function. * Buffer length to be allocated is stored in register 0. */ - public ApfGenerator addAllocateR0() throws IllegalInstructionException { + public ApfV4Generator addAllocateR0() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.ALLOCATE)); } @@ -1012,7 +1051,7 @@ public class ApfGenerator { * * @param size the buffer length to be allocated. */ - public ApfGenerator addAllocate(int size) throws IllegalInstructionException { + public ApfV4Generator addAllocate(int size) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); // R1 means the extra be16 immediate is present return append(new Instruction(ExtendedOpcodes.ALLOCATE, R1).addU16(size)); @@ -1022,7 +1061,7 @@ public class ApfGenerator { * Add an instruction to the beginning of the program to reserve the data region. * @param data the actual data byte */ - public ApfGenerator addData(byte[] data) throws IllegalInstructionException { + public ApfV4Generator addData(byte[] data) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); if (!mInstructions.isEmpty()) { throw new IllegalInstructionException("data instruction has to come first"); @@ -1033,25 +1072,25 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to transmit the allocated buffer. */ - public ApfGenerator addTransmit() throws IllegalInstructionException { + public ApfV4Generator addTransmit() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); // TRANSMIT requires using R0 because it shares opcode with DISCARD - return append(new Instruction(ExtendedOpcodes.TRANSMIT)); + return append(new Instruction(ExtendedOpcodes.TRANSMITDISCARD)); } /** * Add an instruction to the end of the program to discard the allocated buffer. */ - public ApfGenerator addDiscard() throws IllegalInstructionException { + public ApfV4Generator addDiscard() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); // DISCARD requires using R1 because it shares opcode with TRANSMIT - return append(new Instruction(ExtendedOpcodes.DISCARD, R1)); + return append(new Instruction(ExtendedOpcodes.TRANSMITDISCARD, R1)); } /** * Add an instruction to the end of the program to write 1 byte value to output buffer. */ - public ApfGenerator addWriteU8(int val) throws IllegalInstructionException { + public ApfV4Generator addWriteU8(int val) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val)); } @@ -1059,7 +1098,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to write 2 bytes value to output buffer. */ - public ApfGenerator addWriteU16(int val) throws IllegalInstructionException { + public ApfV4Generator addWriteU16(int val) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val)); } @@ -1067,7 +1106,7 @@ public class ApfGenerator { /** * Add an instruction to the end of the program to write 4 bytes value to output buffer. */ - public ApfGenerator addWriteU32(long val) throws IllegalInstructionException { + public ApfV4Generator addWriteU32(long val) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val)); } @@ -1076,7 +1115,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to write 1 byte value from register to output * buffer. */ - public ApfGenerator addWriteU8(Register reg) throws IllegalInstructionException { + public ApfV4Generator addWriteU8(Register reg) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EWRITE1, reg)); } @@ -1085,7 +1124,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to write 2 byte value from register to output * buffer. */ - public ApfGenerator addWriteU16(Register reg) throws IllegalInstructionException { + public ApfV4Generator addWriteU16(Register reg) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EWRITE2, reg)); } @@ -1094,7 +1133,7 @@ public class ApfGenerator { * Add an instruction to the end of the program to write 4 byte value from register to output * buffer. */ - public ApfGenerator addWriteU32(Register reg) throws IllegalInstructionException { + public ApfV4Generator addWriteU32(Register reg) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EWRITE4, reg)); } @@ -1108,7 +1147,7 @@ public class ApfGenerator { * one time. * @return the ApfGenerator object */ - public ApfGenerator addDataCopy(int src, int len) + public ApfV4Generator addDataCopy(int src, int len) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len)); @@ -1123,7 +1162,7 @@ public class ApfGenerator { * one time. * @return the ApfGenerator object */ - public ApfGenerator addPacketCopy(int src, int len) + public ApfV4Generator addPacketCopy(int src, int len) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len)); @@ -1137,7 +1176,7 @@ public class ApfGenerator { * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. * @return the ApfGenerator object */ - public ApfGenerator addDataCopyFromR0(int len) throws IllegalInstructionException { + public ApfV4Generator addDataCopyFromR0(int len) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EDATACOPY).addU8(len)); } @@ -1150,7 +1189,7 @@ public class ApfGenerator { * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. * @return the ApfGenerator object */ - public ApfGenerator addPacketCopyFromR0(int len) throws IllegalInstructionException { + public ApfV4Generator addPacketCopyFromR0(int len) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EPKTCOPY).addU8(len)); } @@ -1163,7 +1202,7 @@ public class ApfGenerator { * * @return the ApfGenerator object */ - public ApfGenerator addDataCopyFromR0LenR1() throws IllegalInstructionException { + public ApfV4Generator addDataCopyFromR0LenR1() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EDATACOPY, R1)); } @@ -1176,11 +1215,106 @@ public class ApfGenerator { * * @return the ApfGenerator object */ - public ApfGenerator addPacketCopyFromR0LenR1() throws IllegalInstructionException { + public ApfV4Generator addPacketCopyFromR0LenR1() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); return append(new Instruction(ExtendedOpcodes.EPKTCOPY, R1)); } + /** + * Check if the byte is valid dns character: A-Z,0-9,-,_ + */ + private static boolean isValidDnsCharacter(byte c) { + return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_'; + } + + private static void validateNames(@NonNull byte[] names) { + final int len = names.length; + if (len < 4) { + throw new IllegalArgumentException("qnames must have at least length 4"); + } + final String errorMessage = "qname: " + HexDump.toHexString(names) + + "is not null-terminated list of TLV-encoded names"; + int i = 0; + while (i < len - 1) { + int label_len = names[i++]; + if (label_len < 1 || label_len > 63) { + throw new IllegalArgumentException( + "label len: " + label_len + " must be between 1 and 63"); + } + if (i + label_len >= len - 1) { + throw new IllegalArgumentException(errorMessage); + } + while (label_len-- > 0) { + if (!isValidDnsCharacter(names[i++])) { + throw new IllegalArgumentException("qname: " + HexDump.toHexString(names) + + " contains invalid character"); + } + } + if (names[i] == 0) { + i++; // skip null terminator. + } + } + if (names[len - 1] != 0) { + throw new IllegalArgumentException(errorMessage); + } + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS questions do NOT contain the QNAMEs specified in {@code qnames} and qtype + * equals {@code qtype}. Examines the payload starting at the offset in R0. + * R = 0 means check for "does not contain". + */ + public ApfV4Generator addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(qnames); + return append(new Instruction(ExtendedOpcodes.JDNSQMATCH).setTargetLabel(tgt).addU8( + qtype).setBytesImm(qnames)); + } + + /** + * 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. + * R = 1 means check for "contain". + */ + public ApfV4Generator addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(qnames); + return append(new Instruction(ExtendedOpcodes.JDNSQMATCH, R1).setTargetLabel(tgt).addU8( + qtype).setBytesImm(qnames)); + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS answers/authority/additional records do NOT contain the NAMEs + * specified in {@code Names}. Examines the payload starting at the offset in R0. + * R = 0 means check for "does not contain". + */ + public ApfV4Generator addJumpIfPktAtR0DoesNotContainDnsA(@NonNull byte[] names, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(names); + return append(new Instruction(ExtendedOpcodes.JDNSAMATCH).setTargetLabel(tgt).setBytesImm( + names)); + } + + /** + * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP + * payload's DNS answers/authority/additional records contain the NAMEs + * specified in {@code Names}. Examines the payload starting at the offset in R0. + * R = 1 means check for "contain". + */ + public ApfV4Generator addJumpIfPktAtR0ContainDnsA(@NonNull byte[] names, + @NonNull String tgt) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + validateNames(names); + return append(new Instruction(ExtendedOpcodes.JDNSAMATCH, R1).setTargetLabel( + tgt).setBytesImm(names)); + } + private static void checkRange(@NonNull String variableName, long value, long lowerBound, long upperBound) { if (value >= lowerBound && value <= upperBound) { @@ -1197,7 +1331,7 @@ public class ApfGenerator { * @{code offset} to the other register. * Requires APF v4 or greater. */ - public ApfGenerator addLoadData(Register dst, int ofs) + public ApfV4Generator addLoadData(Register dst, int ofs) throws IllegalInstructionException { requireApfVersion(APF_VERSION_4); return append(new Instruction(Opcodes.LDDW, dst).addSigned(ofs)); @@ -1209,7 +1343,7 @@ public class ApfGenerator { * @{code offset} to the other register. * Requires APF v4 or greater. */ - public ApfGenerator addStoreData(Register src, int ofs) + public ApfV4Generator addStoreData(Register src, int ofs) throws IllegalInstructionException { requireApfVersion(APF_VERSION_4); return append(new Instruction(Opcodes.STDW, src).addSigned(ofs)); diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java index 5bd2515b..f0f3039a 100644 --- a/src/android/net/apf/DnsUtils.java +++ b/src/android/net/apf/DnsUtils.java @@ -16,8 +16,8 @@ package android.net.apf; -import static android.net.apf.ApfGenerator.Register.R0; -import static android.net.apf.ApfGenerator.Register.R1; +import static android.net.apf.ApfV4Generator.Register.R0; +import static android.net.apf.ApfV4Generator.Register.R1; import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN; @@ -82,7 +82,7 @@ public class DnsUtils { * - R1: label length * - m[SLOT_CURRENT_PARSE_OFFSET]: offset of label text */ - private static void genParseDnsLabel(ApfGenerator gen, JumpTable jumpTable) throws Exception { + private static void genParseDnsLabel(ApfV4Generator gen, JumpTable jumpTable) throws Exception { final String labelParseDnsLabelReal = "parse_dns_label_real"; final String labelPointerOffsetStored = "pointer_offset_stored"; @@ -101,7 +101,7 @@ public class DnsUtils { * JGT R0, R1, DROP // Bad pointer. Drop. */ gen.addLoadFromMemory(R0, SLOT_DNS_HEADER_OFFSET); - gen.addJumpIfR0GreaterThanR1(ApfGenerator.DROP_LABEL); + gen.addJumpIfR0GreaterThanR1(ApfV4Generator.DROP_LABEL); /** * // Now parse the label. @@ -147,8 +147,8 @@ public class DnsUtils { gen.addLoadFromMemory(R1, SLOT_DNS_HEADER_OFFSET); gen.addAddR1(); gen.addLoadFromMemory(R1, SLOT_CURRENT_PARSE_OFFSET); - gen.addJumpIfR0EqualsR1(ApfGenerator.DROP_LABEL); - gen.addJumpIfR0GreaterThanR1(ApfGenerator.DROP_LABEL); + gen.addJumpIfR0EqualsR1(ApfV4Generator.DROP_LABEL); + gen.addJumpIfR0GreaterThanR1(ApfV4Generator.DROP_LABEL); gen.addStoreToMemory(R0, SLOT_CURRENT_PARSE_OFFSET); /** // Pointer chased. Parse starting from the pointer destination (which may also be a @@ -192,7 +192,7 @@ public class DnsUtils { * Outputs: * None */ - private static void genFindNextDnsQuestion(ApfGenerator gen, JumpTable jumpTable) + private static void genFindNextDnsQuestion(ApfV4Generator gen, JumpTable jumpTable) throws Exception { final String labelFindNextDnsQuestionFollow = "find_next_dns_question_follow"; final String labelFindNextDnsQuestionLabel = "find_next_dns_question_label"; @@ -257,7 +257,7 @@ public class DnsUtils { gen.addLoadFromMemory(R0, SLOT_NEGATIVE_QDCOUNT_REMAINING); gen.addAdd(1); gen.addStoreToMemory(R0, SLOT_NEGATIVE_QDCOUNT_REMAINING); - gen.addJumpIfR0Equals(0, ApfGenerator.DROP_LABEL); + gen.addJumpIfR0Equals(0, ApfV4Generator.DROP_LABEL); // If not, return. gen.addJump(jumpTable.getStartLabel()); @@ -278,7 +278,7 @@ public class DnsUtils { return "dns_nomatch_" + labelIndex; } - private static void addMatchLabel(@NonNull ApfGenerator gen, @NonNull JumpTable jumpTable, + private static void addMatchLabel(@NonNull ApfV4Generator gen, @NonNull JumpTable jumpTable, int labelIndex, @NonNull String label, @NonNull String nextLabel) throws Exception { final String parsedLabel = getPostMatchJumpTargetForLabel(labelIndex); final String noMatchLabel = getNoMatchLabel(labelIndex); @@ -346,7 +346,7 @@ public class DnsUtils { * than hit the instruction limit. * </ul> */ - public static void generateFilter(ApfGenerator gen, String[] labels) throws Exception { + public static void generateFilter(ApfV4Generator gen, String[] labels) throws Exception { final int etherPlusUdpLen = ETHER_HEADER_LEN + UDP_HEADER_LEN; final String labelJumpTable = "jump_table"; @@ -401,11 +401,11 @@ public class DnsUtils { gen.defineLabel(LABEL_START_MATCH); for (int i = 0; i < labels.length; i++) { final String nextLabel = (i == labels.length - 1) - ? ApfGenerator.PASS_LABEL + ? ApfV4Generator.PASS_LABEL : getStartMatchLabel(i + 1); addMatchLabel(gen, table, i, labels[i], nextLabel); } - gen.addJump(ApfGenerator.DROP_LABEL); + gen.addJump(ApfV4Generator.DROP_LABEL); } private DnsUtils() { diff --git a/src/android/net/apf/JumpTable.java b/src/android/net/apf/JumpTable.java index b449697d..9b012258 100644 --- a/src/android/net/apf/JumpTable.java +++ b/src/android/net/apf/JumpTable.java @@ -16,7 +16,7 @@ package android.net.apf; -import static android.net.apf.ApfGenerator.Register.R0; +import static android.net.apf.ApfV4Generator.Register.R0; import androidx.annotation.NonNull; @@ -34,8 +34,8 @@ import java.util.Objects; * At compile time, any code that calls a subroutine must: * * <ul> - * <li>Define a label (via {@link ApfGenerator#defineLabel}) immediately after the code that invokes - * the subroutine. + * <li>Define a label (via {@link ApfV4Generator#defineLabel}) immediately after the code that + * invokes the subroutine. * <li>Add the label to the jump table using {@link #addLabel}. * <li>Generate the jump table in the program. * </ul> @@ -88,7 +88,7 @@ public class JumpTable { Objects.requireNonNull(startLabel); mStartLabel = startLabel; if (returnAddressMemorySlot < 0 - || returnAddressMemorySlot >= ApfGenerator.FIRST_PREFILLED_MEMORY_SLOT) { + || returnAddressMemorySlot >= ApfV4Generator.FIRST_PREFILLED_MEMORY_SLOT) { throw new IllegalArgumentException("Invalid memory slot " + returnAddressMemorySlot); } mReturnAddressMemorySlot = returnAddressMemorySlot; @@ -122,8 +122,8 @@ public class JumpTable { } /** Generates APF code for this jump table */ - public void generate(@NonNull ApfGenerator gen) - throws ApfGenerator.IllegalInstructionException { + public void generate(@NonNull ApfV4Generator gen) + throws ApfV4Generator.IllegalInstructionException { gen.defineLabel(mStartLabel); gen.addLoadFromMemory(R0, mReturnAddressMemorySlot); for (Map.Entry<String, Integer> e : mJumpLabels.entrySet()) { @@ -131,6 +131,6 @@ public class JumpTable { } // Cannot happen unless the program is malformed (i.e., the APF code loads an invalid return // label index before jumping to the subroutine. - gen.addJump(ApfGenerator.PASS_LABEL); + gen.addJump(ApfV4Generator.PASS_LABEL); } } diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java index 6b93d89f..d81f8b41 100644 --- a/src/android/net/apf/LegacyApfFilter.java +++ b/src/android/net/apf/LegacyApfFilter.java @@ -43,8 +43,8 @@ import android.net.LinkProperties; import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCounterTracker.Counter; -import android.net.apf.ApfGenerator.IllegalInstructionException; -import android.net.apf.ApfGenerator.Register; +import android.net.apf.ApfV4Generator.IllegalInstructionException; +import android.net.apf.ApfV4Generator.Register; import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; @@ -124,7 +124,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { /** * When APFv4 is supported, loads R1 with the offset of the specified counter. */ - private void maybeSetupCounter(ApfGenerator gen, Counter c) { + private void maybeSetupCounter(ApfV4Generator gen, Counter c) { if (mApfCapabilities.hasDataAccess()) { gen.addLoadImmediate(Register.R1, c.offset()); } @@ -204,7 +204,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { private void logStats() { final long nowMs = mClock.elapsedRealtime(); - synchronized (this) { + synchronized (LegacyApfFilter.this) { final ApfStats stats = new ApfStats.Builder() .setReceivedRas(mReceivedRas) .setMatchingRas(mMatchingRas) @@ -408,8 +408,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { } else { // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP, // preserving the original pre-APFv4 behavior. - mCountAndPassLabel = ApfGenerator.PASS_LABEL; - mCountAndDropLabel = ApfGenerator.DROP_LABEL; + mCountAndPassLabel = ApfV4Generator.PASS_LABEL; + mCountAndDropLabel = ApfV4Generator.DROP_LABEL; } // Now fill the black list from the passed array @@ -439,7 +439,6 @@ public class LegacyApfFilter implements AndroidPacketFilter { return mUniqueCounter++; } - @GuardedBy("this") private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) { ArrayList<Integer> bl = new ArrayList<Integer>(); @@ -952,13 +951,13 @@ public class LegacyApfFilter implements AndroidPacketFilter { } // Filter for a fraction of the lifetime and adjust for the age of the RA. - @GuardedBy("ApfFilter.this") + @GuardedBy("LegacyApfFilter.this") int filterLifetime() { return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER) - (int) (mProgramBaseTime - mLastSeen); } - @GuardedBy("ApfFilter.this") + @GuardedBy("LegacyApfFilter.this") boolean shouldFilter() { return filterLifetime() > 0; } @@ -969,8 +968,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { // value of this function is used to calculate the program min lifetime (which corresponds // to the smallest generated filter lifetime). Returning Long.MAX_VALUE in the case no // filter gets generated makes sure the program lifetime stays unaffected. - @GuardedBy("ApfFilter.this") - long generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("LegacyApfFilter.this") + long generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { String nextFilterLabel = "Ra" + getUniqueNumberLocked(); // Skip if packet is not the right size gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT); @@ -1018,7 +1017,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { // Append a filter for this keepalive ack to {@code gen}. // Jump to drop if it matches the keepalive ack. // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException; } // A class to hold NAT-T keepalive ack information. @@ -1061,7 +1060,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { } @Override - void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("LegacyApfFilter.this") + void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); @@ -1163,7 +1163,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { // Append a filter for this keepalive ack to {@code gen}. // Jump to drop if it matches the keepalive ack. // Jump to the next filter if packet doesn't match the keepalive ack. - abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException; } private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { @@ -1176,7 +1176,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { } @Override - void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("LegacyApfFilter.this") + void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); @@ -1219,7 +1220,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { } @Override - void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); } } @@ -1292,7 +1293,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { * - Packet being filtered is ARP */ @GuardedBy("this") - private void generateArpFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + private void generateArpFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { // Here's a basic summary of what the ARP filter program does: // // if not ARP IPv4 @@ -1360,7 +1361,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { * - Packet being filtered is IPv4 */ @GuardedBy("this") - private void generateIPv4FilterLocked(ApfGenerator gen) throws IllegalInstructionException { + private void generateIPv4FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv4 filter program does: // // if filtering multicast (i.e. multicast lock not held): @@ -1441,7 +1442,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { gen.addJump(mCountAndPassLabel); } - private void generateKeepaliveFilters(ApfGenerator gen, Class<?> filterType, int proto, + @GuardedBy("this") + private void generateKeepaliveFilters(ApfV4Generator gen, Class<?> filterType, int proto, int offset, String label) throws IllegalInstructionException { final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets, ack -> filterType.isInstance(ack)); @@ -1462,12 +1464,14 @@ public class LegacyApfFilter implements AndroidPacketFilter { gen.defineLabel(label); } - private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("this") + private void generateV4KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, "skip_v4_keepalive_filter"); } - private void generateV4NattKeepaliveFilters(ApfGenerator gen) + @GuardedBy("this") + private void generateV4NattKeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, NattKeepaliveResponse.class, IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter"); @@ -1480,7 +1484,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { * - Packet being filtered is IPv6 */ @GuardedBy("this") - private void generateIPv6FilterLocked(ApfGenerator gen) throws IllegalInstructionException { + private void generateIPv6FilterLocked(ApfV4Generator gen) throws IllegalInstructionException { // Here's a basic summary of what the IPv6 filter program does: // // if there is a hop-by-hop option present (e.g. MLD query) @@ -1580,7 +1584,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check. */ @GuardedBy("this") - private void generateMdnsFilterLocked(ApfGenerator gen) + private void generateMdnsFilterLocked(ApfV4Generator gen) throws IllegalInstructionException { final String skipMdnsv4Filter = "skip_mdns_v4_filter"; final String skipMdnsFilter = "skip_mdns_filter"; @@ -1680,8 +1684,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { gen.defineLabel(skipMdnsFilter); } - - private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + @GuardedBy("this") + private void generateV6KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException { generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, "skip_v6_keepalive_filter"); } @@ -1707,9 +1711,9 @@ public class LegacyApfFilter implements AndroidPacketFilter { * </ul> */ @GuardedBy("this") - protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { // This is guaranteed to succeed because of the check in maybeCreate. - ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported); + ApfV4Generator gen = new ApfV4Generator(mApfCapabilities.apfVersionSupported); if (mApfCapabilities.hasDataAccess()) { // Increment TOTAL_PACKETS @@ -1792,7 +1796,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { * before jumping to the actual PASS and DROP labels. */ @GuardedBy("this") - private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException { + private void emitEpilogue(ApfV4Generator gen) throws IllegalInstructionException { // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it // will just fall-through to the PASS label. if (!mApfCapabilities.hasDataAccess()) return; @@ -1822,6 +1826,8 @@ public class LegacyApfFilter implements AndroidPacketFilter { * Generate and install a new filter program. */ @GuardedBy("this") + // errorprone false positive on ra#shouldFilter and ra#generateFilterLocked + @SuppressWarnings("GuardedBy") @VisibleForTesting public void installNewProgramLocked() { purgeExpiredRasLocked(); @@ -1837,7 +1843,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { mProgramBaseTime = currentTimeSeconds(); try { // Step 1: Determine how many RA filters we can fit in the program. - ApfGenerator gen = emitPrologueLocked(); + ApfV4Generator gen = emitPrologueLocked(); // The epilogue normally goes after the RA filters, but add it early to include its // length when estimating the total. @@ -1914,6 +1920,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { /** * Returns {@code true} if a new program should be installed because the current one dies soon. */ + @GuardedBy("this") private boolean shouldInstallnewProgram() { long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime; return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING; @@ -2042,7 +2049,7 @@ public class LegacyApfFilter implements AndroidPacketFilter { // 1. the program generator will need its offsets adjusted. // 2. the packet filter attached to our packet socket will need its offset adjusted. if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null; - if (!ApfGenerator.supportsVersion(apfCapabilities.apfVersionSupported)) { + if (!ApfV4Generator.supportsVersion(apfCapabilities.apfVersionSupported)) { Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported); return null; } diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index b0bac047..4e6a222a 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java @@ -53,7 +53,6 @@ import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.net.module.util.NetworkStackConstants.IPV4_CONFLICT_ANNOUNCE_NUM; import static com.android.net.module.util.NetworkStackConstants.IPV4_CONFLICT_PROBE_NUM; import static com.android.net.module.util.SocketUtils.closeSocketQuietly; -import static com.android.networkstack.util.NetworkStackUtils.DHCP_INIT_REBOOT_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; import static com.android.networkstack.util.NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION; @@ -555,13 +554,6 @@ public class DhcpClient extends StateMachine { } /** - * check whether or not to support caching the last lease info and INIT-REBOOT state. - */ - public boolean isDhcpLeaseCacheEnabled() { - return mDependencies.isFeatureNotChickenedOut(mContext, DHCP_INIT_REBOOT_VERSION); - } - - /** * check whether or not to support DHCP Rapid Commit option. */ public boolean isDhcpRapidCommitEnabled() { @@ -584,7 +576,7 @@ public class DhcpClient extends StateMachine { } private void recordMetricEnabledFeatures() { - if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT); + mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT); if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT); if (isDhcpIpConflictDetectEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD); if (mConfiguration.isPreconnectionEnabled) { @@ -855,17 +847,13 @@ public class DhcpClient extends StateMachine { } private void notifySuccess() { - if (isDhcpLeaseCacheEnabled()) { - maybeSaveLeaseToIpMemoryStore(); - } + maybeSaveLeaseToIpMemoryStore(); mController.sendMessage( CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); } private void notifyFailure(int arg) { - if (isDhcpLeaseCacheEnabled()) { - setLeaseExpiredToIpMemoryStore(); - } + setLeaseExpiredToIpMemoryStore(); mController.sendMessage(CMD_POST_DHCP_ACTION, arg, 0, null); } @@ -1028,7 +1016,7 @@ public class DhcpClient extends StateMachine { if (mConfiguration.isPreconnectionEnabled) { transitionTo(mDhcpPreconnectingState); } else { - startInitRebootOrInit(); + startInitReboot(); } recordMetricEnabledFeatures(); return HANDLED; @@ -1354,13 +1342,8 @@ public class DhcpClient extends StateMachine { } } - private void startInitRebootOrInit() { - if (isDhcpLeaseCacheEnabled()) { - preDhcpTransitionTo(mWaitBeforeObtainingConfigurationState, - mObtainingConfigurationState); - } else { - preDhcpTransitionTo(mWaitBeforeStartState, mDhcpInitState); - } + private void startInitReboot() { + preDhcpTransitionTo(mWaitBeforeObtainingConfigurationState, mObtainingConfigurationState); } class DhcpPreconnectingState extends TimeoutState { @@ -1392,7 +1375,7 @@ public class DhcpClient extends StateMachine { mConfiguration.isPreconnectionEnabled); return HANDLED; case CMD_ABORT_PRECONNECTION: - startInitRebootOrInit(); + startInitReboot(); return HANDLED; default: return NOT_HANDLED; @@ -1410,7 +1393,7 @@ public class DhcpClient extends StateMachine { // and send a DISCOVER. @Override public void timeout() { - startInitRebootOrInit(); + startInitReboot(); } private void sendPreconnectionPacket() { @@ -1507,7 +1490,8 @@ public class DhcpClient extends StateMachine { // IpClient sees the IP address appear, it will enter provisioned state without any // configuration information from DHCP. http://b/146850745. notifySuccess(); - mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress); + mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.leaseDuration, 0, + mDhcpLease.ipAddress); } @Override @@ -2083,7 +2067,7 @@ public class DhcpClient extends StateMachine { } protected void timeout() { - startInitRebootOrInit(); + startInitReboot(); } } diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java index 53dd274d..ff7b036d 100644 --- a/src/android/net/dhcp6/Dhcp6Packet.java +++ b/src/android/net/dhcp6/Dhcp6Packet.java @@ -561,8 +561,6 @@ public class Dhcp6Packet { if (pd != null) { newPacket.mPrefixDelegation = pd; newPacket.mIaId = pd.iaid; - } else { - throw new ParseException("Missing IA_PD option"); } newPacket.mStatusCode = statusCode; newPacket.mRapidCommit = rapidCommit; @@ -600,8 +598,10 @@ public class Dhcp6Packet { Log.e(TAG, "Unexpected transaction ID " + mTransId + ", expected " + transId); return false; } - // mPrefixDelegation is guaranteed to be non-null. In decode() function, we throw the - // exception if IA_PD option doesn't exist. + if (mPrefixDelegation == null) { + Log.e(TAG, "DHCPv6 message without IA_PD option, ignoring"); + return false; + } if (!mPrefixDelegation.isValid()) { Log.e(TAG, "DHCPv6 message takes invalid IA_PD option, ignoring"); return false; diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index d7ef4df2..56dbb6f1 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java @@ -3052,14 +3052,22 @@ public class IpClient extends StateMachine { } } - private void addInterfaceAddress(@NonNull final Inet6Address address, + private void addInterfaceAddress(@Nullable final Inet6Address address, @NonNull final IaPrefixOption ipo) { final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD; final long now = SystemClock.elapsedRealtime(); - final long deprecationTime = now + ipo.preferred; - final long expirationTime = now + ipo.valid; - final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, - RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); + // Per RFC8415 section 21.22 the preferred/valid lifetime in IA Prefix option + // expressed in units of seconds. + final long deprecationTime = now + ipo.preferred * 1000; + final long expirationTime = now + ipo.valid * 1000; + final LinkAddress la; + try { + la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, + RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid IPv6 link address " + e); + return; + } if (!la.isGlobalPreferred()) { Log.w(TAG, la + " is not a global IPv6 address"); return; @@ -3075,15 +3083,21 @@ public class IpClient extends StateMachine { private void updateDelegatedAddresses(@NonNull final List<IaPrefixOption> valid) { if (valid.isEmpty()) return; + final List<IpPrefix> zeroLifetimePrefixList = new ArrayList<>(); for (IaPrefixOption ipo : valid) { final IpPrefix prefix = ipo.getIpPrefix(); // The prefix with preferred/valid lifetime of 0 is considered as a valid prefix, - // it can be passed to IpClient from Dhcp6Client, however, client should stop using - // the global addresses derived from this prefix immediately. + // and can be passed to IpClient from Dhcp6Client, but client should stop using + // the global addresses derived from this prefix asap. Deleting the associated + // global IPv6 addresses immediately before adding another IPv6 address may result + // in a race where the device throws the provisioning failure callback due to the + // loss of all valid IPv6 addresses, however, IPv6 provisioning will soon complete + // successfully when the user space sees the new IPv6 address update. To avoid this + // race, temporarily store all prefix(es) with 0 preferred/valid lifetime and then + // delete them after iterating through all valid IA prefix options. if (ipo.withZeroLifetimes()) { - Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix - + " with 0 preferred/valid lifetime"); - deleteIpv6PrefixDelegationAddresses(prefix); + zeroLifetimePrefixList.add(prefix); + continue; } // Otherwise, configure IPv6 addresses derived from the delegated prefix(es) on // the interface. We've checked that delegated prefix is valid upon receiving the @@ -3094,6 +3108,15 @@ public class IpClient extends StateMachine { macAddressToEui64(mInterfaceParams.macAddr)); addInterfaceAddress(address, ipo); } + + // Delete global IPv6 addresses derived from prefix with 0 preferred/valid lifetime. + if (!zeroLifetimePrefixList.isEmpty()) { + for (IpPrefix prefix : zeroLifetimePrefixList) { + Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix + + " with 0 preferred/valid lifetime"); + deleteIpv6PrefixDelegationAddresses(prefix); + } + } } private void removeExpiredDelegatedAddresses(@NonNull final List<IaPrefixOption> expired) { @@ -3235,8 +3258,19 @@ public class IpClient extends StateMachine { break; case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { + final int leaseDuration = msg.arg1; final LinkAddress ipAddress = (LinkAddress) msg.obj; - if (mInterfaceCtrl.setIPv4Address(ipAddress)) { + // For IPv4 link addresses, there is no concept of preferred/valid lifetimes. + // Populate the ifa_cacheinfo attribute in the netlink message with the DHCP + // lease duration, which is used by the kernel to maintain the validity of the + // IP addresses. + if (NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, + ipAddress.getAddress(), + (short) ipAddress.getPrefixLength(), + 0 /* flags */, + (byte) RT_SCOPE_UNIVERSE /* scope */, + leaseDuration /* preferred */, + leaseDuration /* valid */)) { // Although it's impossible to happen that DHCP client becomes null in // RunningState and then NPE is thrown when it attempts to send a message // on an null object, sometimes it's found during stress tests. If this diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java index 2068caa7..22ee1010 100644 --- a/src/android/net/ip/IpClientLinkObserver.java +++ b/src/android/net/ip/IpClientLinkObserver.java @@ -21,6 +21,7 @@ import static android.system.OsConstants.AF_UNSPEC; import static android.system.OsConstants.IFF_LOOPBACK; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE; import static com.android.net.module.util.netlink.NetlinkConstants.IFF_LOWER_UP; import static com.android.net.module.util.netlink.NetlinkConstants.RTM_F_CLONED; import static com.android.net.module.util.netlink.NetlinkConstants.RTN_UNICAST; @@ -38,6 +39,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.RouteInfo; import android.os.Handler; +import android.os.SystemClock; import android.system.OsConstants; import android.util.Log; @@ -56,6 +58,7 @@ import com.android.net.module.util.netlink.NetlinkMessage; import com.android.net.module.util.netlink.RtNetlinkAddressMessage; import com.android.net.module.util.netlink.RtNetlinkLinkMessage; import com.android.net.module.util.netlink.RtNetlinkRouteMessage; +import com.android.net.module.util.netlink.StructIfacacheInfo; import com.android.net.module.util.netlink.StructIfaddrMsg; import com.android.net.module.util.netlink.StructIfinfoMsg; import com.android.net.module.util.netlink.StructNdOptPref64; @@ -629,13 +632,31 @@ public class IpClientLinkObserver implements NetworkObserver { } } + // The preferred/valid in ifa_cacheinfo expressed in units of seconds, convert + // it to milliseconds for deprecationTime or expirationTime used in LinkAddress. + private static long getDeprecationOrExpirationTime( + @Nullable final StructIfacacheInfo cacheInfo, long now, boolean deprecationTime) { + if (cacheInfo == null) return LinkAddress.LIFETIME_UNKNOWN; + final long lifetime = deprecationTime ? cacheInfo.preferred : cacheInfo.valid; + return (lifetime == Integer.toUnsignedLong(INFINITE_LEASE)) + ? LinkAddress.LIFETIME_PERMANENT + : now + lifetime * 1000; + } + private void processRtNetlinkAddressMessage(RtNetlinkAddressMessage msg) { if (!mNetlinkEventParsingEnabled) return; final StructIfaddrMsg ifaddrMsg = msg.getIfaddrHeader(); if (ifaddrMsg.index != mIfindex) return; + + final StructIfacacheInfo cacheInfo = msg.getIfacacheInfo(); + final long now = SystemClock.elapsedRealtime(); + final long deprecationTime = + getDeprecationOrExpirationTime(cacheInfo, now, true /* deprecationTime */); + final long expirationTime = + getDeprecationOrExpirationTime(cacheInfo, now, false /* deprecationTime */); final LinkAddress la = new LinkAddress(msg.getIpAddress(), ifaddrMsg.prefixLen, - msg.getFlags(), ifaddrMsg.scope); + msg.getFlags(), ifaddrMsg.scope, deprecationTime, expirationTime); switch (msg.getHeader().nlmsg_type) { case NetlinkConstants.RTM_NEWADDR: diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java index e252a68b..d2b1064d 100644 --- a/src/android/net/ip/IpReachabilityMonitor.java +++ b/src/android/net/ip/IpReachabilityMonitor.java @@ -23,6 +23,9 @@ import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC; import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION; import android.content.Context; import android.net.ConnectivityManager; @@ -235,8 +238,11 @@ public class IpReachabilityMonitor { private int mInterSolicitIntervalMs; @NonNull private final Callback mCallback; + private final boolean mMulticastResolicitEnabled; private final boolean mIgnoreIncompleteIpv6DnsServerEnabled; private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled; + private final boolean mMacChangeFailureOnlyAfterRoam; + private final boolean mIgnoreOrganicNudFailure; public IpReachabilityMonitor( Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback, @@ -258,10 +264,16 @@ public class IpReachabilityMonitor { mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker; mCm = context.getSystemService(ConnectivityManager.class); mDependencies = dependencies; + mMulticastResolicitEnabled = dependencies.isFeatureNotChickenedOut(context, + IP_REACHABILITY_MCAST_RESOLICIT_VERSION); mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureNotChickenedOut(context, IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION); mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context, IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION); + mMacChangeFailureOnlyAfterRoam = dependencies.isFeatureNotChickenedOut(context, + IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION); + mIgnoreOrganicNudFailure = dependencies.isFeatureEnabled(context, + IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION); mMetricsLog = metricsLog; mNetd = netd; Preconditions.checkNotNull(mNetd); @@ -270,8 +282,10 @@ public class IpReachabilityMonitor { // In case the overylaid parameters specify an invalid configuration, set the parameters // to the hardcoded defaults first, then set them to the values used in the steady state. try { - setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, - NUD_MCAST_RESOLICIT_NUM); + int numResolicits = mMulticastResolicitEnabled + ? NUD_MCAST_RESOLICIT_NUM + : INVALID_NUD_MCAST_RESOLICIT_NUM; + setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits); } catch (Exception e) { Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults"); } @@ -349,7 +363,15 @@ public class IpReachabilityMonitor { private boolean hasDefaultRouterNeighborMacAddressChanged( @Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) { - if (prev == null || !isNeighborDefaultRouter(event)) return false; + // TODO: once this rolls out safely, merge something like aosp/2908139 and remove this code. + if (mMacChangeFailureOnlyAfterRoam) { + if (!isNeighborDefaultRouter(event)) return false; + if (prev == null || prev.nudState != StructNdMsg.NUD_PROBE) return false; + if (!isNudFailureDueToRoam()) return false; + } else { + // Previous, incorrect, behaviour: MAC address changes are a failure at all times. + if (prev == null || !isNeighborDefaultRouter(event)) return false; + } return !event.macAddr.equals(prev.macAddr); } @@ -408,7 +430,8 @@ public class IpReachabilityMonitor { private void handleNeighborReachable(@Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) { - if (hasDefaultRouterNeighborMacAddressChanged(prev, event)) { + if (mMulticastResolicitEnabled + && hasDefaultRouterNeighborMacAddressChanged(prev, event)) { // This implies device has confirmed the neighbor's reachability from // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac // address hasn't changed is required. If Mac address does change, then @@ -512,7 +535,13 @@ public class IpReachabilityMonitor { Log.w(TAG, logMsg); // TODO: remove |ip| when the callback signature no longer has // an InetAddress argument. - mCallback.notifyLost(ip, logMsg, type); + // Notify critical neighbor lost as long as the NUD failures + // are not from kernel organic or the NUD failure event type is + // NUD_ORGANIC_FAILED_CRITICAL but the experiment flag is not + // enabled. Regardless, the event metrics are still recoreded. + if (type != NudEventType.NUD_ORGANIC_FAILED_CRITICAL || !mIgnoreOrganicNudFailure) { + mCallback.notifyLost(ip, logMsg, type); + } } logNudFailed(event, type); } @@ -574,7 +603,8 @@ public class IpReachabilityMonitor { private long getProbeWakeLockDuration() { final long gracePeriodMs = 500; - final int numSolicits = mNumSolicits + NUD_MCAST_RESOLICIT_NUM; + final int numSolicits = + mNumSolicits + (mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : 0); return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs; } diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index 829d0c6a..c2de03cd 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java @@ -151,11 +151,6 @@ public class NetworkStackUtils { new String [] {"https://www.google.com/generate_204"}; /** - * Minimum module version at which to enable the DHCP INIT-REBOOT state. - */ - public static final String DHCP_INIT_REBOOT_VERSION = "dhcp_init_reboot_version"; - - /** * Minimum module version at which to enable the DHCP Rapid Commit option. */ public static final String DHCP_RAPID_COMMIT_VERSION = "dhcp_rapid_commit_version"; @@ -212,6 +207,13 @@ public class NetworkStackUtils { "ipclient_accept_ipv6_link_local_dns_version"; /** + * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor, + * set it to 3 by default. + */ + public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION = + "ip_reachability_mcast_resolicit_version"; + + /** * Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to * address resolution. */ @@ -226,6 +228,18 @@ public class NetworkStackUtils { "ip_reachability_ignore_incompleted_ipv6_default_router_version"; /** + * Experiment flag to treat router MAC address changes as a failure only on roam. + */ + public static final String IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION = + "ip_reachability_router_mac_change_failure_only_after_roam_version"; + + /** + * Experiment flag to ignore all NUD failures from kernel organic. + */ + public static final String IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION = + "ip_reachability_ignore_organic_nud_failure_version"; + + /** * Experiment flag to enable DHCPv6 Prefix Delegation(RFC8415) in IpClient. */ public static final String IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION = @@ -235,10 +249,12 @@ public class NetworkStackUtils { * Experiment flag to enable new ra filter. */ public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"; + /** * Experiment flag to enable the feature of polling counters in Apf. */ public static final String APF_POLLING_COUNTERS_VERSION = "apf_polling_counters_version"; + /** * Experiment flag to enable the feature of ignoring any individual RA section with lifetime * below accept_ra_min_lft sysctl. @@ -246,6 +262,12 @@ public class NetworkStackUtils { public static final String IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION = "ipclient_ignore_low_ra_lifetime_version"; + /** + * Feature flag to send private DNS resolution queries and probes on a background thread. + */ + public static final String NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION = + "networkmonitor_async_privdns_resolution"; + /**** BEGIN Feature Kill Switch Flags ****/ /** @@ -350,9 +372,10 @@ public class NetworkStackUtils { * Generate an IPv6 address based on the given prefix(/64) and stable interface * identifier(EUI64). */ + @Nullable public static Inet6Address createInet6AddressFromEui64(@NonNull final IpPrefix prefix, @NonNull final byte[] eui64) { - if (prefix.getPrefixLength() != 64) { + if (prefix.getPrefixLength() > 64) { Log.e(TAG, "Invalid IPv6 prefix length " + prefix.getPrefixLength()); return null; } diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index 9303f956..78a47ea8 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java @@ -23,6 +23,9 @@ import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC; import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL; import static android.net.DnsResolver.FLAG_EMPTY; +import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP; +import static android.net.DnsResolver.TYPE_A; +import static android.net.DnsResolver.TYPE_AAAA; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY; import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID; @@ -122,6 +125,7 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -140,7 +144,9 @@ import android.telephony.CellSignalStrength; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import androidx.annotation.ArrayRes; @@ -234,6 +240,10 @@ public class NetworkMonitor extends StateMachine { @VisibleForTesting static final String CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT = "captive_portal_dns_probe_timeout"; + @VisibleForTesting + static final String CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS = + "async_privdns_probe_timeout_ms"; + private static final int DEFAULT_PRIVDNS_PROBE_TIMEOUT_MS = 10_000; private static final int SOCKET_TIMEOUT_MS = 10000; private static final int PROBE_TIMEOUT_MS = 3000; @@ -404,6 +414,32 @@ public class NetworkMonitor extends StateMachine { */ private static final int EVENT_RESOURCE_CONFIG_CHANGED = 25; + /** + * Message to self to notify that private DNS strict mode hostname resolution has finished. + * + * <p>arg2 = Last DNS rcode. + * obj = Pair<List<InetAddress>, DnsCallback>: query results and DnsCallback used. + */ + private static final int CMD_STRICT_MODE_RESOLUTION_COMPLETED = 26; + + /** + * Message to self to notify that the private DNS probe has finished. + * + * <p>arg2 = Last DNS rcode. + * obj = Pair<List<InetAddress>, DnsCallback>: query results and DnsCallback used. + */ + private static final int CMD_PRIVATE_DNS_PROBE_COMPLETED = 27; + + /** + * Message to self to notify that private DNS hostname resolution or probing has failed. + */ + private static final int CMD_PRIVATE_DNS_EVALUATION_FAILED = 28; + + /** + * Message to self to notify that a DNS query has timed out. + */ + private static final int CMD_DNS_TIMEOUT = 29; + // Start mReevaluateDelayMs at this value and double. @VisibleForTesting static final int INITIAL_REEVALUATE_DELAY_MS = 1000; @@ -504,6 +540,10 @@ public class NetworkMonitor extends StateMachine { private final State mEvaluatingState = new EvaluatingState(); private final State mCaptivePortalState = new CaptivePortalState(); private final State mEvaluatingPrivateDnsState = new EvaluatingPrivateDnsState(); + private final State mStartingPrivateDnsEvaluation = new StartingPrivateDnsEvaluation(); + private final State mResolvingPrivateDnsState = new ResolvingPrivateDnsState(); + private final State mProbingForPrivateDnsState = new ProbingForPrivateDnsState(); + private final State mProbingState = new ProbingState(); private final State mWaitingForNextProbeState = new WaitingForNextProbeState(); private final State mEvaluatingBandwidthState = new EvaluatingBandwidthState(); @@ -544,6 +584,8 @@ public class NetworkMonitor extends StateMachine { private final boolean mMetricsEnabled; private final boolean mReevaluateWhenResumeEnabled; + private final boolean mAsyncPrivdnsResolutionEnabled; + @NonNull private final NetworkInformationShim mInfoShim = NetworkInformationShimImpl.newInstance(); @@ -614,6 +656,9 @@ public class NetworkMonitor extends StateMachine { addState(mWaitingForNextProbeState, mEvaluatingState); addState(mCaptivePortalState, mMaybeNotifyState); addState(mEvaluatingPrivateDnsState, mDefaultState); + addState(mStartingPrivateDnsEvaluation, mEvaluatingPrivateDnsState); + addState(mResolvingPrivateDnsState, mEvaluatingPrivateDnsState); + addState(mProbingForPrivateDnsState, mEvaluatingPrivateDnsState); addState(mEvaluatingBandwidthState, mDefaultState); addState(mValidatedState, mDefaultState); setInitialState(mDefaultState); @@ -632,6 +677,8 @@ public class NetworkMonitor extends StateMachine { NetworkStackUtils.VALIDATION_METRICS_VERSION); mReevaluateWhenResumeEnabled = deps.isFeatureEnabled( context, NetworkStackUtils.REEVALUATE_WHEN_RESUME); + mAsyncPrivdnsResolutionEnabled = deps.isFeatureEnabled(context, + NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION); mUseHttps = getUseHttpsValidation(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalFallbackSpecs = @@ -1049,6 +1096,13 @@ public class NetworkMonitor extends StateMachine { if (tst != null) { tst.setOpportunisticMode(cfg.inOpportunisticMode()); } + if (mAsyncPrivdnsResolutionEnabled) { + // When using async privdns validation, reevaluate on any change of + // configuration (even if turning it off), as this will handle + // cancelling current attempts and transitioning to validated state. + removeMessages(CMD_EVALUATE_PRIVATE_DNS); + sendMessage(CMD_EVALUATE_PRIVATE_DNS); + } break; } @@ -1599,13 +1653,28 @@ public class NetworkMonitor extends StateMachine { @Override public boolean processMessage(Message msg) { switch (msg.what) { - case CMD_EVALUATE_PRIVATE_DNS: + case CMD_EVALUATE_PRIVATE_DNS: { + if (mAsyncPrivdnsResolutionEnabled) { + // Cancel any previously scheduled retry attempt + removeMessages(CMD_EVALUATE_PRIVATE_DNS); + + if (inStrictMode()) { + // Note this may happen even in the case where the current state is + // resolve or probe: private DNS evaluation would then restart. + transitionTo(mStartingPrivateDnsEvaluation); + } else { + mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); + transitionToPrivateDnsEvaluationSuccessState(); + } + break; + } + if (inStrictMode()) { - if (!isStrictModeHostnameResolved()) { + if (!isStrictModeHostnameResolved(mPrivateDnsConfig)) { resolveStrictModeHostname(); - if (isStrictModeHostnameResolved()) { - notifyPrivateDnsConfigResolved(); + if (isStrictModeHostnameResolved(mPrivateDnsConfig)) { + notifyPrivateDnsConfigResolved(mPrivateDnsConfig); } else { handlePrivateDnsEvaluationFailure(); // The private DNS probe fails-fast if the server hostname cannot @@ -1630,23 +1699,24 @@ public class NetworkMonitor extends StateMachine { handlePrivateDnsEvaluationFailure(); break; } - handlePrivateDnsEvaluationSuccess(); + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, + true /* succeeded */); } else { mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); } - - if (needEvaluatingBandwidth()) { - transitionTo(mEvaluatingBandwidthState); - } else { - // All good! - transitionTo(mValidatedState); - } + transitionToPrivateDnsEvaluationSuccessState(); break; - case CMD_PRIVATE_DNS_SETTINGS_CHANGED: + } + case CMD_PRIVATE_DNS_SETTINGS_CHANGED: { // When settings change the reevaluation timer must be reset. mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS; // Let the message bubble up and be handled by parent states as usual. return NOT_HANDLED; + } + // Only used with mAsyncPrivdnsResolutionEnabled + case CMD_PRIVATE_DNS_EVALUATION_FAILED: { + reschedulePrivateDnsEvaluation(); + } default: return NOT_HANDLED; } @@ -1657,12 +1727,6 @@ public class NetworkMonitor extends StateMachine { return !TextUtils.isEmpty(mPrivateDnsProviderHostname); } - private boolean isStrictModeHostnameResolved() { - return (mPrivateDnsConfig != null) - && mPrivateDnsConfig.hostname.equals(mPrivateDnsProviderHostname) - && (mPrivateDnsConfig.ips.length > 0); - } - private void resolveStrictModeHostname() { try { // Do a blocking DNS resolution using the network-assigned nameservers. @@ -1675,24 +1739,15 @@ public class NetworkMonitor extends StateMachine { } } - private void notifyPrivateDnsConfigResolved() { - try { - mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel()); - } catch (RemoteException e) { - Log.e(TAG, "Error sending private DNS config resolved notification", e); - } - } - - private void handlePrivateDnsEvaluationSuccess() { - mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, - true /* succeeded */); - } - private void handlePrivateDnsEvaluationFailure() { mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, false /* succeeded */); mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, null /* redirectUrl */); + reschedulePrivateDnsEvaluation(); + } + + private void reschedulePrivateDnsEvaluation() { // Queue up a re-evaluation with backoff. // // TODO: Consider abandoning this state after a few attempts and @@ -1730,6 +1785,285 @@ public class NetworkMonitor extends StateMachine { } } + private void transitionToPrivateDnsEvaluationSuccessState() { + if (needEvaluatingBandwidth()) { + transitionTo(mEvaluatingBandwidthState); + } else { + // All good! + transitionTo(mValidatedState); + } + } + + private class StartingPrivateDnsEvaluation extends State { + @Override + public void enter() { + transitionTo(mResolvingPrivateDnsState); + } + } + + private class DnsCallback implements DnsResolver.Callback<List<InetAddress>> { + private final int mReplyMessage; + final CancellationSignal mCancellationSignal; + final boolean mHighPriorityResults; + + DnsCallback(int replyMessage, boolean highPriorityResults) { + mReplyMessage = replyMessage; + mCancellationSignal = new CancellationSignal(); + mHighPriorityResults = highPriorityResults; + } + + @Override + public void onAnswer(List<InetAddress> answer, int rcode) { + sendMessage(mReplyMessage, 0, rcode, new Pair<>(answer, this)); + } + + @Override + public void onError(DnsResolver.DnsException error) { + sendMessage(mReplyMessage, 0, error.code, new Pair<>(null, this)); + } + } + + /** + * Base class for a state that is sending a DNS query, cancelled if the state is exited. + */ + private abstract class DnsQueryState extends State { + private static final int ERROR_TIMEOUT = -1; + private final int mCompletedCommand; + private final ArraySet<DnsCallback> mPendingQueries = new ArraySet<>(2); + private final List<InetAddress> mResults = new ArrayList<>(); + private String mQueryName; + private long mStartTime; + + private DnsQueryState(int completedCommand) { + mCompletedCommand = completedCommand; + } + + @Override + public void enter() { + mPendingQueries.clear(); + mResults.clear(); + mStartTime = SystemClock.elapsedRealtimeNanos(); + + mQueryName = getQueryName(); + if (TextUtils.isEmpty(mQueryName)) { + // No query necessary (in particular not in strict mode): skip DNS query states + mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS); + transitionToPrivateDnsEvaluationSuccessState(); + return; + } + + final DnsResolver resolver = mDependencies.getDnsResolver(); + mPendingQueries.addAll(sendQueries(mQueryName, resolver)); + sendMessageDelayed(CMD_DNS_TIMEOUT, getTimeoutMs()); + } + + @Override + public void exit() { + removeMessages(CMD_DNS_TIMEOUT); + cancelAllQueries(); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what == mCompletedCommand) { + final Pair<List<InetAddress>, DnsCallback> result = + (Pair<List<InetAddress>, DnsCallback>) msg.obj; + if (!mPendingQueries.remove(result.second)) { + // Ignore previous queries if the state was exited and re-entered. This state + // calls cancelAllQueries on exit, but this may still happen if results were + // already posted when the querier processed the cancel request. + return HANDLED; + } + + if (result.first != null) { + if (result.second.mHighPriorityResults) { + mResults.addAll(0, result.first); + } else { + mResults.addAll(result.first); + } + } + + if (mPendingQueries.isEmpty()) { + removeMessages(CMD_DNS_TIMEOUT); + final long time = SystemClock.elapsedRealtimeNanos() - mStartTime; + onQueryDone(mQueryName, mResults, msg.arg2 /* lastRCode */, time); + } + return HANDLED; + } else if (msg.what == CMD_DNS_TIMEOUT) { + cancelAllQueries(); + // If some queries were successful, onQueryDone will still proceed, even if + // lastRCode is not a success code. + onQueryDone(mQueryName, mResults, ERROR_TIMEOUT /* lastRCode */, + SystemClock.elapsedRealtimeNanos() - mStartTime); + return HANDLED; + } + return NOT_HANDLED; + } + + private void cancelAllQueries() { + for (int i = 0; i < mPendingQueries.size(); i++) { + mPendingQueries.valueAt(i).mCancellationSignal.cancel(); + } + mPendingQueries.clear(); + } + + abstract void onQueryDone(@NonNull String queryName, @NonNull List<InetAddress> answer, + int lastRCode, long elapsedNanos); + + @NonNull + abstract String getQueryName(); + + abstract List<DnsCallback> sendQueries(@NonNull String queryName, + @NonNull DnsResolver resolver); + + abstract long getTimeoutMs(); + } + + private class ResolvingPrivateDnsState extends DnsQueryState { + private ResolvingPrivateDnsState() { + super(CMD_STRICT_MODE_RESOLUTION_COMPLETED); + } + + @Override + List<DnsCallback> sendQueries(@NonNull String queryName, @NonNull DnsResolver resolver) { + // Follow legacy behavior that sent AAAA and A queries synchronously in sequence: AAAA + // is marked as highPriorityResults, so they are placed first in the resulting list. + final DnsCallback v6Cb = new DnsCallback(CMD_STRICT_MODE_RESOLUTION_COMPLETED, + true /* highPriorityResults */); + final DnsCallback v4Cb = new DnsCallback(CMD_STRICT_MODE_RESOLUTION_COMPLETED, + false /* highPriorityResults */); + + resolver.query(mCleartextDnsNetwork, queryName, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP, + Runnable::run, v6Cb.mCancellationSignal, v6Cb); + resolver.query(mCleartextDnsNetwork, queryName, TYPE_A, FLAG_NO_CACHE_LOOKUP, + Runnable::run, v4Cb.mCancellationSignal, v4Cb); + + return List.of(v6Cb, v4Cb); + } + + @Override + void onQueryDone(@NonNull String queryName, @NonNull List<InetAddress> answer, + int lastRCode, long elapsedNanos) { + if (!Objects.equals(queryName, mPrivateDnsProviderHostname)) { + validationLog("Ignoring stale private DNS resolve answers for " + queryName + + " (now \"" + mPrivateDnsProviderHostname + "\"): " + answer); + // This may happen if mPrivateDnsProviderHostname was changed, in which case + // reevaluation must have been queued (CMD_EVALUATE_PRIVATE_DNS), but results for + // the first evaluation are received before the reevaluation command gets processed. + // Just ignore the results and wait for reevaluation to be processed. + // More generally, reevaluation is scheduled every time the hostname changes, so + // IP addresses matching the hostname are eventually received, but intermediate + // results should be ignored to avoid reporting a PrivateDnsConfig with IP addresses + // that don't match mPrivateDnsProviderHostname. + return; + } + + if (!answer.isEmpty()) { + final InetAddress[] ips = answer.toArray(new InetAddress[0]); + final PrivateDnsConfig config = + new PrivateDnsConfig(mPrivateDnsProviderHostname, ips); + notifyPrivateDnsConfigResolved(config); + + validationLog("Strict mode hostname resolution " + elapsedNanos + "ns OK " + + answer + " for " + mPrivateDnsProviderHostname); + transitionTo(mProbingForPrivateDnsState); + } else { + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, + false /* succeeded */); + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + null /* redirectUrl */); + + validationLog("Strict mode hostname resolution " + elapsedNanos + "ns FAIL " + + "lastRCode " + lastRCode + " for " + mPrivateDnsProviderHostname); + sendMessage(CMD_PRIVATE_DNS_EVALUATION_FAILED); + + // The private DNS probe fails-fast if the server hostname cannot + // be resolved. Record it as a failure with zero latency. + recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */, + ProbeResult.PR_FAILURE, null /* capportData */); + } + } + + @NonNull + @Override + String getQueryName() { + return mPrivateDnsProviderHostname; + } + + @Override + long getTimeoutMs() { + return getDnsProbeTimeout(); + } + } + + private class ProbingForPrivateDnsState extends DnsQueryState { + private ProbingForPrivateDnsState() { + super(CMD_PRIVATE_DNS_PROBE_COMPLETED); + } + + @Override + public void enter() { + super.enter(); + } + + @Override + List<DnsCallback> sendQueries(@NonNull String queryName, @NonNull DnsResolver resolver) { + final DnsCallback cb = new DnsCallback(CMD_PRIVATE_DNS_PROBE_COMPLETED, + false /* highPriorityResults */); + resolver.query(mNetwork, queryName, FLAG_EMPTY, Runnable::run, cb.mCancellationSignal, + cb); + return Collections.singletonList(cb); + } + + @Override + void onQueryDone(@NonNull String queryName, @NonNull List<InetAddress> answer, + int lastRCode, long elapsedNanos) { + final boolean success = !answer.isEmpty(); + recordProbeEventMetrics(ProbeType.PT_PRIVDNS, elapsedNanos, + success ? ProbeResult.PR_SUCCESS : + ProbeResult.PR_FAILURE, null /* capportData */); + logValidationProbe(elapsedNanos, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE); + + final String strIps = Objects.toString(answer); + validationLog(PROBE_PRIVDNS, queryName, + String.format("%dus: %s", elapsedNanos / 1000, strIps)); + + mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success); + if (success) { + transitionToPrivateDnsEvaluationSuccessState(); + } else { + mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID, + null /* redirectUrl */); + sendMessage(CMD_PRIVATE_DNS_EVALUATION_FAILED); + } + } + + @Override + long getTimeoutMs() { + return getAsyncPrivateDnsProbeTimeout(); + } + + @NonNull + @Override + String getQueryName() { + return UUID.randomUUID().toString().substring(0, 8) + PRIVATE_DNS_PROBE_HOST_SUFFIX; + } + } + + private boolean isStrictModeHostnameResolved(PrivateDnsConfig config) { + return (config != null) + && config.hostname.equals(mPrivateDnsProviderHostname) + && (config.ips.length > 0); + } + + private void notifyPrivateDnsConfigResolved(@NonNull PrivateDnsConfig config) { + try { + mCallback.notifyPrivateDnsConfigResolved(config.toParcel()); + } catch (RemoteException e) { + Log.e(TAG, "Error sending private DNS config resolved notification", e); + } + } + private class ProbingState extends State { private Thread mThread; @@ -2186,6 +2520,11 @@ public class NetworkMonitor extends StateMachine { CONFIG_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT, DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT); } + private int getAsyncPrivateDnsProbeTimeout() { + return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, + CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS, DEFAULT_PRIVDNS_PROBE_TIMEOUT_MS); + } + /** * Gets an integer setting from resources or device config * diff --git a/tests/integration/AndroidManifest_root.xml b/tests/integration/AndroidManifest_root.xml index 02b82c53..ffc859a4 100644 --- a/tests/integration/AndroidManifest_root.xml +++ b/tests/integration/AndroidManifest_root.xml @@ -16,6 +16,10 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.networkstack.roottests"> + <!-- Used by requesting a network and sending packet on that network. --> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java index 2f1f7d16..72a6b4df 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java @@ -17,8 +17,6 @@ package android.net.ip; import static android.Manifest.permission.MANAGE_TEST_NETWORKS; -import static android.Manifest.permission.READ_DEVICE_CONFIG; -import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; @@ -80,7 +78,7 @@ import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTI import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; -import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; +import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION; import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.parcelingRoundTrip; import static com.android.testutils.TestPermissionUtil.runAsShell; @@ -121,7 +119,6 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; import android.app.Instrumentation; -import android.app.UiAutomation; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.ContentResolver; @@ -181,11 +178,9 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.provider.DeviceConfig; import android.stats.connectivity.NudEventType; import android.system.ErrnoException; import android.system.Os; -import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; @@ -228,7 +223,6 @@ import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; import com.android.testutils.CompatUtil; import com.android.testutils.DevSdkIgnoreRule; -import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.HandlerUtils; import com.android.testutils.TapPacketReader; @@ -272,7 +266,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Random; @@ -313,6 +306,10 @@ public abstract class IpClientIntegrationTestCommon { protected static final long TEST_TIMEOUT_MS = 2_000L; private static final long TEST_WAIT_ENOBUFS_TIMEOUT_MS = 30_000L; private static final long TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS = 15_000L; + // To prevent the flakiness about deprecationTime and expirationTime check, +/- 2s tolerance + // should be enough between the timestamp when the IP provisioning completes successfully and + // when IpClientLinkObserver sees the RTM_NEWADDR netlink events. + private static final long TEST_LIFETIME_TOLERANCE_MS = 2_000L; @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); @@ -482,8 +479,6 @@ public abstract class IpClientIntegrationTestCommon { }; protected class Dependencies extends IpClient.Dependencies { - // Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present. - private HashMap<String, Integer> mIntConfigProperties = new HashMap<>(); private DhcpClient mDhcpClient; private Dhcp6Client mDhcp6Client; private boolean mIsHostnameConfigurationEnabled; @@ -557,12 +552,22 @@ public abstract class IpClientIntegrationTestCommon { } @Override + public int getDeviceConfigPropertyInt(String name, int ignoredDefaultValue) { + // Default is never used because all device config properties must be mocked by test. + try { + return Integer.parseInt(getDeviceConfigProperty(name)); + } catch (NumberFormatException e) { + throw new IllegalStateException("Non-mocked device config property " + name); + } + } + + @Override public Dhcp6Client.Dependencies getDhcp6ClientDependencies() { return new Dhcp6Client.Dependencies() { @Override public int getDeviceConfigPropertyInt(String name, int defaultValue) { return Dependencies.this.getDeviceConfigPropertyInt(name, - 0 /* default value */); + defaultValue); } }; } @@ -584,7 +589,7 @@ public abstract class IpClientIntegrationTestCommon { @Override public int getIntDeviceConfig(final String name, int minimumValue, int maximumValue, int defaultValue) { - return getDeviceConfigPropertyInt(name, 0 /* default value */); + return Dependencies.this.getDeviceConfigPropertyInt(name, defaultValue); } @Override @@ -634,19 +639,6 @@ public abstract class IpClientIntegrationTestCommon { } @Override - public int getDeviceConfigPropertyInt(String name, int defaultValue) { - Integer value = mIntConfigProperties.get(name); - if (value == null) { - throw new IllegalStateException("Non-mocked device config property " + name); - } - return value; - } - - public void setDeviceConfigProperty(String name, int value) { - mIntConfigProperties.put(name, value); - } - - @Override public NetworkQuirkMetrics getNetworkQuirkMetrics() { return new NetworkQuirkMetrics(mNetworkQuirkMetricsDeps); } @@ -656,7 +648,26 @@ public abstract class IpClientIntegrationTestCommon { protected abstract IIpClient makeIIpClient( @NonNull String ifaceName, @NonNull IIpClientCallbacks cb); - protected abstract void setFeatureEnabled(String name, boolean enabled); + // In production. features are enabled if the flag is lower than the package version. + // For testing, we can just use 1 for enabled and -1 for disabled or chickened out. + static final String FEATURE_ENABLED = "1"; + static final String FEATURE_DISABLED = "-1"; + + final void setFeatureEnabled(String feature, boolean enabled) { + setDeviceConfigProperty(feature, enabled ? FEATURE_ENABLED : FEATURE_DISABLED); + } + + final void setFeatureChickenedOut(String feature, boolean chickenedOut) { + setDeviceConfigProperty(feature, chickenedOut ? FEATURE_DISABLED : FEATURE_ENABLED); + } + + final void setDeviceConfigProperty(String name, int value) { + setDeviceConfigProperty(name, Integer.toString(value)); + } + + protected abstract void setDeviceConfigProperty(String name, String value); + + protected abstract String getDeviceConfigProperty(String name); protected abstract boolean isFeatureEnabled(String name); @@ -681,36 +692,8 @@ public abstract class IpClientIntegrationTestCommon { return !useNetworkStackSignature() && mIsSignatureRequiredTest; } - private ArrayMap<String, String> mOriginalPropertyValues = new ArrayMap<>(); - - protected void setDeviceConfigProperty(String name, String value) { - final UiAutomation am = InstrumentationRegistry.getInstrumentation().getUiAutomation(); - am.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG); - try { - // Do not use computeIfAbsent as it would overwrite null values, - // property originally unset. - if (!mOriginalPropertyValues.containsKey(name)) { - mOriginalPropertyValues.put(name, - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name)); - } - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, name, value, - false /* makeDefault */); - } finally { - am.dropShellPermissionIdentity(); - } - } - - protected void setDeviceConfigProperty(String name, int value) { - setDeviceConfigProperty(name, Integer.toString(value)); - } - - private void setFeatureChickenedOut(String name, boolean chickenedOut) { - setDeviceConfigProperty(name, chickenedOut ? "-1" : "0"); - } - - protected void setDhcpFeatures(final boolean isDhcpLeaseCacheEnabled, - final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled) { - setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled); + private void setDhcpFeatures(final boolean isRapidCommitEnabled, + final boolean isDhcpIpConflictDetectEnabled) { setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled); setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION, isDhcpIpConflictDetectEnabled); @@ -832,28 +815,28 @@ public abstract class IpClientIntegrationTestCommon { when(mPackageManager.getPackagesForUid(TEST_DEVICE_OWNER_APP_UID)).thenReturn( new String[] { TEST_DEVICE_OWNER_APP_PACKAGE }); - mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67); - mDependencies.setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10); - mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_PROBE_DELAY_MS, 10); - mDependencies.setDeviceConfigProperty(DhcpClient.ARP_PROBE_MIN_MS, 10); - mDependencies.setDeviceConfigProperty(DhcpClient.ARP_PROBE_MAX_MS, 20); - mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_ANNOUNCE_DELAY_MS, 10); - mDependencies.setDeviceConfigProperty(DhcpClient.ARP_ANNOUNCE_INTERVAL_MS, 10); + setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67); + setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10); + setDeviceConfigProperty(DhcpClient.ARP_FIRST_PROBE_DELAY_MS, 10); + setDeviceConfigProperty(DhcpClient.ARP_PROBE_MIN_MS, 10); + setDeviceConfigProperty(DhcpClient.ARP_PROBE_MAX_MS, 20); + setDeviceConfigProperty(DhcpClient.ARP_FIRST_ANNOUNCE_DELAY_MS, 10); + setDeviceConfigProperty(DhcpClient.ARP_ANNOUNCE_INTERVAL_MS, 10); // Set the initial netlink socket receive buffer size to a minimum of 100KB to ensure test // cases are still working, meanwhile in order to easily overflow the receive buffer by // sending as few RAs as possible for test case where it's used to verify ENOBUFS. - mDependencies.setDeviceConfigProperty(CONFIG_SOCKET_RECV_BUFSIZE, 100 * 1024); + setDeviceConfigProperty(CONFIG_SOCKET_RECV_BUFSIZE, 100 * 1024); // Set the timeout to wait IPv6 autoconf to complete. - mDependencies.setDeviceConfigProperty(CONFIG_IPV6_AUTOCONF_TIMEOUT, 500); + setDeviceConfigProperty(CONFIG_IPV6_AUTOCONF_TIMEOUT, 500); // Set the minimal RA lifetime value, any RA section with liftime below this value will be // ignored. - mDependencies.setDeviceConfigProperty(CONFIG_ACCEPT_RA_MIN_LFT, DEFAULT_ACCEPT_RA_MIN_LFT); + setDeviceConfigProperty(CONFIG_ACCEPT_RA_MIN_LFT, DEFAULT_ACCEPT_RA_MIN_LFT); // Set the polling interval to update APF data snapshot. - mDependencies.setDeviceConfigProperty(CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, + setDeviceConfigProperty(CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS); } @@ -876,22 +859,6 @@ public abstract class IpClientIntegrationTestCommon { awaitIpClientShutdown(); } - @After - public void tearDownDeviceConfigProperties() { - if (testSkipped()) return; - final UiAutomation am = InstrumentationRegistry.getInstrumentation().getUiAutomation(); - am.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG); - try { - for (String key : mOriginalPropertyValues.keySet()) { - if (key == null) continue; - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, key, - mOriginalPropertyValues.get(key), false /* makeDefault */); - } - } finally { - am.dropShellPermissionIdentity(); - } - } - private void setUpTapInterface() throws Exception { final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); final TestNetworkInterface iface = runAsShell(MANAGE_TEST_NETWORKS, () -> { @@ -1230,10 +1197,12 @@ public abstract class IpClientIntegrationTestCommon { mIIpClient.startProvisioning(cfg.toStableParcelable()); } - private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled, - final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled, - final boolean isDhcpIpConflictDetectEnabled, final String displayName, - final ScanResultInfo scanResultInfo, final Layer2Information layer2Info) + private void startIpClientProvisioning(final boolean shouldReplyRapidCommitAck, + final boolean isPreconnectionEnabled, + final boolean isDhcpIpConflictDetectEnabled, + final String displayName, + final ScanResultInfo scanResultInfo, + final Layer2Information layer2Info) throws Exception { ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() @@ -1246,8 +1215,7 @@ public abstract class IpClientIntegrationTestCommon { if (displayName != null) prov.withDisplayName(displayName); if (scanResultInfo != null) prov.withScanResultInfo(scanResultInfo); - setDhcpFeatures(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck, - isDhcpIpConflictDetectEnabled); + setDhcpFeatures(shouldReplyRapidCommitAck, isDhcpIpConflictDetectEnabled); startIpClientProvisioning(prov.build()); if (!isPreconnectionEnabled) { @@ -1256,10 +1224,10 @@ public abstract class IpClientIntegrationTestCommon { verify(mCb, never()).onProvisioningFailure(any()); } - private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled, - final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled, + private void startIpClientProvisioning(final boolean isDhcpRapidCommitEnabled, + final boolean isPreconnectionEnabled, final boolean isDhcpIpConflictDetectEnabled) throws Exception { - startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled, + startIpClientProvisioning(isDhcpRapidCommitEnabled, isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, null /* displayName */, null /* ScanResultInfo */, null /* layer2Info */); } @@ -1311,13 +1279,12 @@ public abstract class IpClientIntegrationTestCommon { // Helper method to complete DHCP 2-way or 4-way handshake private List<DhcpPacket> performDhcpHandshake(final boolean isSuccessLease, - final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled, - final boolean shouldReplyRapidCommitAck, final int mtu, + final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu, final boolean isDhcpIpConflictDetectEnabled, final String captivePortalApiUrl, final String displayName, final ScanResultInfo scanResultInfo, final Layer2Information layer2Info) throws Exception { - startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck, + startIpClientProvisioning(shouldReplyRapidCommitAck, false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled, displayName, scanResultInfo, layer2Info); return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu, @@ -1371,18 +1338,17 @@ public abstract class IpClientIntegrationTestCommon { } private List<DhcpPacket> performDhcpHandshake(final boolean isSuccessLease, - final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled, - final boolean isDhcpRapidCommitEnabled, final int mtu, + final Integer leaseTimeSec, final boolean isDhcpRapidCommitEnabled, final int mtu, final boolean isDhcpIpConflictDetectEnabled) throws Exception { - return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled, - isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled, + return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpRapidCommitEnabled, + mtu, isDhcpIpConflictDetectEnabled, null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */, null /* layer2Info */); } private List<DhcpPacket> performDhcpHandshake() throws Exception { return performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); } @@ -1430,8 +1396,8 @@ public abstract class IpClientIntegrationTestCommon { .onNetworkAttributesRetrieved(new Status(SUCCESS), TEST_L2KEY, na); return null; }).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any()); - startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */, - false /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */, + startIpClientProvisioning(false /* shouldReplyRapidCommitAck */, + false /* isPreconnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); return getNextDhcpPacket(); } @@ -1477,7 +1443,7 @@ public abstract class IpClientIntegrationTestCommon { if (shouldChangeMtu) mtu = TEST_MIN_MTU; performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, mtu, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, mtu); @@ -1529,11 +1495,8 @@ public abstract class IpClientIntegrationTestCommon { final boolean shouldFirePreconnectionTimeout, final boolean timeoutBeforePreconnectionComplete) throws Exception { final long currentTime = System.currentTimeMillis(); - final ArgumentCaptor<InterfaceConfigurationParcel> ifConfig = - ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); - - startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */, - shouldReplyRapidCommitAck, true /* isDhcpPreConnectionEnabled */, + startIpClientProvisioning(shouldReplyRapidCommitAck, + true /* isDhcpPreConnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart(); final int preconnDiscoverTransId = packet.getTransactionId(); @@ -1588,12 +1551,7 @@ public abstract class IpClientIntegrationTestCommon { } } verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true); - - final LinkAddress ipAddress = new LinkAddress(CLIENT_ADDR, PREFIX_LENGTH); - verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetCfg(ifConfig.capture()); - assertEquals(ifConfig.getValue().ifName, mIfaceName); - assertEquals(ifConfig.getValue().ipv4Addr, ipAddress.getAddress().getHostAddress()); - assertEquals(ifConfig.getValue().prefixLength, PREFIX_LENGTH); + verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); } @@ -1651,7 +1609,7 @@ public abstract class IpClientIntegrationTestCommon { final long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, shouldReplyRapidCommitAck, + shouldReplyRapidCommitAck, TEST_DEFAULT_MTU, isDhcpIpConflictDetectEnabled); // If we receive an ARP packet here, it's guaranteed to be from IP conflict detection, @@ -1712,8 +1670,8 @@ public abstract class IpClientIntegrationTestCommon { @Test public void testDhcpInit() throws Exception { - startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, - false /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */, + startIpClientProvisioning(false /* shouldReplyRapidCommitAck */, + false /* isPreconnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket packet = getNextDhcpPacket(); assertTrue(packet instanceof DhcpDiscoverPacket); @@ -1723,7 +1681,7 @@ public abstract class IpClientIntegrationTestCommon { public void testHandleSuccessDhcpLease() throws Exception { final long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); @@ -1732,7 +1690,7 @@ public abstract class IpClientIntegrationTestCommon { @Test public void testHandleFailureDhcpLease() throws Exception { performDhcpHandshake(false /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verify(mCb, never()).onProvisioningSuccess(any()); @@ -1743,7 +1701,7 @@ public abstract class IpClientIntegrationTestCommon { public void testHandleInfiniteLease() throws Exception { final long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, INFINITE_LEASE, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime, TEST_DEFAULT_MTU); @@ -1753,26 +1711,17 @@ public abstract class IpClientIntegrationTestCommon { public void testHandleNoLease() throws Exception { final long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, null /* no lease time */, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(null, currentTime, TEST_DEFAULT_MTU); } - @Test @IgnoreAfter(Build.VERSION_CODES.Q) // INIT-REBOOT is enabled on R. - public void testHandleDisableInitRebootState() throws Exception { - performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, - TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); - verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); - assertIpMemoryNeverStoreNetworkAttributes(); - } - @Test public void testHandleRapidCommitOption() throws Exception { final long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, true /* shouldReplyRapidCommitAck */, + true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); @@ -1780,8 +1729,8 @@ public abstract class IpClientIntegrationTestCommon { @Test @IgnoreUpTo(Build.VERSION_CODES.Q) public void testRollbackFromRapidCommitOption() throws Exception { - startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, - true /* isDhcpRapidCommitEnabled */, false /* isPreConnectionEnabled */, + startIpClientProvisioning(true /* isDhcpRapidCommitEnabled */, + false /* isPreConnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); final List<DhcpPacket> discoverList = new ArrayList<DhcpPacket>(); @@ -1857,8 +1806,8 @@ public abstract class IpClientIntegrationTestCommon { @Test public void testDhcpClientRapidCommitEnabled() throws Exception { - startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, - true /* shouldReplyRapidCommitAck */, false /* isPreconnectionEnabled */, + startIpClientProvisioning(true /* shouldReplyRapidCommitAck */, + false /* isPreconnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket packet = getNextDhcpPacket(); assertTrue(packet instanceof DhcpDiscoverPacket); @@ -1925,8 +1874,7 @@ public abstract class IpClientIntegrationTestCommon { final long currentTime = System.currentTimeMillis(); setFeatureEnabled(NetworkStackUtils.DHCP_SLOW_RETRANSMISSION_VERSION, true); performDhcpHandshake(true /* isSuccessLease */, - TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, - false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + TEST_LEASE_DURATION_S, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); final LinkProperties lp = verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); @@ -2030,7 +1978,7 @@ public abstract class IpClientIntegrationTestCommon { long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_MIN_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_MIN_MTU); @@ -2047,7 +1995,7 @@ public abstract class IpClientIntegrationTestCommon { currentTime = System.currentTimeMillis(); // Intend to set mtu option to 0, then verify that won't influence interface mtu restore. performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, 0 /* mtu */, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, 0 /* mtu */); @@ -2535,7 +2483,7 @@ public abstract class IpClientIntegrationTestCommon { private void doIPv4OnlyProvisioningAndExitWithLeftAddress() throws Exception { final long currentTime = System.currentTimeMillis(); performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); @@ -2583,8 +2531,8 @@ public abstract class IpClientIntegrationTestCommon { // Enter ClearingIpAddressesState to clear the remaining IPv4 addresses and transition to // PreconnectionState instead of RunningState. - startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, - false /* shouldReplyRapidCommitAck */, true /* isDhcpPreConnectionEnabled */, + startIpClientProvisioning(false /* shouldReplyRapidCommitAck */, + true /* isDhcpPreConnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); assertDiscoverPacketOnPreconnectionStart(); @@ -2710,7 +2658,7 @@ public abstract class IpClientIntegrationTestCommon { .withoutIpReachabilityMonitor() .withPreconnection() .build(); - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + setDhcpFeatures(false /* shouldReplyRapidCommitAck */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); assertDiscoverPacketOnPreconnectionStart(); @@ -2741,8 +2689,8 @@ public abstract class IpClientIntegrationTestCommon { // Start provisioning again to verify IpClient can process CMD_START correctly at // StoppedState. - startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, - false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */, + startIpClientProvisioning(false /* shouldReplyRapidCommitAck */, + false /* isPreConnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket discover = getNextDhcpPacket(); assertTrue(discover instanceof DhcpDiscoverPacket); @@ -2811,8 +2759,7 @@ public abstract class IpClientIntegrationTestCommon { final long currentTime = System.currentTimeMillis(); final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, - TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, - false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + TEST_LEASE_DURATION_S, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); assertEquals(2, sentPackets.size()); @@ -2828,8 +2775,7 @@ public abstract class IpClientIntegrationTestCommon { final long currentTime = System.currentTimeMillis(); final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, - TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, - false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + TEST_LEASE_DURATION_S, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); assertEquals(2, sentPackets.size()); @@ -2845,8 +2791,7 @@ public abstract class IpClientIntegrationTestCommon { final long currentTime = System.currentTimeMillis(); final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, - TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, - false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + TEST_LEASE_DURATION_S, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); assertEquals(2, sentPackets.size()); @@ -2858,8 +2803,8 @@ public abstract class IpClientIntegrationTestCommon { private LinkProperties runDhcpClientCaptivePortalApiTest(boolean featureEnabled, boolean serverSendsOption) throws Exception { - startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */, - false /* shouldReplyRapidCommitAck */, false /* isPreConnectionEnabled */, + startIpClientProvisioning(false /* shouldReplyRapidCommitAck */, + false /* isPreConnectionEnabled */, false /* isDhcpIpConflictDetectEnabled */); final DhcpPacket discover = getNextDhcpPacket(); assertTrue(discover instanceof DhcpDiscoverPacket); @@ -2968,8 +2913,7 @@ public abstract class IpClientIntegrationTestCommon { data); final long currentTime = System.currentTimeMillis(); final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */, - TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */, - false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, + TEST_LEASE_DURATION_S, false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */, null /* layer2Info */); @@ -3083,7 +3027,7 @@ public abstract class IpClientIntegrationTestCommon { mDependencies.setHostnameConfiguration(true /* isHostnameConfigurationEnabled */, null /* hostname */); performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, + false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */, null /* captivePortalApiUrl */, displayName, null /* scanResultInfo */, layer2Info); @@ -3236,8 +3180,7 @@ public abstract class IpClientIntegrationTestCommon { // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */); + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); // Both signature and root tests can use this function to do dual-stack provisioning. if (useNetworkStackSignature()) { mIpc.startProvisioning(config); @@ -3340,7 +3283,7 @@ public abstract class IpClientIntegrationTestCommon { final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */, + setDhcpFeatures(false /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); @@ -3404,8 +3347,7 @@ public abstract class IpClientIntegrationTestCommon { .withPreconnection() .build(); - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, - false /* isDhcpIpConflictDetectEnabled */); + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); final DhcpPacket packet = assertDiscoverPacketOnPreconnectionStart(); @@ -3460,7 +3402,7 @@ public abstract class IpClientIntegrationTestCommon { MacAddress.fromString(TEST_DEFAULT_BSSID))) .build(); - setDhcpFeatures(true /* isDhcpLeaseCacheEnabled */, false /* isRapidCommitEnabled */, + setDhcpFeatures(false /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); @@ -3586,7 +3528,7 @@ public abstract class IpClientIntegrationTestCommon { ); private DhcpPacket doCustomizedDhcpOptionsTest(final List<DhcpOption> options, - final ScanResultInfo info, boolean isDhcpLeaseCacheEnabled) throws Exception { + final ScanResultInfo info) throws Exception { ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER, @@ -3595,7 +3537,7 @@ public abstract class IpClientIntegrationTestCommon { .withDhcpOptions(options) .withoutIPv6(); - setDhcpFeatures(isDhcpLeaseCacheEnabled, false /* isRapidCommitEnabled */, + setDhcpFeatures(false /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(prov.build()); @@ -3609,8 +3551,7 @@ public abstract class IpClientIntegrationTestCommon { public void testDiscoverCustomizedDhcpOptions() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID); @@ -3621,8 +3562,7 @@ public abstract class IpClientIntegrationTestCommon { public void testDiscoverCustomizedDhcpOptions_nullDhcpOptions() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3632,7 +3572,7 @@ public abstract class IpClientIntegrationTestCommon { @Test public void testDiscoverCustomizedDhcpOptions_nullScanResultInfo() throws Exception { final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, - null /* scanResultInfo */, false /* isDhcpLeaseCacheEnabled */); + null /* scanResultInfo */); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3643,8 +3583,7 @@ public abstract class IpClientIntegrationTestCommon { public void testDiscoverCustomizedDhcpOptions_disallowedOui() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, new byte[]{ 0x00, 0x11, 0x22} /* oui */, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3655,8 +3594,7 @@ public abstract class IpClientIntegrationTestCommon { public void testDiscoverCustomizedDhcpOptions_invalidIeId() throws Exception { final ScanResultInfo info = makeScanResultInfo(0xde /* vendor-specific IE */, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3667,8 +3605,7 @@ public abstract class IpClientIntegrationTestCommon { public void testDiscoverCustomizedDhcpOptions_invalidVendorSpecificType() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, (byte) 0x10 /* vendor-specific IE type */); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3679,8 +3616,7 @@ public abstract class IpClientIntegrationTestCommon { public void testDiscoverCustomizedDhcpOptions_legacyVendorSpecificType() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, LEGACY_TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3696,8 +3632,7 @@ public abstract class IpClientIntegrationTestCommon { makeDhcpOption((byte) 26, HexDump.toByteArray(TEST_DEFAULT_MTU))); final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID); @@ -3714,8 +3649,7 @@ public abstract class IpClientIntegrationTestCommon { makeDhcpOption((byte) 42, null)); final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID); @@ -3730,8 +3664,7 @@ public abstract class IpClientIntegrationTestCommon { makeDhcpOption((byte) 77, null)); final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, - false /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info); assertTrue(packet instanceof DhcpDiscoverPacket); assertTrue(packet.hasRequestedParam((byte) 77 /* DHCP_USER_CLASS */)); @@ -3744,8 +3677,7 @@ public abstract class IpClientIntegrationTestCommon { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID); @@ -3758,8 +3690,7 @@ public abstract class IpClientIntegrationTestCommon { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(null /* options */, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3771,7 +3702,7 @@ public abstract class IpClientIntegrationTestCommon { setUpRetrievedNetworkAttributesForInitRebootState(); final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, - null /* scanResultInfo */, true /* isDhcpLeaseCacheEnabled */); + null /* scanResultInfo */); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3784,8 +3715,7 @@ public abstract class IpClientIntegrationTestCommon { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, new byte[]{ 0x00, 0x11, 0x22} /* oui */, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3798,8 +3728,7 @@ public abstract class IpClientIntegrationTestCommon { final ScanResultInfo info = makeScanResultInfo(0xde /* vendor-specific IE */, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3812,8 +3741,7 @@ public abstract class IpClientIntegrationTestCommon { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, (byte) 0x20 /* vendor-specific IE type */); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3826,8 +3754,7 @@ public abstract class IpClientIntegrationTestCommon { final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, LEGACY_TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(TEST_OEM_DHCP_OPTIONS, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, new String("android-dhcp-" + Build.VERSION.RELEASE)); @@ -3845,8 +3772,7 @@ public abstract class IpClientIntegrationTestCommon { makeDhcpOption((byte) 26, HexDump.toByteArray(TEST_DEFAULT_MTU))); final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID); @@ -3865,8 +3791,7 @@ public abstract class IpClientIntegrationTestCommon { makeDhcpOption((byte) 42, null)); final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info); assertTrue(packet instanceof DhcpRequestPacket); assertEquals(packet.mVendorId, TEST_OEM_VENDOR_ID); @@ -3883,8 +3808,7 @@ public abstract class IpClientIntegrationTestCommon { makeDhcpOption((byte) 77, null)); final ScanResultInfo info = makeScanResultInfo(TEST_VENDOR_SPECIFIC_IE_ID, TEST_OEM_OUI, TEST_VENDOR_SPECIFIC_IE_TYPE); - final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info, - true /* isDhcpLeaseCacheEnabled */); + final DhcpPacket packet = doCustomizedDhcpOptionsTest(options, info); assertTrue(packet instanceof DhcpRequestPacket); assertTrue(packet.hasRequestedParam((byte) 77 /* DHCP_USER_CLASS */)); @@ -3936,7 +3860,6 @@ public abstract class IpClientIntegrationTestCommon { setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, true /* isGratuitousNaEnabled */); - assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION)); startIpClientProvisioning(config); doIpv6OnlyProvisioning(); @@ -3966,7 +3889,7 @@ public abstract class IpClientIntegrationTestCommon { // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); // Disable gratuitious neighbor discovery feature manually, if the feature is enabled on @@ -3974,13 +3897,10 @@ public abstract class IpClientIntegrationTestCommon { // mess up the assert of received NA packets. setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, false /* isGratuitousNaEnabled */); - assumeFalse(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION)); if (isGratuitousArpNaRoamingEnabled) { setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, true); - assumeTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION)); } else { setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false); - assumeFalse(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION)); } startIpClientProvisioning(prov.build()); } @@ -4140,6 +4060,12 @@ public abstract class IpClientIntegrationTestCommon { return ns; } + // Override this function with disabled experiment flag by default, in order not to + // affect those tests which are just related to basic IpReachabilityMonitor infra. + private void prepareIpReachabilityMonitorTest() throws Exception { + prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */); + } + private void assertNotifyNeighborLost(Inet6Address targetIp, NudEventType eventType) throws Exception { // For root test suite, rely on the IIpClient aidl interface version constant defined in @@ -4172,7 +4098,8 @@ public abstract class IpClientIntegrationTestCommon { verify(mCb, never()).onReachabilityLost(any()); } - private void prepareIpReachabilityMonitorTest() throws Exception { + private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled) + throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER, @@ -4181,6 +4108,8 @@ public abstract class IpClientIntegrationTestCommon { .withDisplayName(TEST_DEFAULT_SSID) .withoutIPv4() .build(); + setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION, + isMulticastResolicitEnabled); startIpClientProvisioning(config); verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true); doIpv6OnlyProvisioning(); @@ -4194,15 +4123,11 @@ public abstract class IpClientIntegrationTestCommon { final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations(); final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource(); - int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM; - assertEquals(expectedSize, nsList.size()); - for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) { + assertEquals(expectedNudSolicitNum, nsList.size()); + for (NeighborSolicitation ns : nsList) { assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); } - for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) { - assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */); - } } @Test @@ -4237,10 +4162,43 @@ public abstract class IpClientIntegrationTestCommon { assertNeverNotifyNeighborLost(); } + private void runIpReachabilityMonitorMcastResolicitProbeFailedTest() throws Exception { + prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); + + final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations(); + final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource(); + int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM; + assertEquals(expectedSize, nsList.size()); + for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) { + assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */, + ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); + } + for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) { + assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */); + } + } + + @Test + public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception { + runIpReachabilityMonitorMcastResolicitProbeFailedTest(); + assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */, + NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL); + } + + @Test @SignatureRequiredTest(reason = "requires mock callback object") + public void testIpReachabilityMonitor_mcastResolicitProbeFailed_legacyCallback() + throws Exception { + when(mCb.getInterfaceVersion()).thenReturn(12 /* assign an older interface aidl version */); + + runIpReachabilityMonitorMcastResolicitProbeFailedTest(); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(any()); + verify(mCb, never()).onReachabilityFailure(any()); + } + @Test public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithSameLinkLayerAddress() throws Exception { - prepareIpReachabilityMonitorTest(); + prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); @@ -4257,7 +4215,7 @@ public abstract class IpClientIntegrationTestCommon { @Test public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithDiffLinkLayerAddress() throws Exception { - prepareIpReachabilityMonitorTest(); + prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); @@ -4278,9 +4236,113 @@ public abstract class IpClientIntegrationTestCommon { NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED); } - private void sendUdpPacketToNetwork(final Network network, final Inet6Address remoteIp, + private void prepareIpReachabilityMonitorIpv4AddressResolutionTest() throws Exception { + mNetworkAgentThread = + new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName()); + mNetworkAgentThread.start(); + + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv6() + .build(); + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); + startIpClientProvisioning(config); + + // Start IPv4 provisioning and wait until entire provisioning completes. + handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S, + true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */); + final LinkProperties lp = + verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); + + runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp)); + + // Send a UDP packet to IPv4 DNS server to trigger address resolution process for IPv4 + // on-link DNS server or default router. + final Random random = new Random(); + final byte[] data = new byte[100]; + random.nextBytes(data); + sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), SERVER_ADDR, 1234 /* port */, data); + } + + private void doTestIpReachabilityMonitor_replyBroadcastArpRequestWithDiffMacAddresses( + boolean disconnect) throws Exception { + prepareIpReachabilityMonitorIpv4AddressResolutionTest(); + + // Respond to the broadcast ARP request. + final ArpPacket request = getNextArpPacket(); + assertArpRequest(request, SERVER_ADDR); + sendArpReply(request.senderHwAddress.toByteArray() /* dst */, ROUTER_MAC_BYTES /* srcMac */, + request.senderIp /* target IP */, SERVER_ADDR /* sender IP */); + + Thread.sleep(1500); + + // Reply with a different MAC address but the same server IP. + final MacAddress gateway = MacAddress.fromString("00:11:22:33:44:55"); + sendArpReply(request.senderHwAddress.toByteArray() /* dst */, + gateway.toByteArray() /* srcMac */, + request.senderIp /* target IP */, SERVER_ADDR /* sender IP */); + + if (disconnect) { + final ArgumentCaptor<ReachabilityLossInfoParcelable> lossInfoCaptor = + ArgumentCaptor.forClass(ReachabilityLossInfoParcelable.class); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityFailure(lossInfoCaptor.capture()); + assertEquals(ReachabilityLossReason.ORGANIC, lossInfoCaptor.getValue().reason); + } else { + verify(mCb, after(100).never()).onReachabilityFailure(any()); + } + } + + @Test + public void testIpReachabilityMonitor_macAddressChangedWithoutRoam_ok() + throws Exception { + setFeatureChickenedOut(IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION, + false); + doTestIpReachabilityMonitor_replyBroadcastArpRequestWithDiffMacAddresses(false); + } + + @Test + public void testIpReachabilityMonitor_macAddressChangedWithoutRoam_disconnect() + throws Exception { + setFeatureChickenedOut(IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION, + true); + doTestIpReachabilityMonitor_replyBroadcastArpRequestWithDiffMacAddresses(true); + } + + @Test + public void testIpReachabilityMonitor_ignoreIpv4DefaultRouterOrganicNudFailure() + throws Exception { + setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, + true /* ignoreOrganicNudFailure */); + prepareIpReachabilityMonitorIpv4AddressResolutionTest(); + + ArpPacket packet; + while ((packet = getNextArpPacket(TEST_TIMEOUT_MS)) != null) { + // wait address resolution to complete. + } + verify(mCb, never()).onReachabilityFailure(any()); + } + + @Test + public void testIpReachabilityMonitor_ignoreIpv4DefaultRouterOrganicNudFailure_flagoff() + throws Exception { + setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, + false /* ignoreOrganicNudFailure */); + prepareIpReachabilityMonitorIpv4AddressResolutionTest(); + + ArpPacket packet; + while ((packet = getNextArpPacket(TEST_TIMEOUT_MS)) != null) { + // wait address resolution to complete. + } + final ArgumentCaptor<ReachabilityLossInfoParcelable> lossInfoCaptor = + ArgumentCaptor.forClass(ReachabilityLossInfoParcelable.class); + verify(mCb).onReachabilityFailure(lossInfoCaptor.capture()); + assertEquals(ReachabilityLossReason.ORGANIC, lossInfoCaptor.getValue().reason); + } + + private void sendUdpPacketToNetwork(final Network network, final InetAddress remoteIp, int port, final byte[] data) throws Exception { - final DatagramSocket socket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY); + final InetAddress laddr = + (remoteIp instanceof Inet6Address) ? Inet6Address.ANY : Inet4Address.ANY; + final DatagramSocket socket = new DatagramSocket(0, laddr); final DatagramPacket pkt = new DatagramPacket(data, data.length, remoteIp, port); network.bindSocket(socket); socket.send(pkt); @@ -4290,12 +4352,13 @@ public abstract class IpClientIntegrationTestCommon { final Inet6Address targetIp, final boolean isIgnoreIncompleteIpv6DnsServerEnabled, final boolean isIgnoreIncompleteIpv6DefaultRouterEnabled, + final boolean isIgnoreOrganicNudFailureEnabled, final boolean expectNeighborLost) throws Exception { mNetworkAgentThread = new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName()); mNetworkAgentThread.start(); - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); setFeatureEnabled( NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, @@ -4303,6 +4366,9 @@ public abstract class IpClientIntegrationTestCommon { setFeatureEnabled( NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, isIgnoreIncompleteIpv6DefaultRouterEnabled); + setFeatureEnabled( + NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, + isIgnoreOrganicNudFailureEnabled); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .build(); startIpClientProvisioning(config); @@ -4354,18 +4420,17 @@ public abstract class IpClientIntegrationTestCommon { } @Test - @SignatureRequiredTest(reason = "Need to mock NetworkAgent") public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack() throws Exception { final Inet6Address targetIp = (Inet6Address) InetAddresses.parseNumericAddress(IPV6_ON_LINK_DNS_SERVER); runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp, true /* isIgnoreIncompleteIpv6DnsServerEnabled */, false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + false /* isIgnoreOrganicNudFailureEnabled */, false /* expectNeighborLost */); } @Test - @SignatureRequiredTest(reason = "Need to mock NetworkAgent") public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack_flagoff() throws Exception { final Inet6Address targetIp = @@ -4373,28 +4438,75 @@ public abstract class IpClientIntegrationTestCommon { runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp, false /* isIgnoreIncompleteIpv6DnsServerEnabled */, false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + false /* isIgnoreOrganicNudFailureEnabled */, true /* expectNeighborLost */); } @Test - @SignatureRequiredTest(reason = "Need to mock the NetworkAgent") public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack() throws Exception { runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER, ROUTER_LINK_LOCAL /* targetIp */, false /* isIgnoreIncompleteIpv6DnsServerEnabled */, true /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + false /* isIgnoreOrganicNudFailureEnabled */, false /* expectNeighborLost */); } @Test - @SignatureRequiredTest(reason = "Need to mock the NetworkAgent") public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack_flagoff() throws Exception { runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER, ROUTER_LINK_LOCAL /* targetIp */, false /* isIgnoreIncompleteIpv6DnsServerEnabled */, false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + false /* isIgnoreOrganicNudFailureEnabled */, + true /* expectNeighborLost */); + } + + @Test + public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure() + throws Exception { + final Inet6Address targetIp = + (Inet6Address) InetAddresses.parseNumericAddress(IPV6_ON_LINK_DNS_SERVER); + runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp, + false /* isIgnoreIncompleteIpv6DnsServerEnabled */, + false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + true /* isIgnoreOrganicNudFailureEnabled */, + false /* expectNeighborLost */); + } + + @Test + public void testIpReachabilityMonitor_ignoreOnLinkIpv6DnsOrganicNudFailure_flagoff() + throws Exception { + final Inet6Address targetIp = + (Inet6Address) InetAddresses.parseNumericAddress(IPV6_ON_LINK_DNS_SERVER); + runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp, + false /* isIgnoreIncompleteIpv6DnsServerEnabled */, + false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + false /* isIgnoreOrganicNudFailureEnabled */, + true /* expectNeighborLost */); + } + + @Test + public void testIpReachabilityMonitor_ignoreIpv6DefaultRouterOrganicNudFailure() + throws Exception { + runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER, + ROUTER_LINK_LOCAL /* targetIp */, + false /* isIgnoreIncompleteIpv6DnsServerEnabled */, + false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + true /* isIgnoreOrganicNudFailureEnabled */, + false /* expectNeighborLost */); + } + + @Test + public void testIpReachabilityMonitor_ignoreIpv6DefaultRouterOrganicNudFailure_flagoff() + throws Exception { + runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER, + ROUTER_LINK_LOCAL /* targetIp */, + false /* isIgnoreIncompleteIpv6DnsServerEnabled */, + false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */, + false /* isIgnoreOrganicNudFailureEnabled */, true /* expectNeighborLost */); } @@ -4471,7 +4583,7 @@ public abstract class IpClientIntegrationTestCommon { reset(mCb); // Speed up provisioning by enabling rapid commit. TODO: why is this necessary? - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); config = new ProvisioningConfiguration.Builder() .build(); @@ -4739,7 +4851,7 @@ public abstract class IpClientIntegrationTestCommon { @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) public void testMaxDtimMultiplier_IPv4OnlyNetwork() throws Exception { performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S, - true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setMaxDtimMultiplier( @@ -4813,7 +4925,7 @@ public abstract class IpClientIntegrationTestCommon { private IaPrefixOption buildIaPrefixOption(final IpPrefix prefix, int preferred, int valid) { return new IaPrefixOption((short) IaPrefixOption.LENGTH, preferred, valid, - (byte) RFC7421_PREFIX_LENGTH, prefix.getRawAddress() /* prefix */); + (byte) prefix.getPrefixLength(), prefix.getRawAddress() /* prefix */); } private void handleDhcp6Packets(final IpPrefix prefix, boolean shouldReplyRapidCommit) @@ -4874,7 +4986,25 @@ public abstract class IpClientIntegrationTestCommon { handleDhcp6Packets(prefix, true /* shouldReplyRapidCommit */); final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); - assertTrue(hasIpv6AddressPrefixedWith(captor.getValue(), prefix)); + final LinkProperties lp = captor.getValue(); + assertTrue(hasIpv6AddressPrefixedWith(lp, prefix)); + + // Only run the test when the flag of parsing netlink events is enabled, where the + // deprecationTime and expirationTime is set. + if (mIsNetlinkEventParseEnabled) { + final long now = SystemClock.elapsedRealtime(); + long when = 0; + for (LinkAddress la : lp.getLinkAddresses()) { + if (la.getAddress().isLinkLocalAddress()) { + assertLinkAddressPermanentLifetime(la); + } else if (la.isGlobalPreferred()) { + when = now + 4500 * 1000; // preferred=4500s + assertLinkAddressDeprecationTime(la, when); + when = now + 7200 * 1000; // valid=7200s + assertLinkAddressExpirationTime(la, when); + } + } + } } @Test @@ -4955,7 +5085,7 @@ public abstract class IpClientIntegrationTestCommon { ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .build(); - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); @@ -5013,7 +5143,7 @@ public abstract class IpClientIntegrationTestCommon { ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .build(); - setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, + setDhcpFeatures(true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */); startIpClientProvisioning(config); @@ -5571,6 +5701,7 @@ public abstract class IpClientIntegrationTestCommon { mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "accept_ra_defrtr")); assertEquals(1, acceptRaDefRtr); } + private void runDhcpDomainSearchListOptionTest(final String domainName, final List<String> domainSearchList, final String expectedDomain) throws Exception { when(mResources.getBoolean(R.bool.config_dhcp_client_domain_search_list)).thenReturn(true); @@ -5618,4 +5749,104 @@ public abstract class IpClientIntegrationTestCommon { runDhcpDomainSearchListOptionTest(null /* domainName */, searchList, expectedDomain); } + + private void assertLinkAddressDeprecationTime(final LinkAddress la, final long when) { + assertTrue(la.getDeprecationTime() != LinkAddress.LIFETIME_UNKNOWN); + // Allow +/- 2 seconds to prevent flaky tests + assertTrue(la.getDeprecationTime() < when + TEST_LIFETIME_TOLERANCE_MS); + assertTrue(la.getDeprecationTime() > when - TEST_LIFETIME_TOLERANCE_MS); + } + + private void assertLinkAddressExpirationTime(final LinkAddress la, final long when) { + assertTrue(la.getExpirationTime() != LinkAddress.LIFETIME_UNKNOWN); + // Allow +/- 2 seconds to prevent flaky tests + assertTrue(la.getExpirationTime() < when + TEST_LIFETIME_TOLERANCE_MS); + assertTrue(la.getExpirationTime() > when - TEST_LIFETIME_TOLERANCE_MS); + } + + private void assertLinkAddressPermanentLifetime(final LinkAddress la) { + assertEquals(LinkAddress.LIFETIME_PERMANENT, la.getDeprecationTime()); + assertEquals(LinkAddress.LIFETIME_PERMANENT, la.getExpirationTime()); + } + + @Test + public void testPopulateLinkAddressLifetime() throws Exception { + // Only run the test when the flag of parsing netlink events is enabled to verify the + // code of setting deprecationTime/expirationTime added when IpClientLinkObserver sees + // the RTM_NEWADDR, and we are going to delete the dead old code path completely soon. + assumeTrue(mIsNetlinkEventParseEnabled); + + final LinkProperties lp = doDualStackProvisioning(); + final long now = SystemClock.elapsedRealtime(); + long when = 0; + for (LinkAddress la : lp.getLinkAddresses()) { + if (la.isIpv4()) { + when = now + 3600 * 1000; // DHCP lease duration + assertLinkAddressDeprecationTime(la, when); + assertLinkAddressExpirationTime(la, when); + } else if (la.isIpv6() && la.getAddress().isLinkLocalAddress()) { + assertLinkAddressPermanentLifetime(la); + } else if (la.isIpv6() && la.isGlobalPreferred()) { + when = now + 1800 * 1000; // preferred=1800s + assertLinkAddressDeprecationTime(la, when); + when = now + 3600 * 1000; // valid=3600s + assertLinkAddressExpirationTime(la, when); + } + } + } + + @Test + public void testPopulateLinkAddressLifetime_infiniteLeaseDuration() throws Exception { + // Only run the test when the flag of parsing netlink events is enabled. + assumeTrue(mIsNetlinkEventParseEnabled); + + final ProvisioningConfiguration cfg = new ProvisioningConfiguration.Builder() + .withoutIPv6() + .build(); + + startIpClientProvisioning(cfg); + handleDhcpPackets(true /* isSuccessLease */, DhcpPacket.INFINITE_LEASE, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, + null /* captivePortalApiUrl */, null /* ipv6OnlyWaitTime */, + null /* domainName */, null /* domainSearchList */); + + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); + final LinkProperties lp = captor.getValue(); + assertNotNull(lp); + for (LinkAddress la : lp.getLinkAddresses()) { + if (la.isIpv4()) { + assertLinkAddressPermanentLifetime(la); + } + } + } + + @Test + public void testPopulateLinkAddressLifetime_minimalLeaseDuration() throws Exception { + // Only run the test when the flag of parsing netlink events is enabled. + assumeTrue(mIsNetlinkEventParseEnabled); + + final ProvisioningConfiguration cfg = new ProvisioningConfiguration.Builder() + .withoutIPv6() + .build(); + + startIpClientProvisioning(cfg); + handleDhcpPackets(true /* isSuccessLease */, 59 /* lease duration */, + false /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, + null /* captivePortalApiUrl */, null /* ipv6OnlyWaitTime */, + null /* domainName */, null /* domainSearchList */); + + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); + final LinkProperties lp = captor.getValue(); + assertNotNull(lp); + for (LinkAddress la : lp.getLinkAddresses()) { + if (la.isIpv4()) { + final long now = SystemClock.elapsedRealtime(); + final long when = now + 60 * 1000; // minimal lease duration + assertLinkAddressDeprecationTime(la, when); + assertLinkAddressExpirationTime(la, when); + } + } + } } diff --git a/tests/integration/root/android/net/ip/IpClientRootTest.kt b/tests/integration/root/android/net/ip/IpClientRootTest.kt index 77d327f9..715e27d5 100644 --- a/tests/integration/root/android/net/ip/IpClientRootTest.kt +++ b/tests/integration/root/android/net/ip/IpClientRootTest.kt @@ -16,7 +16,7 @@ package android.net.ip -import android.Manifest +import android.Manifest.permission.NETWORK_SETTINGS import android.Manifest.permission.READ_DEVICE_CONFIG import android.Manifest.permission.WRITE_DEVICE_CONFIG import android.net.IIpMemoryStore @@ -27,10 +27,13 @@ import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener import android.net.ipmemorystore.Status import android.net.networkstack.TestNetworkStackServiceClient import android.os.Process +import android.provider.DeviceConfig +import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY +import android.util.ArrayMap import android.util.Log import androidx.test.platform.app.InstrumentationRegistry -import com.android.net.module.util.DeviceConfigUtils import java.lang.System.currentTimeMillis +import java.lang.UnsupportedOperationException import java.util.concurrent.CompletableFuture import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -80,7 +83,7 @@ class IpClientRootTest : IpClientIntegrationTestCommon() { // Connect to the NetworkStack only once, as it is relatively expensive (~50ms plus any // polling time waiting for the test UID to be allowed), and there should be minimal // side-effects between tests compared to reconnecting every time. - automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS) + automation.adoptShellPermissionIdentity(NETWORK_SETTINGS) try { automation.executeShellCommand("su root service call network_stack " + "$ALLOW_TEST_UID_INDEX i32 " + Process.myUid()) @@ -122,7 +125,7 @@ class IpClientRootTest : IpClientIntegrationTestCommon() { @JvmStatic @AfterClass fun tearDownClass() { nsClient.disconnect() - automation.adoptShellPermissionIdentity(Manifest.permission.NETWORK_SETTINGS) + automation.adoptShellPermissionIdentity(NETWORK_SETTINGS) try { // Reset the test UID as -1. // This may not be called if the test process is terminated before completing, @@ -176,27 +179,47 @@ class IpClientRootTest : IpClientIntegrationTestCommon() { return ipClientCaptor.value } - override fun setFeatureEnabled(feature: String, enabled: Boolean) { - // The feature is enabled if the flag is lower than the package version. - // Package versions follow a standard format with 9 digits. - // TODO: consider resetting flag values on reboot when set to special values like "1" or - // "999999999" - setDeviceConfigProperty(feature, if (enabled) "1" else "999999999") - } + // These are not needed in IpClientRootTest because there is no dependency injection and + // IpClient always uses the production implementations. + override fun getDeviceConfigProperty(name: String) = throw UnsupportedOperationException() + override fun isFeatureEnabled(name: String) = throw UnsupportedOperationException() + override fun isFeatureNotChickenedOut(name: String) = throw UnsupportedOperationException() + + private val mOriginalPropertyValues = ArrayMap<String, String>() - override fun isFeatureEnabled(name: String): Boolean { + override fun setDeviceConfigProperty(name: String?, value: String?) { automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) try { - return DeviceConfigUtils.isNetworkStackFeatureEnabled(mContext, name) + // Do not use computeIfAbsent as it would overwrite null values, + // property originally unset. + if (!mOriginalPropertyValues.containsKey(name)) { + mOriginalPropertyValues[name] = DeviceConfig.getProperty( + DeviceConfig.NAMESPACE_CONNECTIVITY, + (name)!! + ) + } + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_CONNECTIVITY, + (name)!!, value, + false /* makeDefault */ + ) } finally { automation.dropShellPermissionIdentity() } } - override fun isFeatureNotChickenedOut(name: String): Boolean { + @After + fun tearDownDeviceConfigProperties() { + if (testSkipped()) return automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) try { - return DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(mContext, name) + for (key in mOriginalPropertyValues.keys) { + if (key == null) continue + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_CONNECTIVITY, key, + mOriginalPropertyValues[key], false /* makeDefault */ + ) + } } finally { automation.dropShellPermissionIdentity() } diff --git a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt index b10e6e14..2e52e3f1 100644 --- a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt +++ b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt @@ -67,10 +67,8 @@ private const val TEST_TAG = 0xF00D @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) class NetworkStatsIntegrationTest { private val TAG = NetworkStatsIntegrationTest::class.java.simpleName - private val INTERNAL_V6ADDR = + private val LOCAL_V6ADDR = LinkAddress(InetAddresses.parseNumericAddress("2001:db8::1234"), 64) - private val EXTERNAL_V6ADDR = - LinkAddress(InetAddresses.parseNumericAddress("2001:db8::5678"), 64) // Remote address, both the client and server will have a hallucination that // they are talking to this address. @@ -101,13 +99,13 @@ class NetworkStatsIntegrationTest { private val inst = InstrumentationRegistry.getInstrumentation() private val context = inst.getContext() private val packetBridge = runAsShell(MANAGE_TEST_NETWORKS) { - PacketBridge(context, INTERNAL_V6ADDR, EXTERNAL_V6ADDR, REMOTE_V6ADDR.address) + PacketBridge(context, listOf(LOCAL_V6ADDR), REMOTE_V6ADDR.address) } private val cm = context.getSystemService(ConnectivityManager::class.java)!! // Set up DNS server for testing server and DNS64. private val fakeDns = TestDnsServer( - packetBridge.externalNetwork, InetSocketAddress(EXTERNAL_V6ADDR.address, DNS_SERVER_PORT) + packetBridge.externalNetwork, InetSocketAddress(LOCAL_V6ADDR.address, DNS_SERVER_PORT) ).apply { start() setAnswer( @@ -118,7 +116,7 @@ class NetworkStatsIntegrationTest { } // Start up test http server. - private val httpServer = TestHttpServer(EXTERNAL_V6ADDR.address.hostAddress).apply { + private val httpServer = TestHttpServer(LOCAL_V6ADDR.address.hostAddress).apply { start() } diff --git a/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt b/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt index 2f91f4fd..f4010730 100644 --- a/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt +++ b/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt @@ -33,10 +33,14 @@ import org.mockito.Mockito.verify * Tests for IpClient, run with signature permissions. */ class IpClientSignatureTest : IpClientIntegrationTestCommon() { + companion object { + private val TAG = IpClientSignatureTest::class.java.simpleName + } + private val DEFAULT_NUD_SOLICIT_NUM_POST_ROAM = 5 private val DEFAULT_NUD_SOLICIT_NUM_STEADY_STATE = 10 - private val mEnabledFeatures = ArrayMap<String, Boolean>() + private val mDeviceConfigProperties = ArrayMap<String, String>() override fun makeIIpClient(ifaceName: String, cb: IIpClientCallbacks): IIpClient { return mIpc.makeConnector() @@ -45,21 +49,20 @@ class IpClientSignatureTest : IpClientIntegrationTestCommon() { override fun useNetworkStackSignature() = true override fun isFeatureEnabled(name: String): Boolean { - return mEnabledFeatures.get(name) ?: false + return FEATURE_ENABLED.equals(getDeviceConfigProperty(name)) } override fun isFeatureNotChickenedOut(name: String): Boolean { - return mEnabledFeatures.get(name) ?: true + return !FEATURE_DISABLED.equals(getDeviceConfigProperty(name)) } - override fun setFeatureEnabled(name: String, enabled: Boolean) { - mEnabledFeatures.put(name, enabled) + override fun setDeviceConfigProperty(name: String, value: String) { + mDeviceConfigProperties.put(name, value) } - override fun setDeviceConfigProperty(name: String, value: Int) { - mDependencies.setDeviceConfigProperty(name, value) + override fun getDeviceConfigProperty(name: String): String? { + return mDeviceConfigProperties.get(name) } - override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes { val networkAttributesCaptor = ArgumentCaptor.forClass(NetworkAttributes::class.java) diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt index 3f01beaa..f5c06a18 100644 --- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt +++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt @@ -30,7 +30,6 @@ import android.system.Os import android.system.OsConstants import android.system.OsConstants.AF_INET import android.system.OsConstants.AF_PACKET -import android.system.OsConstants.ARPHRD_ETHER import android.system.OsConstants.ETH_P_IPV6 import android.system.OsConstants.IPPROTO_UDP import android.system.OsConstants.SOCK_CLOEXEC @@ -264,14 +263,15 @@ class NetworkStackUtilsIntegrationTest { // Don't accept the prefix length larger than 64. assertNull(NetworkStackUtils.createInet6AddressFromEui64(prefix, eui64)) + // prefix length equals to or less than 64 is acceptable. prefix = IpPrefix("2001:db8:1::/48") - // Don't accept the prefix length less than 64. - assertNull(NetworkStackUtils.createInet6AddressFromEui64(prefix, eui64)) - - prefix = IpPrefix("2001:db8:1::/64") // IPv6 address string is formed by combining the IPv6 prefix("2001:db8:1::") and // EUI64 converted from TEST_SRC_MAC, see above test for the output EUI64 example. - val expected = parseNumericAddress("2001:db8:1::b898:76ff:fe54:3210") as Inet6Address + var expected = parseNumericAddress("2001:db8:1::b898:76ff:fe54:3210") as Inet6Address + assertEquals(expected, NetworkStackUtils.createInet6AddressFromEui64(prefix, eui64)) + + prefix = IpPrefix("2001:db8:1:2::/64") + expected = parseNumericAddress("2001:db8:1:2:b898:76ff:fe54:3210") as Inet6Address assertEquals(expected, NetworkStackUtils.createInet6AddressFromEui64(prefix, eui64)) } diff --git a/tests/unit/jni/apf_jni.cpp b/tests/unit/jni/apf_jni.cpp index 8e14b3a4..39dd2c29 100644 --- a/tests/unit/jni/apf_jni.cpp +++ b/tests/unit/jni/apf_jni.cpp @@ -245,8 +245,6 @@ static jboolean com_android_server_ApfTest_dropsAllPackets( return true; } -static char output_buffer[512]; - static jobjectArray com_android_server_ApfTest_disassembleApf( JNIEnv* env, jclass, jbyteArray jprogram) { uint32_t program_len = env->GetArrayLength(jprogram); @@ -256,9 +254,7 @@ static jobjectArray com_android_server_ApfTest_disassembleApf( reinterpret_cast<jbyte*>(buf.data())); std::vector<std::string> disassemble_output; for (uint32_t pc = 0; pc < program_len;) { - pc = apf_disassemble(buf.data(), program_len, pc, output_buffer, - sizeof(output_buffer) / sizeof(output_buffer[0])); - disassemble_output.emplace_back(output_buffer); + disassemble_output.emplace_back(apf_disassemble(buf.data(), program_len, &pc)); } jclass stringClass = env->FindClass("java/lang/String"); jobjectArray disassembleOutput = @@ -281,14 +277,14 @@ jbyteArray com_android_server_ApfTest_getTransmittedPacket(JNIEnv* env, if (apf_test_tx_packet_len == 0) { return jdata; } env->SetByteArrayRegion(jdata, 0, (jint) apf_test_tx_packet_len, - reinterpret_cast<jbyte*>(apf_test_tx_packet)); + reinterpret_cast<jbyte*>(apf_test_buffer)); return jdata; } void com_android_server_ApfTest_resetTransmittedPacketMemory(JNIEnv, jclass) { apf_test_tx_packet_len = 0; - memset(apf_test_tx_packet, 0, APF_TX_BUFFER_SIZE); + memset(apf_test_buffer, 0xff, sizeof(apf_test_buffer)); } extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 4e1187b1..5c6a906d 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java @@ -16,9 +16,11 @@ package android.net.apf; -import static android.net.apf.ApfGenerator.APF_VERSION_4; -import static android.net.apf.ApfGenerator.Register.R0; -import static android.net.apf.ApfGenerator.Register.R1; +import static android.net.apf.ApfV4Generator.APF_VERSION_4; +import static android.net.apf.ApfV4Generator.DROP_LABEL; +import static android.net.apf.ApfV4Generator.PASS_LABEL; +import static android.net.apf.ApfV4Generator.Register.R0; +import static android.net.apf.ApfV4Generator.Register.R1; import static android.net.apf.ApfJniUtils.compareBpfApf; import static android.net.apf.ApfJniUtils.compileToBpf; import static android.net.apf.ApfJniUtils.dropsAllPackets; @@ -37,6 +39,8 @@ 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.HexDump.hexStringToByteArray; +import static com.android.net.module.util.HexDump.toHexString; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static org.junit.Assert.assertEquals; @@ -63,10 +67,10 @@ import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCounterTracker.Counter; import android.net.apf.ApfFilter.ApfConfiguration; -import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfTestUtils.MockIpClientCallback; import android.net.apf.ApfTestUtils.TestApfFilter; import android.net.apf.ApfTestUtils.TestLegacyApfFilter; +import android.net.apf.ApfV4Generator.IllegalInstructionException; import android.net.metrics.IpConnectivityLog; import android.os.Build; import android.os.PowerManager; @@ -197,11 +201,11 @@ public class ApfTest { return config; } - private void assertPass(ApfGenerator gen) throws ApfGenerator.IllegalInstructionException { + private void assertPass(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { ApfTestUtils.assertPass(mApfVersion, gen); } - private void assertDrop(ApfGenerator gen) throws ApfGenerator.IllegalInstructionException { + private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException { ApfTestUtils.assertDrop(mApfVersion, gen); } @@ -221,13 +225,13 @@ public class ApfTest { ApfTestUtils.assertDrop(mApfVersion, program, packet, filterAge); } - private void assertPass(ApfGenerator gen, byte[] packet, int filterAge) - throws ApfGenerator.IllegalInstructionException { + private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge) + throws ApfV4Generator.IllegalInstructionException { ApfTestUtils.assertPass(mApfVersion, gen, packet, filterAge); } - private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge) - throws ApfGenerator.IllegalInstructionException { + private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge) + throws ApfV4Generator.IllegalInstructionException { ApfTestUtils.assertDrop(mApfVersion, gen, packet, filterAge); } @@ -256,20 +260,20 @@ public class ApfTest { // Empty program should pass because having the program counter reach the // location immediately after the program indicates the packet should be // passed to the AP. - ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION); + ApfV4Generator gen = new ApfV4Generator(MIN_APF_VERSION); assertPass(gen); // Test jumping to pass label. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJump(gen.PASS_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJump(PASS_LABEL); byte[] program = gen.generate(); assertEquals(1, program.length); assertEquals((14 << 3) | (0 << 1) | 0, program[0]); assertPass(program, new byte[MIN_PKT_SIZE], 0); // Test jumping to drop label. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJump(gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJump(DROP_LABEL); program = gen.generate(); assertEquals(2, program.length); assertEquals((14 << 3) | (1 << 1) | 0, program[0]); @@ -277,362 +281,362 @@ public class ApfTest { assertDrop(program, new byte[15], 15); // Test jumping if equal to 0. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0Equals(0, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test jumping if not equal to 0. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0NotEquals(0, DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL); + gen.addJumpIfR0NotEquals(0, DROP_LABEL); assertDrop(gen); // Test jumping if registers equal. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0EqualsR1(gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0EqualsR1(DROP_LABEL); assertDrop(gen); // Test jumping if registers not equal. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0NotEqualsR1(DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL); + gen.addJumpIfR0NotEqualsR1(DROP_LABEL); assertDrop(gen); // Test load immediate. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test add. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addAdd(1234567890); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test add with a small signed negative value. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addAdd(-1); - gen.addJumpIfR0Equals(-1, gen.DROP_LABEL); + gen.addJumpIfR0Equals(-1, DROP_LABEL); assertDrop(gen); // Test subtract. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addAdd(-1234567890); - gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test or. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addOr(1234567890); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test and. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addAnd(123456789); - gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 & 123456789, DROP_LABEL); assertDrop(gen); // Test left shift. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addLeftShift(1); - gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 << 1, DROP_LABEL); assertDrop(gen); // Test right shift. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addRightShift(1); - gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 >> 1, DROP_LABEL); assertDrop(gen); // Test multiply. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 123456789); gen.addMul(2); - gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL); + gen.addJumpIfR0Equals(123456789 * 2, DROP_LABEL); assertDrop(gen); // Test divide. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addDiv(2); - gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 / 2, DROP_LABEL); assertDrop(gen); // Test divide by zero. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addDiv(0); - gen.addJump(gen.DROP_LABEL); + gen.addJump(DROP_LABEL); assertPass(gen); // Test add. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1234567890); gen.addAddR1(); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test subtract. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, -1234567890); gen.addAddR1(); - gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test or. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1234567890); gen.addOrR1(); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test and. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 123456789); gen.addAndR1(); - gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 & 123456789, DROP_LABEL); assertDrop(gen); // Test left shift. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 1); gen.addLeftShiftR1(); - gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 << 1, DROP_LABEL); assertDrop(gen); // Test right shift. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, -1); gen.addLeftShiftR1(); - gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 >> 1, DROP_LABEL); assertDrop(gen); // Test multiply. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 123456789); gen.addLoadImmediate(R1, 2); gen.addMulR1(); - gen.addJumpIfR0Equals(123456789 * 2, gen.DROP_LABEL); + gen.addJumpIfR0Equals(123456789 * 2, DROP_LABEL); assertDrop(gen); // Test divide. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addLoadImmediate(R1, 2); gen.addDivR1(); - gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890 / 2, DROP_LABEL); assertDrop(gen); // Test divide by zero. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addDivR1(); - gen.addJump(gen.DROP_LABEL); + gen.addJump(DROP_LABEL); assertPass(gen); // Test byte load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoad8(R0, 1); - gen.addJumpIfR0Equals(45, gen.DROP_LABEL); + gen.addJumpIfR0Equals(45, DROP_LABEL); assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test out of bounds load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoad8(R0, 16); - gen.addJumpIfR0Equals(0, gen.DROP_LABEL); + gen.addJumpIfR0Equals(0, DROP_LABEL); assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test half-word load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoad16(R0, 1); - gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL); + gen.addJumpIfR0Equals((45 << 8) | 67, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test word load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoad32(R0, 1); - gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL); + gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0); // Test byte indexed load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1); gen.addLoad8Indexed(R0, 0); - gen.addJumpIfR0Equals(45, gen.DROP_LABEL); + gen.addJumpIfR0Equals(45, DROP_LABEL); assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test out of bounds indexed load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 8); gen.addLoad8Indexed(R0, 8); - gen.addJumpIfR0Equals(0, gen.DROP_LABEL); + gen.addJumpIfR0Equals(0, DROP_LABEL); assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test half-word indexed load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1); gen.addLoad16Indexed(R0, 0); - gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL); + gen.addJumpIfR0Equals((45 << 8) | 67, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0); // Test word indexed load. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1); gen.addLoad32Indexed(R0, 0); - gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL); + gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, DROP_LABEL); assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0); // Test jumping if greater than. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0GreaterThan(0, DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL); + gen.addJumpIfR0GreaterThan(0, DROP_LABEL); assertDrop(gen); // Test jumping if less than. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0LessThan(0, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0LessThan(0, DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0LessThan(1, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0LessThan(1, DROP_LABEL); assertDrop(gen); // Test jumping if any bits set. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL); + gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertDrop(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 3); - gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL); + gen.addJumpIfR0AnyBitsSet(3, DROP_LABEL); assertDrop(gen); // Test jumping if register greater than. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0GreaterThanR1(DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 2); gen.addLoadImmediate(R1, 1); - gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL); + gen.addJumpIfR0GreaterThanR1(DROP_LABEL); assertDrop(gen); // Test jumping if register less than. - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfR0LessThanR1(gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfR0LessThanR1(DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1); - gen.addJumpIfR0LessThanR1(gen.DROP_LABEL); + gen.addJumpIfR0LessThanR1(DROP_LABEL); assertDrop(gen); // Test jumping if any bits set in register. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 3); - gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL); + gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertPass(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 3); gen.addLoadImmediate(R0, 1); - gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL); + gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertDrop(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 3); gen.addLoadImmediate(R0, 3); - gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL); + gen.addJumpIfR0AnyBitsSetR1(DROP_LABEL); assertDrop(gen); // Test load from memory. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadFromMemory(R0, 0); - gen.addJumpIfR0Equals(0, gen.DROP_LABEL); + gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test store to memory. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1234567890); gen.addStoreToMemory(R1, 12); gen.addLoadFromMemory(R0, 12); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test filter age pre-filled memory. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadFromMemory(R0, gen.FILTER_AGE_MEMORY_SLOT); - gen.addJumpIfR0Equals(123, gen.DROP_LABEL); + gen.addJumpIfR0Equals(123, DROP_LABEL); assertDrop(gen, new byte[MIN_PKT_SIZE], 123); // Test packet size pre-filled memory. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadFromMemory(R0, gen.PACKET_SIZE_MEMORY_SLOT); - gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL); + gen.addJumpIfR0Equals(MIN_PKT_SIZE, DROP_LABEL); assertDrop(gen); // Test IPv4 header size pre-filled memory. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadFromMemory(R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); - gen.addJumpIfR0Equals(20, gen.DROP_LABEL); + gen.addJumpIfR0Equals(20, DROP_LABEL); assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0); // Test not. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addNot(R0); - gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(~1234567890, DROP_LABEL); assertDrop(gen); // Test negate. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addNeg(R0); - gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(-1234567890, DROP_LABEL); assertDrop(gen); // Test move. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1234567890); gen.addMove(R0); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addMove(R1); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); // Test swap. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, 1234567890); gen.addSwap(); - gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL); + gen.addJumpIfR0Equals(1234567890, DROP_LABEL); assertDrop(gen); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1234567890); gen.addSwap(); - gen.addJumpIfR0Equals(0, gen.DROP_LABEL); + gen.addJumpIfR0Equals(0, DROP_LABEL); assertDrop(gen); // Test jump if bytes not equal. - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); program = gen.generate(); assertEquals(6, program.length); assertEquals((13 << 3) | (1 << 1) | 0, program[0]); @@ -642,34 +646,34 @@ public class ApfTest { assertEquals(1, program[4]); assertEquals(123, program[5]); assertDrop(program, new byte[MIN_PKT_SIZE], 0); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0}; assertPass(gen, packet123, 0); - gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); + gen = new ApfV4Generator(MIN_APF_VERSION); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, DROP_LABEL); assertDrop(gen, packet123, 0); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, DROP_LABEL); byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0}; assertDrop(gen, packet12345, 0); - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, DROP_LABEL); assertPass(gen, packet12345, 0); } - @Test(expected = ApfGenerator.IllegalInstructionException.class) + @Test(expected = ApfV4Generator.IllegalInstructionException.class) public void testApfGeneratorWantsV2OrGreater() throws Exception { // The minimum supported APF version is 2. - new ApfGenerator(1); + new ApfV4Generator(1); } @Test public void testApfDataOpcodesWantApfV3() throws IllegalInstructionException, Exception { - ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION); + ApfV4Generator gen = new ApfV4Generator(MIN_APF_VERSION); try { gen.addStoreData(R0, 0); fail(); @@ -689,25 +693,25 @@ public class ApfTest { */ @Test public void testImmediateEncoding() throws IllegalInstructionException { - ApfGenerator gen; + ApfV4Generator gen; // 0-byte immediate: li R0, 0 - gen = new ApfGenerator(4); + gen = new ApfV4Generator(4); gen.addLoadImmediate(R0, 0); assertProgramEquals(new byte[]{LI_OP | SIZE0}, gen.generate()); // 1-byte immediate: li R0, 42 - gen = new ApfGenerator(4); + gen = new ApfV4Generator(4); gen.addLoadImmediate(R0, 42); assertProgramEquals(new byte[]{LI_OP | SIZE8, 42}, gen.generate()); // 2-byte immediate: li R1, 0x1234 - gen = new ApfGenerator(4); + gen = new ApfV4Generator(4); gen.addLoadImmediate(R1, 0x1234); assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1_REG, 0x12, 0x34}, gen.generate()); // 4-byte immediate: li R0, 0x12345678 - gen = new ApfGenerator(3); + gen = new ApfV4Generator(3); gen.addLoadImmediate(R0, 0x12345678); assertProgramEquals( new byte[]{LI_OP | SIZE32, 0x12, 0x34, 0x56, 0x78}, @@ -719,21 +723,21 @@ public class ApfTest { */ @Test public void testNegativeImmediateEncoding() throws IllegalInstructionException { - ApfGenerator gen; + ApfV4Generator gen; // 1-byte negative immediate: li R0, -42 - gen = new ApfGenerator(3); + gen = new ApfV4Generator(3); gen.addLoadImmediate(R0, -42); assertProgramEquals(new byte[]{LI_OP | SIZE8, -42}, gen.generate()); // 2-byte negative immediate: li R1, -0x1122 - gen = new ApfGenerator(3); + gen = new ApfV4Generator(3); gen.addLoadImmediate(R1, -0x1122); assertProgramEquals(new byte[]{LI_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // 4-byte negative immediate: li R0, -0x11223344 - gen = new ApfGenerator(3); + gen = new ApfV4Generator(3); gen.addLoadImmediate(R0, -0x11223344); assertProgramEquals( new byte[]{LI_OP | SIZE32, (byte)0xEE, (byte)0xDD, (byte)0xCC, (byte)0xBC}, @@ -745,26 +749,26 @@ public class ApfTest { */ @Test public void testLoadStoreDataEncoding() throws IllegalInstructionException { - ApfGenerator gen; + ApfV4Generator gen; // Load data with no offset: lddw R0, [0 + r1] - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadData(R0, 0); assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate()); // Store data with 8bit negative offset: lddw r0, [-42 + r1] - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addStoreData(R0, -42); assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate()); // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0] - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addStoreData(R1, -0x1122); assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0] - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadData(R1, 0xDEADBEEF); assertProgramEquals( new byte[]{LDDW_OP | SIZE32 | R1_REG, @@ -782,12 +786,12 @@ public class ApfTest { byte[] expected_data = data.clone(); // No memory access instructions: should leave the data segment untouched. - ApfGenerator gen = new ApfGenerator(APF_VERSION_4); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Expect value 0x87654321 to be stored starting from address -11 from the end of the // data buffer, in big-endian order. - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R0, 0x87654321); gen.addLoadImmediate(R1, -5); gen.addStoreData(R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16) @@ -804,10 +808,10 @@ public class ApfTest { @Test public void testApfDataRead() throws IllegalInstructionException, Exception { // Program that DROPs if address 10 (-6) contains 0x87654321. - ApfGenerator gen = new ApfGenerator(APF_VERSION_4); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R1, 1000); gen.addLoadData(R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16) - gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL); + gen.addJumpIfR0Equals(0x87654321, DROP_LABEL); byte[] program = gen.generate(); byte[] packet = new byte[MIN_PKT_SIZE]; @@ -833,7 +837,7 @@ public class ApfTest { */ @Test public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception { - ApfGenerator gen = new ApfGenerator(APF_VERSION_4); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R1, -22); gen.addLoadData(R0, 0); // Load from address 32 -22 + 0 = 10 gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733 @@ -860,38 +864,38 @@ public class ApfTest { byte[] expected_data = data; // Program that DROPs unconditionally. This is our the baseline. - ApfGenerator gen = new ApfGenerator(APF_VERSION_4); + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R0, 3); gen.addLoadData(R1, 7); - gen.addJump(gen.DROP_LABEL); + gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // Same program as before, but this time we're trying to load past the end of the data. - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, 15); // 20 + 15 > 32 - gen.addJump(gen.DROP_LABEL); // Not reached. + gen.addJump(DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Subtracting an immediate should work... - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -4); - gen.addJump(gen.DROP_LABEL); + gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...and underflowing simply wraps around to the end of the buffer... - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -30); - gen.addJump(gen.DROP_LABEL); + gen.addJump(DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...but doesn't allow accesses before the start of the buffer - gen = new ApfGenerator(APF_VERSION_4); + gen = new ApfV4Generator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -1000); - gen.addJump(gen.DROP_LABEL); // Not reached. + gen.addJump(DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); } @@ -1379,18 +1383,18 @@ public class ApfTest { } /** Adds to the program a no-op instruction that is one byte long. */ - private void addOneByteNoop(ApfGenerator gen) { + private void addOneByteNoop(ApfV4Generator gen) { gen.addLeftShift(0); } @Test public void testAddOneByteNoopAddsOneByte() throws Exception { - ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION); + ApfV4Generator gen = new ApfV4Generator(MIN_APF_VERSION); addOneByteNoop(gen); assertEquals(1, gen.generate().length); final int count = 42; - gen = new ApfGenerator(MIN_APF_VERSION); + gen = new ApfV4Generator(MIN_APF_VERSION); for (int i = 0; i < count; i++) { addOneByteNoop(gen); } @@ -1469,8 +1473,8 @@ public class ApfTest { apfFilter.shutdown(); } - private ApfGenerator generateDnsFilter(boolean ipv6, String... labels) throws Exception { - ApfGenerator gen = new ApfGenerator(MIN_APF_VERSION); + private ApfV4Generator generateDnsFilter(boolean ipv6, String... labels) throws Exception { + ApfV4Generator gen = new ApfV4Generator(MIN_APF_VERSION); gen.addLoadImmediate(R1, ipv6 ? IPV6_HEADER_LEN : IPV4_HEADER_LEN); DnsUtils.generateFilter(gen, labels); return gen; @@ -1479,7 +1483,7 @@ public class ApfTest { private void doTestDnsParsing(boolean expectPass, boolean ipv6, String filterName, byte[] pkt) throws Exception { final String[] labels = filterName.split(/*regex=*/ "[.]"); - ApfGenerator gen = generateDnsFilter(ipv6, labels); + ApfV4Generator gen = generateDnsFilter(ipv6, labels); // Hack to prevent the APF instruction limit triggering. for (int i = 0; i < 500; i++) { @@ -1543,7 +1547,7 @@ public class ApfTest { String filterName) throws Exception { final String[] labels = filterName.split(/*regex=*/ "[.]"); - ApfGenerator gen = generateDnsFilter(/*ipv6=*/ true, labels); + ApfV4Generator gen = generateDnsFilter(/*ipv6=*/ true, labels); assertEquals("Program for " + filterName + " had unexpected length:", expectedLength, gen.generate().length); } @@ -1567,7 +1571,7 @@ public class ApfTest { // Check that the generated code, when the program contains the specified number of extra // bytes, is capable of dropping the packet. - ApfGenerator gen = generateDnsFilter(/*ipv6=*/ true, labels); + ApfV4Generator gen = generateDnsFilter(/*ipv6=*/ true, labels); for (int i = 0; i < expectedNecessaryOverhead; i++) { addOneByteNoop(gen); } @@ -3401,4 +3405,420 @@ public class ApfTest { public void testNoMetricsWrittenForShortDuration_LegacyApfFilter() throws Exception { verifyNoMetricsWrittenForShortDuration(true /* isLegacy */); } + + @Test + public void testFullApfV4ProgramGenerationIPV6() throws IllegalInstructionException { + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); + gen.addLoadImmediate(R1, -4); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addLoad16(R0, 12); + gen.addLoadImmediate(R1, -108); + gen.addJumpIfR0LessThan(0x600, "LABEL_504"); + gen.addLoadImmediate(R1, -112); + gen.addJumpIfR0Equals(0x88a2, "LABEL_504"); + gen.addJumpIfR0Equals(0x88a4, "LABEL_504"); + gen.addJumpIfR0Equals(0x88b8, "LABEL_504"); + gen.addJumpIfR0Equals(0x88cd, "LABEL_504"); + gen.addJumpIfR0Equals(0x88e1, "LABEL_504"); + gen.addJumpIfR0Equals(0x88e3, "LABEL_504"); + gen.addJumpIfR0NotEquals(0x806, "LABEL_116"); + gen.addLoadImmediate(R0, 14); + gen.addLoadImmediate(R1, -36); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_498"); + gen.addLoad16(R0, 20); + gen.addJumpIfR0Equals(0x1, "LABEL_102"); + gen.addLoadImmediate(R1, -40); + gen.addJumpIfR0NotEquals(0x2, "LABEL_498"); + gen.addLoad32(R0, 28); + gen.addLoadImmediate(R1, -116); + gen.addJumpIfR0Equals(0x0, "LABEL_504"); + gen.addLoadImmediate(R0, 0); + gen.addLoadImmediate(R1, -44); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498"); + + gen.defineLabel("LABEL_102"); + gen.addLoad32(R0, 38); + gen.addLoadImmediate(R1, -64); + gen.addJumpIfR0Equals(0x0, "LABEL_504"); + gen.addLoadImmediate(R1, -8); + gen.addJump("LABEL_498"); + + gen.defineLabel("LABEL_116"); + gen.addLoad16(R0, 12); + gen.addJumpIfR0NotEquals(0x800, "LABEL_207"); + gen.addLoad8(R0, 23); + gen.addJumpIfR0NotEquals(0x11, "LABEL_159"); + gen.addLoad16(R0, 20); + gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_159"); + gen.addLoadFromMemory(R1, 13); + gen.addLoad16Indexed(R0, 16); + gen.addJumpIfR0NotEquals(0x44, "LABEL_159"); + gen.addLoadImmediate(R0, 50); + gen.addAddR1(); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345"), "LABEL_159"); + gen.addLoadImmediate(R1, -12); + gen.addJump("LABEL_498"); + + gen.defineLabel("LABEL_159"); + gen.addLoad8(R0, 30); + gen.addAnd(240); + gen.addLoadImmediate(R1, -84); + gen.addJumpIfR0Equals(0xe0, "LABEL_504"); + gen.addLoadImmediate(R1, -76); + gen.addLoad32(R0, 30); + gen.addJumpIfR0Equals(0xffffffff, "LABEL_504"); + gen.addLoadImmediate(R1, -24); + gen.addLoadImmediate(R0, 0); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498"); + gen.addLoadImmediate(R1, -72); + gen.addJump("LABEL_504"); + gen.addLoadImmediate(R1, -16); + gen.addJump("LABEL_498"); + + gen.defineLabel("LABEL_207"); + gen.addJumpIfR0Equals(0x86dd, "LABEL_231"); + gen.addLoadImmediate(R0, 0); + gen.addLoadImmediate(R1, -48); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_498"); + gen.addLoadImmediate(R1, -56); + gen.addJump("LABEL_504"); + + gen.defineLabel("LABEL_231"); + gen.addLoad8(R0, 20); + gen.addJumpIfR0Equals(0x3a, "LABEL_249"); + gen.addLoadImmediate(R1, -104); + gen.addLoad8(R0, 38); + gen.addJumpIfR0Equals(0xff, "LABEL_504"); + gen.addLoadImmediate(R1, -32); + gen.addJump("LABEL_498"); + + gen.defineLabel("LABEL_249"); + gen.addLoad8(R0, 54); + gen.addLoadImmediate(R1, -88); + gen.addJumpIfR0Equals(0x85, "LABEL_504"); + gen.addJumpIfR0NotEquals(0x88, "LABEL_283"); + gen.addLoadImmediate(R0, 38); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_283"); + gen.addLoadImmediate(R1, -92); + gen.addJump("LABEL_504"); + + gen.defineLabel("LABEL_283"); + gen.addLoadFromMemory(R0, 14); + gen.addJumpIfR0NotEquals(0xa6, "LABEL_496"); + gen.addLoadFromMemory(R0, 15); + gen.addJumpIfR0GreaterThan(0x254, "LABEL_496"); + gen.addLoadImmediate(R0, 0); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("e212507c6345648788fd6df086dd68"), "LABEL_496"); + gen.addLoadImmediate(R0, 18); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c63458600"), "LABEL_496"); + gen.addLoadImmediate(R0, 58); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("4000"), "LABEL_496"); + gen.addLoad16(R0, 60); + gen.addJumpIfR0LessThan(0x254, "LABEL_496"); + gen.addLoadImmediate(R0, 62); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("0000000000000000"), "LABEL_496"); + gen.addLoadImmediate(R0, 78); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("19050000"), "LABEL_496"); + gen.addLoad32(R0, 82); + gen.addJumpIfR0LessThan(0x254, "LABEL_496"); + gen.addLoadImmediate(R0, 86); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2001486048600000000000000000646420014860486000000000000000000064"), "LABEL_496"); + gen.addLoadImmediate(R0, 118); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("030440c0"), "LABEL_496"); + gen.addLoad32(R0, 122); + gen.addJumpIfR0LessThan(0x254, "LABEL_496"); + gen.addLoad32(R0, 126); + gen.addJumpIfR0LessThan(0x254, "LABEL_496"); + gen.addLoadImmediate(R0, 130); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("00000000"), "LABEL_496"); + gen.addLoadImmediate(R0, 134); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("2a0079e10abc15390000000000000000"), "LABEL_496"); + gen.addLoadImmediate(R1, -60); + gen.addJump("LABEL_504"); + + gen.defineLabel("LABEL_496"); + gen.addLoadImmediate(R1, -28); + + gen.defineLabel("LABEL_498"); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addJump(PASS_LABEL); + + gen.defineLabel("LABEL_504"); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addJump(DROP_LABEL); + + byte[] program = gen.generate(); + final String programString = toHexString(program).toLowerCase(); + final String referenceProgramHexString = "6bfcb03a01b8120c6b949401e906006b907c01e288a27c01dd88a47c01d888b87c01d388cd7c01ce88e17c01c988e384004008066a0e6bdca401af000600010800060412147a1e016bd88401a300021a1c6b8c7c01a00000686bd4a4018c0006ffffffffffff1a266bc07c018900006bf874017e120c84005408000a17821f1112149c00181fffab0d2a108211446a3239a20506e212507c63456bf47401530a1e52f06bac7c014e00e06bb41a1e7e00000141ffffffff6be868a4012d0006ffffffffffff6bb874012e6bf07401237c001386dd686bd0a401100006ffffffffffff6bc87401110a147a0d3a6b980a267c010300ff6be072f90a366ba87af8858218886a26a2040fff02000000000000000000000000006ba472ddaa0e82d0a6aa0f8c00c9025468a2b60fe212507c6345648788fd6df086dd686a12a28b2600703afffe800000000000002a0079e10abc1539fe80000000000000e01250fffe7c634586006a3aa284024000123c94007d02546a3ea2700800000000000000006a4ea26704190500001a5294006002546a56a23b2020014860486000000000000000006464200148604860000000000000000000646a76a23204030440c01a7a94002b02541a7e94002402546c0082a21a04000000006c0086a204102a0079e10abc153900000000000000006bc472086be4b03a01b87206b03a01b87201"; + assertEquals(referenceProgramHexString, programString); + } + + @Test + public void testFullApfV4ProgramGenerationIPV4() throws IllegalInstructionException { + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); + gen.addLoadImmediate(R1, -4); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addLoad16(R0, 12); + gen.addLoadImmediate(R1, -108); + gen.addJumpIfR0LessThan(0x600, "LABEL_283"); + gen.addLoadImmediate(R1, -112); + gen.addJumpIfR0Equals(0x88a2, "LABEL_283"); + gen.addJumpIfR0Equals(0x88a4, "LABEL_283"); + gen.addJumpIfR0Equals(0x88b8, "LABEL_283"); + gen.addJumpIfR0Equals(0x88cd, "LABEL_283"); + gen.addJumpIfR0Equals(0x88e1, "LABEL_283"); + gen.addJumpIfR0Equals(0x88e3, "LABEL_283"); + gen.addJumpIfR0NotEquals(0x806, "LABEL_109"); + gen.addLoadImmediate(R0, 14); + gen.addLoadImmediate(R1, -36); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_277"); + gen.addLoad16(R0, 20); + gen.addJumpIfR0Equals(0x1, "LABEL_94"); + gen.addLoadImmediate(R1, -40); + gen.addJumpIfR0NotEquals(0x2, "LABEL_277"); + gen.addLoad32(R0, 28); + gen.addLoadImmediate(R1, -116); + gen.addJumpIfR0Equals(0x0, "LABEL_283"); + gen.addLoadImmediate(R0, 0); + gen.addLoadImmediate(R1, -44); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277"); + + gen.defineLabel("LABEL_94"); + gen.addLoadImmediate(R0, 38); + gen.addLoadImmediate(R1, -68); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801b3"), "LABEL_283"); + gen.addLoadImmediate(R1, -8); + gen.addJump("LABEL_277"); + + gen.defineLabel("LABEL_109"); + gen.addLoad16(R0, 12); + gen.addJumpIfR0NotEquals(0x800, "LABEL_204"); + gen.addLoad8(R0, 23); + gen.addJumpIfR0NotEquals(0x11, "LABEL_151"); + gen.addLoad16(R0, 20); + gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_151"); + gen.addLoadFromMemory(R1, 13); + gen.addLoad16Indexed(R0, 16); + gen.addJumpIfR0NotEquals(0x44, "LABEL_151"); + gen.addLoadImmediate(R0, 50); + gen.addAddR1(); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("f683d58f832b"), "LABEL_151"); + gen.addLoadImmediate(R1, -12); + gen.addJump("LABEL_277"); + + gen.defineLabel("LABEL_151"); + gen.addLoad8(R0, 30); + gen.addAnd(240); + gen.addLoadImmediate(R1, -84); + gen.addJumpIfR0Equals(0xe0, "LABEL_283"); + gen.addLoadImmediate(R1, -76); + gen.addLoad32(R0, 30); + gen.addJumpIfR0Equals(0xffffffff, "LABEL_283"); + gen.addLoadImmediate(R1, -80); + gen.addJumpIfR0Equals(0xc0a801ff, "LABEL_283"); + gen.addLoadImmediate(R1, -24); + gen.addLoadImmediate(R0, 0); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277"); + gen.addLoadImmediate(R1, -72); + gen.addJump("LABEL_283"); + gen.addLoadImmediate(R1, -16); + gen.addJump("LABEL_277"); + + gen.defineLabel("LABEL_204"); + gen.addJumpIfR0Equals(0x86dd, "LABEL_225"); + gen.addLoadImmediate(R0, 0); + gen.addLoadImmediate(R1, -48); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_277"); + gen.addLoadImmediate(R1, -56); + gen.addJump("LABEL_283"); + + gen.defineLabel("LABEL_225"); + gen.addLoad8(R0, 20); + gen.addJumpIfR0Equals(0x3a, "LABEL_241"); + gen.addLoadImmediate(R1, -104); + gen.addLoad8(R0, 38); + gen.addJumpIfR0Equals(0xff, "LABEL_283"); + gen.addLoadImmediate(R1, -32); + gen.addJump("LABEL_277"); + + gen.defineLabel("LABEL_241"); + gen.addLoad8(R0, 54); + gen.addLoadImmediate(R1, -88); + gen.addJumpIfR0Equals(0x85, "LABEL_283"); + gen.addJumpIfR0NotEquals(0x88, "LABEL_275"); + gen.addLoadImmediate(R0, 38); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_275"); + gen.addLoadImmediate(R1, -92); + gen.addJump("LABEL_283"); + + gen.defineLabel("LABEL_275"); + gen.addLoadImmediate(R1, -28); + + gen.defineLabel("LABEL_277"); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addJump(PASS_LABEL); + + gen.defineLabel("LABEL_283"); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addJump(DROP_LABEL); + + byte[] program = gen.generate(); + final String programString = toHexString(program).toLowerCase(); + final String referenceProgramHexString = "6bfcb03a01b8120c6b9494010c06006b907c010588a27c010088a47c00fb88b87c00f688cd7c00f188e17c00ec88e384003908066a0e6bdca2d40600010800060412147a18016bd882ca021a1c6b8c7ac900686bd4a2b706ffffffffffff6a266bbca2b204c0a801b36bf872a8120c84005808000a17821e1112149c00171fffab0d2a108210446a3239a20406f683d58f832b6bf4727e0a1e52f06bac7a7be06bb41a1e7e0000006effffffff6bb07e00000063c0a801ff6be868a25106ffffffffffff6bb872536bf072497c001086dd686bd0a23806ffffffffffff6bc8723a0a147a0b3a6b980a267a2eff6be072240a366ba87a23858218886a26a2040fff02000000000000000000000000006ba472086be4b03a01b87206b03a01b87201"; + assertEquals(referenceProgramHexString, programString); + } + + @Test + public void testFullApfV4ProgramGenerationNatTKeepAliveV4() throws IllegalInstructionException { + ApfV4Generator gen = new ApfV4Generator(APF_VERSION_4); + gen.addLoadImmediate(R1, -4); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addLoad16(R0, 12); + gen.addLoadImmediate(R1, -108); + gen.addJumpIfR0LessThan(0x600, "LABEL_345"); + gen.addLoadImmediate(R1, -112); + gen.addJumpIfR0Equals(0x88a2, "LABEL_345"); + gen.addJumpIfR0Equals(0x88a4, "LABEL_345"); + gen.addJumpIfR0Equals(0x88b8, "LABEL_345"); + gen.addJumpIfR0Equals(0x88cd, "LABEL_345"); + gen.addJumpIfR0Equals(0x88e1, "LABEL_345"); + gen.addJumpIfR0Equals(0x88e3, "LABEL_345"); + gen.addJumpIfR0NotEquals(0x806, "LABEL_115"); + gen.addLoadImmediate(R0, 14); + gen.addLoadImmediate(R1, -36); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("000108000604"), "LABEL_339"); + gen.addLoad16(R0, 20); + gen.addJumpIfR0Equals(0x1, "LABEL_100"); + gen.addLoadImmediate(R1, -40); + gen.addJumpIfR0NotEquals(0x2, "LABEL_339"); + gen.addLoad32(R0, 28); + gen.addLoadImmediate(R1, -116); + gen.addJumpIfR0Equals(0x0, "LABEL_345"); + gen.addLoadImmediate(R0, 0); + gen.addLoadImmediate(R1, -44); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_339"); + + gen.defineLabel("LABEL_100"); + gen.addLoadImmediate(R0, 38); + gen.addLoadImmediate(R1, -68); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("c0a801be"), "LABEL_345"); + gen.addLoadImmediate(R1, -8); + gen.addJump("LABEL_339"); + + gen.defineLabel("LABEL_115"); + gen.addLoad16(R0, 12); + gen.addJumpIfR0NotEquals(0x800, "LABEL_263"); + gen.addLoad8(R0, 23); + gen.addJumpIfR0NotEquals(0x11, "LABEL_157"); + gen.addLoad16(R0, 20); + gen.addJumpIfR0AnyBitsSet(0x1fff, "LABEL_157"); + gen.addLoadFromMemory(R1, 13); + gen.addLoad16Indexed(R0, 16); + gen.addJumpIfR0NotEquals(0x44, "LABEL_157"); + gen.addLoadImmediate(R0, 50); + gen.addAddR1(); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ea42226789c0"), "LABEL_157"); + gen.addLoadImmediate(R1, -12); + gen.addJump("LABEL_339"); + + gen.defineLabel("LABEL_157"); + gen.addLoad8(R0, 30); + gen.addAnd(240); + gen.addLoadImmediate(R1, -84); + gen.addJumpIfR0Equals(0xe0, "LABEL_345"); + gen.addLoadImmediate(R1, -76); + gen.addLoad32(R0, 30); + gen.addJumpIfR0Equals(0xffffffff, "LABEL_345"); + gen.addLoadImmediate(R1, -80); + gen.addJumpIfR0Equals(0xc0a801ff, "LABEL_345"); + gen.addLoad8(R0, 23); + gen.addJumpIfR0NotEquals(0x11, "LABEL_243"); + gen.addLoadImmediate(R0, 26); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("6b7a1f1fc0a801be"), "LABEL_243"); + gen.addLoadFromMemory(R0, 13); + gen.addAdd(8); + gen.addSwap(); + gen.addLoad16(R0, 16); + gen.addNeg(R1); + gen.addAddR1(); + gen.addJumpIfR0NotEquals(0x1, "LABEL_243"); + gen.addLoadFromMemory(R0, 13); + gen.addAdd(14); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("1194ceca"), "LABEL_243"); + gen.addAdd(8); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff"), "LABEL_243"); + gen.addLoadImmediate(R1, -128); + gen.addJump("LABEL_345"); + + gen.defineLabel("LABEL_243"); + gen.addLoadImmediate(R1, -24); + gen.addLoadImmediate(R0, 0); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_339"); + gen.addLoadImmediate(R1, -72); + gen.addJump("LABEL_345"); + gen.addLoadImmediate(R1, -16); + gen.addJump("LABEL_339"); + + gen.defineLabel("LABEL_263"); + gen.addJumpIfR0Equals(0x86dd, "LABEL_284"); + gen.addLoadImmediate(R0, 0); + gen.addLoadImmediate(R1, -48); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ffffffffffff"), "LABEL_339"); + gen.addLoadImmediate(R1, -56); + gen.addJump("LABEL_345"); + + gen.defineLabel("LABEL_284"); + gen.addLoad8(R0, 20); + gen.addJumpIfR0Equals(0x0, "LABEL_339"); + gen.addJumpIfR0Equals(0x3a, "LABEL_303"); + gen.addLoadImmediate(R1, -104); + gen.addLoad8(R0, 38); + gen.addJumpIfR0Equals(0xff, "LABEL_345"); + gen.addLoadImmediate(R1, -32); + gen.addJump("LABEL_339"); + + gen.defineLabel("LABEL_303"); + gen.addLoad8(R0, 54); + gen.addLoadImmediate(R1, -88); + gen.addJumpIfR0Equals(0x85, "LABEL_345"); + gen.addJumpIfR0NotEquals(0x88, "LABEL_337"); + gen.addLoadImmediate(R0, 38); + gen.addJumpIfBytesAtR0NotEqual(hexStringToByteArray("ff0200000000000000000000000000"), "LABEL_337"); + gen.addLoadImmediate(R1, -92); + gen.addJump("LABEL_345"); + + gen.defineLabel("LABEL_337"); + gen.addLoadImmediate(R1, -28); + + gen.defineLabel("LABEL_339"); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addJump(PASS_LABEL); + + gen.defineLabel("LABEL_345"); + gen.addLoadData(R0, 0); + gen.addAdd(1); + gen.addStoreData(R0, 0); + gen.addJump(DROP_LABEL); + + byte[] program = gen.generate(); + final String programString = toHexString(program).toLowerCase(); + final String referenceProgramHexString = "6bfcb03a01b8120c6b9494014a06006b907c014388a27c013e88a47c013988b87c013488cd7c012f88e17c012a88e384003f08066a0e6bdca40110000600010800060412147a1c016bd884010400021a1c6b8c7c01010000686bd4a2ef06ffffffffffff6a266bbca2ea04c0a801be6bf872e0120c84008d08000a17821e1112149c00171fffab0d2a108210446a3239a20406ea42226789c06bf472b60a1e52f06bac7ab3e06bb41a1e7e000000a6ffffffff6bb07e0000009bc0a801ff0a178230116a1aa223086b7a1f1fc0a801beaa0d3a08aa221210ab2139821501aa0d3a0ea20a041194ceca3a08a20401ff6b8072666be868a25406ffffffffffff6bb872566bf0724c7c001086dd686bd0a23b06ffffffffffff6bc8723d0a147a32007a0b3a6b980a267a2eff6be072240a366ba87a23858218886a26a2040fff02000000000000000000000000006ba472086be4b03a01b87206b03a01b87201"; + assertEquals(referenceProgramHexString, programString); + } } diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java index abbdd6b2..56d7cae9 100644 --- a/tests/unit/src/android/net/apf/ApfTestUtils.java +++ b/tests/unit/src/android/net/apf/ApfTestUtils.java @@ -28,7 +28,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.apf.ApfGenerator.IllegalInstructionException; +import android.net.apf.ApfV4Generator.IllegalInstructionException; import android.net.ip.IIpClientCallbacks; import android.net.ip.IpClient; import android.net.metrics.IpConnectivityLog; @@ -155,7 +155,7 @@ public class ApfTestUtils { */ public static void assertDataMemoryContents(int apfVersion, int expected, byte[] program, byte[] packet, byte[] data, byte[] expectedData) - throws ApfGenerator.IllegalInstructionException, Exception { + throws ApfV4Generator.IllegalInstructionException, Exception { assertReturnCodesEqual(expected, apfSimulate(apfVersion, program, packet, data, 0 /* filterAge */)); @@ -176,8 +176,8 @@ public class ApfTestUtils { apfSimulate(apfVersion, program, packet, data, 0 /* filterAge */)); } - private static void assertVerdict(int apfVersion, int expected, ApfGenerator gen, byte[] packet, - int filterAge) throws ApfGenerator.IllegalInstructionException { + private static void assertVerdict(int apfVersion, int expected, ApfV4Generator gen, + byte[] packet, int filterAge) throws ApfV4Generator.IllegalInstructionException { assertReturnCodesEqual(expected, apfSimulate(apfVersion, gen.generate(), packet, null, filterAge)); } @@ -185,32 +185,32 @@ public class ApfTestUtils { /** * Runs the APF program and checks the return code is PASS. */ - public static void assertPass(int apfVersion, ApfGenerator gen, byte[] packet, int filterAge) - throws ApfGenerator.IllegalInstructionException { + public static void assertPass(int apfVersion, ApfV4Generator gen, byte[] packet, int filterAge) + throws ApfV4Generator.IllegalInstructionException { assertVerdict(apfVersion, PASS, gen, packet, filterAge); } /** * Runs the APF program and checks the return code is DROP. */ - public static void assertDrop(int apfVersion, ApfGenerator gen, byte[] packet, int filterAge) - throws ApfGenerator.IllegalInstructionException { + public static void assertDrop(int apfVersion, ApfV4Generator gen, byte[] packet, int filterAge) + throws ApfV4Generator.IllegalInstructionException { assertVerdict(apfVersion, DROP, gen, packet, filterAge); } /** * Runs the APF program and checks the return code is PASS. */ - public static void assertPass(int apfVersion, ApfGenerator gen) - throws ApfGenerator.IllegalInstructionException { + public static void assertPass(int apfVersion, ApfV4Generator gen) + throws ApfV4Generator.IllegalInstructionException { assertVerdict(apfVersion, PASS, gen, new byte[MIN_PKT_SIZE], 0); } /** * Runs the APF program and checks the return code is DROP. */ - public static void assertDrop(int apfVersion, ApfGenerator gen) - throws ApfGenerator.IllegalInstructionException { + public static void assertDrop(int apfVersion, ApfV4Generator gen) + throws ApfV4Generator.IllegalInstructionException { assertVerdict(apfVersion, DROP, gen, new byte[MIN_PKT_SIZE], 0); } @@ -383,7 +383,7 @@ public class ApfTestUtils { @Override @GuardedBy("this") - protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { if (mThrowsExceptionWhenGeneratesProgram) { throw new IllegalStateException(); } @@ -479,7 +479,7 @@ public class ApfTestUtils { @Override @GuardedBy("this") - protected ApfGenerator emitPrologueLocked() throws IllegalInstructionException { + protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { if (mThrowsExceptionWhenGeneratesProgram) { throw new IllegalStateException(); } diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt index 1977a6c2..30d19b2c 100644 --- a/tests/unit/src/android/net/apf/ApfV5Test.kt +++ b/tests/unit/src/android/net/apf/ApfV5Test.kt @@ -15,9 +15,11 @@ */ package android.net.apf -import android.net.apf.ApfGenerator.IllegalInstructionException -import android.net.apf.ApfGenerator.Register.R0 -import android.net.apf.ApfGenerator.Register.R1 +import android.net.apf.ApfV4Generator.IllegalInstructionException +import android.net.apf.ApfV4Generator.MIN_APF_VERSION +import android.net.apf.ApfV4Generator.MIN_APF_VERSION_IN_DEV +import android.net.apf.ApfV4Generator.Register.R0 +import android.net.apf.ApfV4Generator.Register.R1 import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import java.lang.IllegalArgumentException @@ -35,7 +37,7 @@ class ApfV5Test { @Test fun testApfInstructionVersionCheck() { - var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION) + var gen = ApfV4Generator(MIN_APF_VERSION) assertFailsWith<IllegalInstructionException> { gen.addDrop() } assertFailsWith<IllegalInstructionException> { gen.addCountAndDrop(12) } assertFailsWith<IllegalInstructionException> { gen.addCountAndPass(1000) } @@ -59,18 +61,28 @@ class ApfV5Test { assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0LenR1() } assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0(10) } assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0(10) } + assertFailsWith<IllegalInstructionException> { + gen.addJumpIfBytesAtR0Equal(byteArrayOf('A'.code.toByte()), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfV4Generator.DROP_LABEL) } } @Test fun testDataInstructionMustComeFirst() { - var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + var gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addAllocateR0() assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } } @Test fun testApfInstructionEncodingSizeCheck() { - var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + var gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) } assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) } assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) } @@ -83,41 +95,125 @@ class ApfV5Test { assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) } assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) } assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 0, 0), 256, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), 0x0c, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, '.'.code.toByte(), 0, 0), 0x0c, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(0, 0), 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 0, 0), 256, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), 0x0c, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, '.'.code.toByte(), 0, 0), 0x0c, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(0, 0), 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + 0xc0, ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(0, 0), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(0, 0), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + ApfV4Generator.DROP_LABEL) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + ApfV4Generator.DROP_LABEL) } } @Test fun testApfInstructionsEncoding() { - var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION) + var gen = ApfV4Generator(MIN_APF_VERSION) gen.addPass() var program = gen.generate() // encoding PASS opcode: opcode=0, imm_len=0, R=0 assertContentEquals( byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 0)), program) + assertContentEquals( + listOf("0: pass"), + ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addDrop() program = gen.generate() // encoding DROP opcode: opcode=0, imm_len=0, R=1 assertContentEquals( byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 1)), program) + assertContentEquals( + listOf("0: drop"), + ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addCountAndPass(129) program = gen.generate() // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber assertContentEquals( byteArrayOf(encodeInstruction(opcode = 0, immLength = 1, register = 0), 0x81.toByte()), program) + assertContentEquals( + listOf("0: pass 129"), + ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addCountAndDrop(1000) program = gen.generate() // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber assertContentEquals( byteArrayOf(encodeInstruction(opcode = 0, immLength = 2, register = 1), 0x03, 0xe8.toByte()), program) + assertContentEquals( + listOf("0: drop 1000"), + ApfJniUtils.disassembleApf(program).map { it.trim() } ) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addAllocateR0() gen.addAllocate(1500) program = gen.generate() @@ -128,10 +224,10 @@ class ApfV5Test { encodeInstruction(opcode = 21, immLength = 1, register = 1), 36, 0x05, 0xDC.toByte()), program) - // TODO: add back disassembling test check after we update the apf_disassembler - // assertContentEquals(arrayOf(" 0: alloc"), ApfJniUtils.disassembleApf(program)) + assertContentEquals(listOf("0: allocate r0", "2: allocate 1500"), + ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addTransmit() gen.addDiscard() program = gen.generate() @@ -142,10 +238,10 @@ class ApfV5Test { encodeInstruction(opcode = 21, immLength = 1, register = 0), 37, encodeInstruction(opcode = 21, immLength = 1, register = 1), 37, ), program) - // TODO: add back disassembling test check after we update the apf_disassembler - // assertContentEquals(arrayOf(" 0: trans"), ApfJniUtils.disassembleApf(program)) + assertContentEquals(listOf("0: discard", "2: transmit"), + ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) val largeByteArray = ByteArray(256) { 0x01 } gen.addData(largeByteArray) program = gen.generate() @@ -153,8 +249,10 @@ class ApfV5Test { assertContentEquals(byteArrayOf( encodeInstruction(opcode = 14, immLength = 2, register = 1), 0x01, 0x00) + largeByteArray, program) + assertContentEquals(listOf("0: data 256," + "01".repeat(256) ), + ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addWriteU8(0x01) gen.addWriteU16(0x0102) gen.addWriteU32(0x01020304) @@ -176,19 +274,20 @@ class ApfV5Test { encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00, encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00, 0x00), program) - assertContentEquals(arrayOf( - " 0: write 0x01", - " 2: write 0x0102", - " 5: write 0x01020304", - " 10: write 0x00", - " 12: write 0x80", - " 14: write 0x0000", - " 17: write 0x8000", - " 20: write 0x00000000", - " 25: write 0x80000000"), - ApfJniUtils.disassembleApf(program)) - - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + assertContentEquals(listOf( + "0: write 0x01", + "2: write 0x0102", + "5: write 0x01020304", + "10: write 0x00", + "12: write 0x80", + "14: write 0x0000", + "17: write 0x8000", + "20: write 0x00000000", + "25: write 0x80000000" + ), + ApfJniUtils.disassembleApf(program).map { it.trim() }) + + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addWriteU8(R0) gen.addWriteU16(R0) gen.addWriteU32(R0) @@ -204,16 +303,15 @@ class ApfV5Test { encodeInstruction(21, 1, 1), 39, encodeInstruction(21, 1, 1), 40 ), program) - // TODO: add back disassembling test check after we update the apf_disassembler -// assertContentEquals(arrayOf( -// " 0: ewrite1 r0", -// " 2: ewrite2 r0", -// " 4: ewrite4 r0", -// " 6: ewrite1 r1", -// " 8: ewrite2 r1", -// " 10: ewrite4 r1"), ApfJniUtils.disassembleApf(program)) - - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + assertContentEquals(listOf( + "0: ewrite1 r0", + "2: ewrite2 r0", + "4: ewrite4 r0", + "6: ewrite1 r1", + "8: ewrite2 r1", + "10: ewrite4 r1"), ApfJniUtils.disassembleApf(program).map { it.trim() }) + + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addDataCopy(0, 10) gen.addDataCopy(1, 5) gen.addPacketCopy(1000, 255) @@ -224,12 +322,12 @@ class ApfV5Test { encodeInstruction(25, 2, 0), 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(), ), program) - // TODO: add back disassembling test check after we update the apf_disassembler -// assertContentEquals(arrayOf( -// " 0: dcopy 0, 5", -// " 3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program)) + assertContentEquals(listOf( + "0: dcopy 0, 10", + "2: dcopy 1, 5", + "5: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program).map { it.trim() }) - gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) gen.addPacketCopyFromR0LenR1() gen.addPacketCopyFromR0(5) gen.addDataCopyFromR0LenR1() @@ -245,6 +343,34 @@ class ApfV5Test { // assertContentEquals(arrayOf( // " 0: dcopy [r1+0], 5", // " 4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program)) + + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) + gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfV4Generator.DROP_LABEL) + program = gen.generate() + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 20, immLength = 1, register = 1), + 1, 1, 'a'.code.toByte()), program) + + val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0) + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) + gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 43, 11, 0x0c.toByte(), + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(), + ) + qnames, program) + + gen = ApfV4Generator(MIN_APF_VERSION_IN_DEV) + gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfV4Generator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfV4Generator.DROP_LABEL) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 44, 10, + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 44, 1, + ) + qnames, program) } private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte { diff --git a/tests/unit/src/android/net/apf/Bpf2Apf.java b/tests/unit/src/android/net/apf/Bpf2Apf.java index 5d57cde2..795c2b35 100644 --- a/tests/unit/src/android/net/apf/Bpf2Apf.java +++ b/tests/unit/src/android/net/apf/Bpf2Apf.java @@ -16,9 +16,8 @@ package android.net.apf; -import android.net.apf.ApfGenerator; -import android.net.apf.ApfGenerator.IllegalInstructionException; -import android.net.apf.ApfGenerator.Register; +import android.net.apf.ApfV4Generator.IllegalInstructionException; +import android.net.apf.ApfV4Generator.Register; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -52,7 +51,7 @@ public class Bpf2Apf { * APF instruction(s) and append them to {@code gen}. Here's an example line: * (001) jeq #0x86dd jt 2 jf 7 */ - private static void convertLine(String line, ApfGenerator gen) + private static void convertLine(String line, ApfV4Generator gen) throws IllegalInstructionException { if (line.indexOf("(") != 0 || line.indexOf(")") != 4 || line.indexOf(" ") != 5) { throw new IllegalArgumentException("Unhandled instruction: " + line); @@ -307,7 +306,7 @@ public class Bpf2Apf { * program and return it. */ public static byte[] convert(String bpf) throws IllegalInstructionException { - ApfGenerator gen = new ApfGenerator(3); + ApfV4Generator gen = new ApfV4Generator(3); for (String line : bpf.split("\\n")) convertLine(line, gen); return gen.generate(); } @@ -320,7 +319,7 @@ public class Bpf2Apf { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line = null; StringBuilder responseData = new StringBuilder(); - ApfGenerator gen = new ApfGenerator(3); + ApfV4Generator gen = new ApfV4Generator(3); while ((line = in.readLine()) != null) convertLine(line, gen); System.out.write(gen.generate()); } diff --git a/tests/unit/src/android/net/apf/JumpTableTest.kt b/tests/unit/src/android/net/apf/JumpTableTest.kt index 6fdf38f7..3858aac0 100644 --- a/tests/unit/src/android/net/apf/JumpTableTest.kt +++ b/tests/unit/src/android/net/apf/JumpTableTest.kt @@ -34,7 +34,7 @@ import org.mockito.MockitoAnnotations class JumpTableTest { @Mock - lateinit var gen: ApfGenerator + lateinit var gen: ApfV4Generator @Before fun setUp() { @@ -94,11 +94,11 @@ class JumpTableTest { j.generate(gen) inOrder.verify(gen).defineLabel(name) - inOrder.verify(gen).addLoadFromMemory(ApfGenerator.Register.R0, slot) + inOrder.verify(gen).addLoadFromMemory(ApfV4Generator.Register.R0, slot) inOrder.verify(gen).addJumpIfR0Equals(0, "foo") inOrder.verify(gen).addJumpIfR0Equals(1, "bar") inOrder.verify(gen).addJumpIfR0Equals(2, "baz") - inOrder.verify(gen).addJump(ApfGenerator.PASS_LABEL) + inOrder.verify(gen).addJump(ApfV4Generator.PASS_LABEL) inOrder.verifyNoMoreInteractions() } } diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt index 32cf4647..8e100e4c 100644 --- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt +++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt @@ -29,21 +29,6 @@ import org.junit.runner.RunWith @SmallTest class Dhcp6PacketTest { @Test - fun testDecodeDhcp6PacketWithoutIaPdOption() { - val solicitHex = - // Solicit, Transaction ID - "01000F51" + - // client identifier option(option_len=12) - "0001000C0003001B024CCBFFFE5F6EA9" + - // elapsed time option(option_len=2) - "000800020000" - val bytes = HexDump.hexStringToByteArray(solicitHex) - assertThrows(Dhcp6Packet.ParseException::class.java) { - Dhcp6Packet.decode(bytes, bytes.size) - } - } - - @Test fun testDecodeDhcp6SolicitPacket() { val solicitHex = // Solicit, Transaction ID diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt index 4d57df5e..f93a3bdf 100644 --- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt +++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt @@ -34,10 +34,8 @@ import android.stats.connectivity.IpType.IPV6 import android.stats.connectivity.NudEventType import android.stats.connectivity.NudEventType.NUD_CONFIRM_FAILED import android.stats.connectivity.NudEventType.NUD_CONFIRM_FAILED_CRITICAL -import android.stats.connectivity.NudEventType.NUD_CONFIRM_MAC_ADDRESS_CHANGED import android.stats.connectivity.NudEventType.NUD_ORGANIC_FAILED import android.stats.connectivity.NudEventType.NUD_ORGANIC_FAILED_CRITICAL -import android.stats.connectivity.NudEventType.NUD_ORGANIC_MAC_ADDRESS_CHANGED import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL import android.stats.connectivity.NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED @@ -53,9 +51,13 @@ import com.android.net.module.util.InterfaceParams import com.android.net.module.util.SharedLog import com.android.net.module.util.ip.IpNeighborMonitor import com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED +import com.android.net.module.util.netlink.StructNdMsg.NUD_PROBE import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE import com.android.networkstack.metrics.IpReachabilityMonitorMetrics +import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION +import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION +import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION import com.android.testutils.makeNewNeighMessage import com.android.testutils.waitForIdle import java.io.FileDescriptor @@ -89,6 +91,9 @@ private const val TEST_TIMEOUT_MS = 10_000L private val TEST_IPV4_GATEWAY = parseNumericAddress("192.168.222.3") as Inet4Address private val TEST_IPV6_GATEWAY = parseNumericAddress("2001:db8::1") as Inet6Address +private val TEST_MAC_1 = "001122334455" +private val TEST_MAC_2 = "1122334455aa" + // IPv4 gateway is also DNS server. private val TEST_IPV4_GATEWAY_DNS = parseNumericAddress("192.168.222.100") as Inet4Address @@ -258,6 +263,14 @@ class IpReachabilityMonitorTest { }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any()) doReturn(mIpReachabilityMonitorMetrics) .`when`(dependencies).getIpReachabilityMonitorMetrics() + doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(), + eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION)) + + // TODO: test with non-default flag combinations. + // Note: because dependencies is a mock, all features that are not specified here are + // neither enabled nor chickened out. + doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(), + eq(IP_REACHABILITY_ROUTER_MAC_CHANGE_FAILURE_ONLY_AFTER_ROAM_VERSION)) val monitorFuture = CompletableFuture<IpReachabilityMonitor>() // IpReachabilityMonitor needs to be started from the handler thread @@ -300,6 +313,15 @@ class IpReachabilityMonitorTest { lostNeighbor: InetAddress, eventType: NudEventType ) { + runLoseProvisioningTest(newLp, lostNeighbor, eventType, true /* expectedNotifyLost */) + } + + private fun runLoseProvisioningTest( + newLp: LinkProperties, + lostNeighbor: InetAddress, + eventType: NudEventType, + expectedNotifyLost: Boolean + ) { reachabilityMonitor.updateLinkProperties(newLp) neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV4_GATEWAY, NUD_STALE)) @@ -308,8 +330,12 @@ class IpReachabilityMonitorTest { neighborMonitor.enqueuePacket(makeNewNeighMessage(TEST_IPV6_DNS, NUD_STALE)) neighborMonitor.enqueuePacket(makeNewNeighMessage(lostNeighbor, NUD_FAILED)) - verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(lostNeighbor), anyString(), - eq(eventType)) + if (expectedNotifyLost) { + verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(lostNeighbor), anyString(), + eq(eventType)) + } else { + verify(callback, never()).notifyLost(eq(lostNeighbor), anyString(), any()) + } } private fun verifyNudFailureMetrics( @@ -324,6 +350,13 @@ class IpReachabilityMonitorTest { .setNudNeighborType(eq(lostNeighborType)) } + private fun verifyNudFailureMetricsNotReported( + ) { + verify(mIpReachabilityMonitorMetrics, never()).setNudIpType(any()) + verify(mIpReachabilityMonitorMetrics, never()).setNudEventType(any()) + verify(mIpReachabilityMonitorMetrics, never()).setNudNeighborType(any()) + } + // Verify if the notifyLost will be called when one neighbor has lost but it's still // provisioned. private fun runLoseNeighborStillProvisionedTest( @@ -343,12 +376,12 @@ class IpReachabilityMonitorTest { private fun prepareNeighborReachableButMacAddrChangedTest( newLp: LinkProperties, - neighbor: InetAddress + neighbor: InetAddress, + macaddr: String ) { reachabilityMonitor.updateLinkProperties(newLp) - neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE, - "001122334455" /* oldMac */)) + neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE, macaddr)) handlerThread.waitForIdle(TEST_TIMEOUT_MS) verify(callback, never()).notifyLost(eq(neighbor), anyString(), any(NudEventType::class.java)) @@ -376,6 +409,38 @@ class IpReachabilityMonitorTest { NUD_ORGANIC_FAILED_CRITICAL) } + @Test + fun testLoseProvisioning_ignoreOrganicIpv4DnsLost() { + doReturn(true).`when`(dependencies).isFeatureEnabled(any(), + eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION)) + runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV4_DNS, NUD_ORGANIC_FAILED_CRITICAL, + false /* expectedNotifyLost */) + } + + @Test + fun testLoseProvisioning_ignoreOrganicIpv6DnsLost() { + doReturn(true).`when`(dependencies).isFeatureEnabled(any(), + eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION)) + runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_DNS, NUD_ORGANIC_FAILED_CRITICAL, + false /* expectedNotifyLost */) + } + + @Test + fun testLoseProvisioning_ignoreOrganicIpv4GatewayLost() { + doReturn(true).`when`(dependencies).isFeatureEnabled(any(), + eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION)) + runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY, + NUD_ORGANIC_FAILED_CRITICAL, false /* expectedNotifyLost */) + } + + @Test + fun testLoseProvisioning_ignoreOrganicIpv6GatewayLost() { + doReturn(true).`when`(dependencies).isFeatureEnabled(any(), + eq(IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION)) + runLoseProvisioningTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY, + NUD_ORGANIC_FAILED_CRITICAL, false /* expectedNotifyLost */) + } + private fun runNudProbeFailureMetricsTest( lp: LinkProperties, lostNeighbor: InetAddress, @@ -554,47 +619,62 @@ class IpReachabilityMonitorTest { verifyNudFailureMetrics(NUD_CONFIRM_FAILED_CRITICAL, IPV6, NUD_NEIGHBOR_GATEWAY) } - private fun verifyNudMacAddrChangedType( + private fun probeWithNeighborEvent(dueToRoam: Boolean, neighbor: InetAddress, macaddr: String) { + reachabilityMonitor.probeAll(dueToRoam) + neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_PROBE, macaddr)) + } + + private fun verifyNudMacAddrChanged( neighbor: InetAddress, eventType: NudEventType, ipType: IpType ) { - neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE, - "1122334455aa" /* newMac */)) + neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE, TEST_MAC_2)) verify(callback, timeout(TEST_TIMEOUT_MS)).notifyLost(eq(neighbor), anyString(), eq(eventType)) verifyNudFailureMetrics(eventType, ipType, NUD_NEIGHBOR_GATEWAY) } + private fun verifyNudMacAddrChangeNotReported( + neighbor: InetAddress, + ) { + neighborMonitor.enqueuePacket(makeNewNeighMessage(neighbor, NUD_REACHABLE, TEST_MAC_2)) + verify(callback, never()).notifyLost(eq(neighbor), anyString(), any()) + verifyNudFailureMetricsNotReported() + } + @Test fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChangedAfterRoaming() { - prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY) - - reachabilityMonitor.probeAll(true /* dueToRoam */) - verifyNudMacAddrChangedType(TEST_IPV6_GATEWAY, NUD_POST_ROAMING_MAC_ADDRESS_CHANGED, IPV6) + prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY, + TEST_MAC_1) + probeWithNeighborEvent(true /* dueToRoam */, TEST_IPV6_GATEWAY, TEST_MAC_1) + verifyNudMacAddrChanged(TEST_IPV6_GATEWAY, NUD_POST_ROAMING_MAC_ADDRESS_CHANGED, IPV6) } @Test fun testNudProbeFailedMetrics_defaultIPv4GatewayMacAddrChangedAfterRoaming() { - prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY) + prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV4_GATEWAY, + TEST_MAC_1) - reachabilityMonitor.probeAll(true /* dueToRoam */) - verifyNudMacAddrChangedType(TEST_IPV4_GATEWAY, NUD_POST_ROAMING_MAC_ADDRESS_CHANGED, IPV4) + probeWithNeighborEvent(true /* dueToRoam */, TEST_IPV4_GATEWAY, TEST_MAC_1) + verifyNudMacAddrChanged(TEST_IPV4_GATEWAY, NUD_POST_ROAMING_MAC_ADDRESS_CHANGED, IPV4) } @Test fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChangedAfterConfirm() { - prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY) + prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY, + TEST_MAC_1) reachabilityMonitor.probeAll(false /* dueToRoam */) - verifyNudMacAddrChangedType(TEST_IPV6_GATEWAY, NUD_CONFIRM_MAC_ADDRESS_CHANGED, IPV6) + verifyNudMacAddrChangeNotReported(TEST_IPV6_GATEWAY) } @Test fun testNudProbeFailedMetrics_defaultIPv6GatewayMacAddrChangedAfterOrganic() { - prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY) + prepareNeighborReachableButMacAddrChangedTest(TEST_LINK_PROPERTIES, TEST_IPV6_GATEWAY, + TEST_MAC_1) - verifyNudMacAddrChangedType(TEST_IPV6_GATEWAY, NUD_ORGANIC_MAC_ADDRESS_CHANGED, IPV6) + verifyNudMacAddrChangeNotReported(TEST_IPV6_GATEWAY) } @SuppressLint("NewApi") diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java index 77e3a129..43bee559 100644 --- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -19,6 +19,7 @@ package com.android.server.connectivity; import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.net.CaptivePortal.APP_RETURN_DISMISSED; import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import static android.net.DnsResolver.TYPE_A; import static android.net.DnsResolver.TYPE_AAAA; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; @@ -29,6 +30,7 @@ import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; +import static android.net.InetAddresses.parseNumericAddress; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; @@ -64,6 +66,7 @@ import static com.android.networkstack.util.NetworkStackUtils.CAPTIVE_PORTAL_USE import static com.android.networkstack.util.NetworkStackUtils.DEFAULT_CAPTIVE_PORTAL_DNS_PROBE_TIMEOUT; import static com.android.networkstack.util.NetworkStackUtils.DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION; import static com.android.networkstack.util.NetworkStackUtils.REEVALUATE_WHEN_RESUME; +import static com.android.server.connectivity.NetworkMonitor.CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS; import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS; import static com.android.server.connectivity.NetworkMonitor.extractCharset; @@ -123,6 +126,7 @@ import android.net.Network; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkTestResultParcelable; +import android.net.PrivateDnsConfigParcel; import android.net.Uri; import android.net.captiveportal.CaptivePortalProbeResult; import android.net.metrics.IpConnectivityLog; @@ -168,6 +172,7 @@ import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.metrics.DataStallDetectionStats; import com.android.networkstack.metrics.DataStallStatsUtils; import com.android.networkstack.netlink.TcpSocketTracker; +import com.android.networkstack.util.NetworkStackUtils; import com.android.server.NetworkStackService.NetworkStackServiceManager; import com.android.server.connectivity.nano.CellularData; import com.android.server.connectivity.nano.DnsEvent; @@ -214,8 +219,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.net.ssl.SSLHandshakeException; @@ -366,11 +375,11 @@ public class NetworkMonitorTest { class DnsEntry { final String mHostname; final int mType; - final List<InetAddress> mAddresses; - DnsEntry(String host, int type, List<InetAddress> addr) { + final AddressSupplier mAddressesSupplier; + DnsEntry(String host, int type, AddressSupplier addr) { mHostname = host; mType = type; - mAddresses = addr; + mAddressesSupplier = addr; } // Full match or partial match that target host contains the entry hostname to support // random private dns probe hostname. @@ -378,6 +387,21 @@ public class NetworkMonitorTest { return hostname.endsWith(mHostname) && type == mType; } } + interface AddressSupplier { + List<InetAddress> get() throws DnsResolver.DnsException; + } + + class InstantAddressSupplier implements AddressSupplier { + private final List<InetAddress> mAddresses; + InstantAddressSupplier(List<InetAddress> addresses) { + mAddresses = addresses; + } + @Override + public List<InetAddress> get() { + return mAddresses; + } + } + private final ArrayList<DnsEntry> mAnswers = new ArrayList<DnsEntry>(); private boolean mNonBypassPrivateDnsWorking = true; @@ -387,40 +411,76 @@ public class NetworkMonitorTest { } /** Clears all DNS entries. */ - private synchronized void clearAll() { - mAnswers.clear(); + private void clearAll() { + synchronized (mAnswers) { + mAnswers.clear(); + } } /** Returns the answer for a given name and type on the given mock network. */ - private synchronized List<InetAddress> getAnswer(Object mock, String hostname, int type) { - if (mock == mNetwork && !mNonBypassPrivateDnsWorking) { - return null; + private CompletableFuture<List<InetAddress>> getAnswer(Network mockNetwork, String hostname, + int type) { + if (mockNetwork == mNetwork && !mNonBypassPrivateDnsWorking) { + return CompletableFuture.completedFuture(null); } - return mAnswers.stream().filter(e -> e.matches(hostname, type)) - .map(answer -> answer.mAddresses).findFirst().orElse(null); + final AddressSupplier answerSupplier; + + synchronized (mAnswers) { + answerSupplier = mAnswers.stream() + .filter(e -> e.matches(hostname, type)) + .map(answer -> answer.mAddressesSupplier).findFirst().orElse(null); + } + if (answerSupplier == null) { + return CompletableFuture.completedFuture(null); + } + + if (answerSupplier instanceof InstantAddressSupplier) { + // Save latency waiting for a query thread if the answer is hardcoded. + return CompletableFuture.completedFuture( + ((InstantAddressSupplier) answerSupplier).get()); + } + final CompletableFuture<List<InetAddress>> answerFuture = new CompletableFuture<>(); + new Thread(() -> { + try { + answerFuture.complete(answerSupplier.get()); + } catch (DnsResolver.DnsException e) { + answerFuture.completeExceptionally(e); + } + }).start(); + return answerFuture; } /** Sets the answer for a given name and type. */ - private synchronized void setAnswer(String hostname, String[] answer, int type) - throws UnknownHostException { - DnsEntry record = new DnsEntry(hostname, type, generateAnswer(answer)); - // Remove the existing one. - mAnswers.removeIf(entry -> entry.matches(hostname, type)); - // Add or replace a new record. - mAnswers.add(record); + private void setAnswer(String hostname, String[] answer, int type) { + setAnswer(hostname, new InstantAddressSupplier(generateAnswer(answer)), type); + } + + private void setAnswer(String hostname, AddressSupplier answerSupplier, int type) { + DnsEntry record = new DnsEntry(hostname, type, answerSupplier); + synchronized (mAnswers) { + // Remove the existing one. + mAnswers.removeIf(entry -> entry.matches(hostname, type)); + // Add or replace a new record. + mAnswers.add(record); + } } private List<InetAddress> generateAnswer(String[] answer) { if (answer == null) return new ArrayList<>(); - return Arrays.stream(answer).map(addr -> InetAddress.parseNumericAddress(addr)) - .collect(toList()); + return Arrays.stream(answer).map(InetAddresses::parseNumericAddress).collect(toList()); } /** Simulates a getAllByName call for the specified name on the specified mock network. */ - private InetAddress[] getAllByName(Object mock, String hostname) + private InetAddress[] getAllByName(Network mockNetwork, String hostname) throws UnknownHostException { - List<InetAddress> answer = queryAllTypes(mock, hostname); + final List<InetAddress> answer; + try { + answer = queryAllTypes(mockNetwork, hostname).get( + HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new AssertionError("No mock DNS reply within timeout", e); + } if (answer == null || answer.size() == 0) { throw new UnknownHostException(hostname); } @@ -428,57 +488,76 @@ public class NetworkMonitorTest { } // Regardless of the type, depends on what the responses contained in the network. - private List<InetAddress> queryAllTypes(Object mock, String hostname) { - List<InetAddress> answer = new ArrayList<>(); - addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_A)); - addAllIfNotNull(answer, getAnswer(mock, hostname, TYPE_AAAA)); - return answer; - } - - private void addAllIfNotNull(List<InetAddress> list, List<InetAddress> c) { - if (c != null) { - list.addAll(c); + private CompletableFuture<List<InetAddress>> queryAllTypes( + Network mockNetwork, String hostname) { + if (mockNetwork == mNetwork && !mNonBypassPrivateDnsWorking) { + return CompletableFuture.completedFuture(null); } + + final CompletableFuture<List<InetAddress>> aFuture = + getAnswer(mockNetwork, hostname, TYPE_A) + .exceptionally(e -> Collections.emptyList()); + final CompletableFuture<List<InetAddress>> aaaaFuture = + getAnswer(mockNetwork, hostname, TYPE_AAAA) + .exceptionally(e -> Collections.emptyList()); + + final CompletableFuture<List<InetAddress>> combinedFuture = new CompletableFuture<>(); + aFuture.thenAcceptBoth(aaaaFuture, (res1, res2) -> { + final List<InetAddress> answer = new ArrayList<>(); + if (res1 != null) answer.addAll(res1); + if (res2 != null) answer.addAll(res2); + combinedFuture.complete(answer); + }); + return combinedFuture; } /** Starts mocking DNS queries. */ private void startMocking() throws UnknownHostException { // Queries on mNetwork using getAllByName. doAnswer(invocation -> { - return getAllByName(invocation.getMock(), invocation.getArgument(0)); + return getAllByName((Network) invocation.getMock(), invocation.getArgument(0)); }).when(mNetwork).getAllByName(any()); // Queries on mCleartextDnsNetwork using DnsResolver#query. doAnswer(invocation -> { - return mockQuery(invocation, 1 /* posHostname */, 3 /* posExecutor */, - 5 /* posCallback */, -1 /* posType */); + return mockQuery(invocation, 0 /* posNetwork */, 1 /* posHostname */, + 3 /* posExecutor */, 5 /* posCallback */, -1 /* posType */); }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any()); // Queries on mCleartextDnsNetwork using DnsResolver#query with QueryType. doAnswer(invocation -> { - return mockQuery(invocation, 1 /* posHostname */, 4 /* posExecutor */, - 6 /* posCallback */, 2 /* posType */); + return mockQuery(invocation, 0 /* posNetwork */, 1 /* posHostname */, + 4 /* posExecutor */, 6 /* posCallback */, 2 /* posType */); }).when(mDnsResolver).query(any(), any(), anyInt(), anyInt(), any(), any(), any()); } // Mocking queries on DnsResolver#query. - private Answer mockQuery(InvocationOnMock invocation, int posHostname, int posExecutor, - int posCallback, int posType) { + private Answer mockQuery(InvocationOnMock invocation, int posNetwork, int posHostname, + int posExecutor, int posCallback, int posType) { String hostname = (String) invocation.getArgument(posHostname); Executor executor = (Executor) invocation.getArgument(posExecutor); DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(posCallback); - List<InetAddress> answer; - - answer = posType != -1 - ? getAnswer(invocation.getMock(), hostname, invocation.getArgument(posType)) : - queryAllTypes(invocation.getMock(), hostname); - - if (answer != null && answer.size() > 0) { - new Handler(Looper.getMainLooper()).post(() -> { - executor.execute(() -> callback.onAnswer(answer, 0)); - }); - } - // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE. + Network network = invocation.getArgument(posNetwork); + + final CompletableFuture<List<InetAddress>> answerFuture = posType != -1 + ? getAnswer(network, hostname, invocation.getArgument(posType)) + : queryAllTypes(network, hostname); + + answerFuture.whenComplete((answer, exception) -> { + new Handler(Looper.getMainLooper()).post(() -> executor.execute(() -> { + if (exception != null) { + if (!(exception instanceof DnsResolver.DnsException)) { + throw new AssertionError("Test error building DNS response", exception); + } + callback.onError((DnsResolver.DnsException) exception); + return; + } + if (answer != null && answer.size() > 0) { + callback.onAnswer(answer, 0); + } + })); + }); + // If the future does not complete or has no answer do nothing. The timeout should fire. return null; } } @@ -528,6 +607,8 @@ public class NetworkMonitorTest { // it will fail the test because of timeout expired for querying AAAA and A sequentially. doReturn(200).when(mResources) .getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)); + doReturn(200).when(mDependencies).getDeviceConfigPropertyInt( + eq(NAMESPACE_CONNECTIVITY), eq(CONFIG_ASYNC_PRIVDNS_PROBE_TIMEOUT_MS), anyInt()); doAnswer((invocation) -> { URL url = invocation.getArgument(0); @@ -1239,7 +1320,7 @@ public class NetworkMonitorTest { setPortal302(mHttpConnection); final String httpHost = new URL(TEST_HTTP_URL).getHost(); mFakeDns.setAnswer(httpHost, new String[] { "2001:db8::123" }, TYPE_AAAA); - final InetAddress parsedPrivateAddr = InetAddresses.parseNumericAddress(privateAddr); + final InetAddress parsedPrivateAddr = parseNumericAddress(privateAddr); mFakeDns.setAnswer(httpHost, new String[] { privateAddr }, (parsedPrivateAddr instanceof Inet6Address) ? TYPE_AAAA : TYPE_A); } @@ -2199,8 +2280,7 @@ public class NetworkMonitorTest { NETWORK_VALIDATION_RESULT_VALID, 0 /* probesSucceeded */)); } - @Test - public void testPrivateDnsSuccess() throws Exception { + private void runPrivateDnsSuccessTest() throws Exception { setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2232,7 +2312,20 @@ public class NetworkMonitorTest { } @Test - public void testProbeStatusChanged() throws Exception { + public void testPrivateDnsSuccess_SyncDns() throws Exception { + doReturn(false).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + runPrivateDnsSuccessTest(); + } + + @Test + public void testPrivateDnsSuccess_AsyncDns() throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + runPrivateDnsSuccessTest(); + } + + private void runProbeStatusChangedTest() throws Exception { // Set no record in FakeDns and expect validation to fail. setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2255,7 +2348,20 @@ public class NetworkMonitorTest { } @Test - public void testPrivateDnsResolutionRetryUpdate() throws Exception { + public void testProbeStatusChanged_SyncDns() throws Exception { + doReturn(false).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + runProbeStatusChangedTest(); + } + + @Test + public void testProbeStatusChanged_AsyncDns() throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + runProbeStatusChangedTest(); + } + + private void runPrivateDnsResolutionRetryUpdateTest() throws Exception { // Set no record in FakeDns and expect validation to fail. setStatus(mHttpsConnection, 204); setStatus(mHttpConnection, 204); @@ -2300,6 +2406,197 @@ public class NetworkMonitorTest { } @Test + public void testPrivateDnsResolutionRetryUpdate_SyncDns() throws Exception { + doReturn(false).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + runPrivateDnsResolutionRetryUpdateTest(); + } + + @Test + public void testPrivateDnsResolutionRetryUpdate_AsyncDns() throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + runPrivateDnsResolutionRetryUpdateTest(); + } + + @Test + public void testAsyncPrivateDnsResolution_PartialTimeout() throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + + // Only provide AAAA answer + mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA); + + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + + final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel(); + expectedConfig.hostname = "dns.google"; + expectedConfig.ips = new String[] {"2001:db8::1"}; + expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig); + } + + @Test + public void testAsyncPrivateDnsResolution_PartialFailure() throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + + // A succeeds, AAAA fails + mFakeDns.setAnswer("dns.google", new String[]{"192.0.2.123"}, TYPE_A); + mFakeDns.setAnswer("dns.google", () -> { + // DnsResolver.DnsException constructor is T+, so use a mock instead + throw mock(DnsResolver.DnsException.class); + }, TYPE_AAAA); + + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + + final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel(); + expectedConfig.hostname = "dns.google"; + expectedConfig.ips = new String[] {"192.0.2.123"}; + expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig); + } + + @Test + public void testAsyncPrivateDnsResolution_AQuerySucceedsFirst_PrioritizeAAAA() + throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0])); + + final ConditionVariable v4Queried = new ConditionVariable(); + mFakeDns.setAnswer("dns.google", () -> { + v4Queried.open(); + return List.of(parseNumericAddress("192.0.2.123")); + }, TYPE_A); + mFakeDns.setAnswer("dns.google", () -> { + // Make sure the v6 query processing is a bit slower than the v6 one. The small delay + // below still does not guarantee that the v4 query will complete first, but it should + // the large majority of the time, which should be enough to test it. Even if it does + // not, the test should pass. + v4Queried.block(HANDLER_TIMEOUT_MS); + SystemClock.sleep(10L); + return List.of(parseNumericAddress("2001:db8::1"), parseNumericAddress("2001:db8::2")); + }, TYPE_AAAA); + + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + + final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel(); + expectedConfig.hostname = "dns.google"; + // The IPv6 addresses are still first + expectedConfig.ips = new String[] {"2001:db8::1", "2001:db8::2", "192.0.2.123"}; + expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig); + } + + @Test + public void testAsyncPrivateDnsResolution_ConfigChange_RestartsWithNewConfig() + throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("v1.google", new InetAddress[0])); + + final ConditionVariable blockReplies = new ConditionVariable(); + final CountDownLatch queriedLatch = new CountDownLatch(2); + mFakeDns.setAnswer("v1.google", () -> { + queriedLatch.countDown(); + blockReplies.block(HANDLER_TIMEOUT_MS); + return List.of(parseNumericAddress("192.0.2.123")); + }, TYPE_A); + mFakeDns.setAnswer("v1.google", () -> { + queriedLatch.countDown(); + blockReplies.block(HANDLER_TIMEOUT_MS); + return List.of(parseNumericAddress("2001:db8::1")); + }, TYPE_AAAA); + + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + + queriedLatch.await(HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS); + + // Send config update while DNS queries are in flight + mFakeDns.setAnswer("v2.google", new String[] { "192.0.2.124" }, TYPE_A); + mFakeDns.setAnswer("v2.google", new String[] { "2001:db8::2" }, TYPE_AAAA); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("v2.google", new InetAddress[0])); + + // Let the original queries finish. Once DNS queries finish results are posted to the + // handler, so they will be processed on the handler after the DNS settings change. + blockReplies.open(); + + // Expect only callbacks for the 2nd configuration + verifyNetworkTestedValidFromPrivateDns(1 /* interactions */); + + final PrivateDnsConfigParcel expectedConfig = new PrivateDnsConfigParcel(); + expectedConfig.hostname = "v2.google"; + expectedConfig.ips = new String[] {"2001:db8::2", "192.0.2.124"}; + expectedConfig.privateDnsMode = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + + verify(mCallbacks).notifyPrivateDnsConfigResolved(expectedConfig); + } + + @Test + public void testAsyncPrivateDnsResolution_TurnOffStrictMode_SkipsDnsValidation() + throws Exception { + doReturn(true).when(mDependencies).isFeatureEnabled( + any(), eq(NetworkStackUtils.NETWORKMONITOR_ASYNC_PRIVDNS_RESOLUTION)); + setStatus(mHttpsConnection, 204); + setStatus(mHttpConnection, 204); + + WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor(); + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("v1.google", new InetAddress[0])); + + final ConditionVariable blockReplies = new ConditionVariable(); + final CountDownLatch queriedLatch = new CountDownLatch(2); + mFakeDns.setAnswer("v1.google", () -> { + queriedLatch.countDown(); + blockReplies.block(HANDLER_TIMEOUT_MS); + return List.of(parseNumericAddress("192.0.2.123")); + }, TYPE_A); + mFakeDns.setAnswer("v1.google", () -> { + queriedLatch.countDown(); + blockReplies.block(HANDLER_TIMEOUT_MS); + return List.of(parseNumericAddress("2001:db8::1")); + }, TYPE_AAAA); + + notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES); + + queriedLatch.await(HANDLER_TIMEOUT_MS, TimeUnit.MILLISECONDS); + + // Send config update while DNS queries are in flight + wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig(true /* useTls */)); + + // Let the original queries finish. Once DNS queries finish results are posted to the + // handler, so they will be processed on the handler after the DNS settings change. + blockReplies.open(); + + verifyNetworkTestedValidFromHttps(1 /* interactions */); + verify(mCallbacks, never()).notifyPrivateDnsConfigResolved(any()); + } + + @Test public void testReevaluationInterval_networkResume() throws Exception { // Setup nothing and expect validation to fail. doReturn(true).when(mDependencies).isFeatureEnabled(any(), eq(REEVALUATE_WHEN_RESUME)); @@ -2725,8 +3022,8 @@ public class NetworkMonitorTest { } catch (UnknownHostException e) { } - mFakeDns.setAnswer("www.android.com", null, TYPE_A); - mFakeDns.setAnswer("www.android.com", null, TYPE_AAAA); + mFakeDns.setAnswer("www.android.com", (String[]) null, TYPE_A); + mFakeDns.setAnswer("www.android.com", (String[]) null, TYPE_AAAA); try { wnm.sendDnsProbeWithTimeout("www.android.com", shortTimeoutMs); fail("DNS query timed out, expected UnknownHostException"); |