Merge changes I5391fedf,I4b49f2c0,I91fd81f5 into main * changes: APFv6.1: addTransmitL4() optimizations APFv6.1: addTransmitWithoutChecksum() Add JBSPTRMATCH support to APFv6.1 generator and ApfFilter
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index e738da4..7c6692f 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -2342,7 +2342,8 @@ .addCountAndDropIfBytesAtR0EqualsNoneOf(allSuffixes, DROPPED_IPV6_NS_OTHER_HOST) .addJump(endOfIpV6DstCheck) .defineLabel(notIpV6SolicitedNodeMcast) - .addCountAndDropIfBytesAtR0EqualsNoneOf(allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST) + .addCountAndDropIfBytesAtOffsetEqualsNoneOf( + IPV6_DEST_ADDR_OFFSET, allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST) .defineLabel(endOfIpV6DstCheck); // Hop limit not 255, NS requires hop limit to be 255 -> drop @@ -2365,10 +2366,10 @@ true, /* includeTentative */ false /* includeAnycast */ ); - v6Gen.addLoadImmediate(R0, ICMP6_NS_TARGET_IP_OFFSET); + if (!tentativeIPv6Addrs.isEmpty()) { - v6Gen.addCountAndPassIfBytesAtR0EqualsAnyOf( - tentativeIPv6Addrs, PASSED_IPV6_ICMP); + v6Gen.addCountAndPassIfBytesAtOffsetEqualsAnyOf( + ICMP6_NS_TARGET_IP_OFFSET, tentativeIPv6Addrs, PASSED_IPV6_ICMP); } final List<byte[]> nonTentativeIpv6Addrs = getIpv6Addresses( @@ -2380,8 +2381,8 @@ v6Gen.addCountAndDrop(DROPPED_IPV6_NS_OTHER_HOST); return; } - v6Gen.addCountAndDropIfBytesAtR0EqualsNoneOf( - nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST); + v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf( + ICMP6_NS_TARGET_IP_OFFSET, nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST); // if source ip is unspecified (::), it's DAD request -> pass v6Gen.addLoadImmediate(R0, IPV6_SRC_ADDR_OFFSET) @@ -3737,6 +3738,21 @@ return gen.programLengthOverEstimate() - gen.getBaseProgramSize(); } + void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException { + final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */, + true /* includeTentative */, true /* includeAnycast */); + final int preloadDataSize = preloadedIPv6Address.size() * 16; + final byte[] preloadData = new byte[preloadDataSize]; + int offset = 0; + for (byte[] addr : preloadedIPv6Address) { + System.arraycopy(addr, 0, preloadData, offset, 16); + offset += 16; + } + if (preloadDataSize > 0) { + gen.addPreloadData(preloadData); + } + } + /** * Generate and install a new filter program. */ @@ -3758,6 +3774,9 @@ try { // Step 1: Determine how many RA filters/mDNS offloads we can fit in the program. ApfV4GeneratorBase<?> gen = createApfGenerator(); + if (gen instanceof ApfV61GeneratorBase<?>) { + preloadData((ApfV61GeneratorBase<?>) gen); + } short labelCheckMdnsQueryPayload = gen.getUniqueLabel(); emitPrologue(gen, labelCheckMdnsQueryPayload);
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java index 212d814..014d893 100644 --- a/src/android/net/apf/ApfV61GeneratorBase.java +++ b/src/android/net/apf/ApfV61GeneratorBase.java
@@ -15,11 +15,13 @@ */ package android.net.apf; +import static android.net.apf.BaseApfGenerator.Rbit.Rbit0; import static android.net.apf.BaseApfGenerator.Rbit.Rbit1; import static android.net.apf.BaseApfGenerator.Register.R0; import androidx.annotation.NonNull; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -205,6 +207,124 @@ return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit1).addUnsigned(imm)); } + @Override + public Type addTransmitWithoutChecksum() { + return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0)); + } + + @Override + protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, + int partialCsum, boolean isUdp) { + if (ipOfs != 14) return false; + int v = -1; + if ( isUdp && csumStart == 26 && csumOfs == 40) v = 0; // ether/ipv4/udp + if (!isUdp && csumStart == 26 && csumOfs == 44) v = 1; // ether/ipv4/tcp + if (!isUdp && csumStart == 34 && csumOfs == 36) v = 2; // ether/ipv4/icmp + if (!isUdp && csumStart == 38 && csumOfs == 40) v = 3; // ether/ipv4/routeralert/icmp + if ( isUdp && csumStart == 22 && csumOfs == 60) v = 4; // ether/ipv6/udp + if (!isUdp && csumStart == 22 && csumOfs == 64) v = 5; // ether/ipv6/tcp + if (!isUdp && csumStart == 22 && csumOfs == 56) v = 6; // ether/ipv6/icmp + if (!isUdp && csumStart == 22 && csumOfs == 64) v = 7; // ether/ipv6/routeralert/icmp + if (v < 0) return false; + v |= partialCsum << 3; + append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0).addUnsigned(v)); + return true; + } + + private List<byte[]> addJumpIfBytesAtOffsetEqualsHelper(int offset, + @NonNull List<byte[]> bytesList, short tgt, boolean jumpOnMatch) + throws IllegalInstructionException { + final List<byte[]> deduplicatedList = + bytesList.size() == 1 ? bytesList : validateDeduplicateBytesList(bytesList); + if (offset < 0 || offset > 255) { + return deduplicatedList; + } + final int count = deduplicatedList.size(); + final int compareLength = deduplicatedList.get(0).length; + if (compareLength > 16) { + return deduplicatedList; + } + final List<byte[]> failbackList = new ArrayList<>(); + final List<Integer> ptrs = new ArrayList<>(); + for (int i = 0; i < count; ++i) { + final byte[] bytes = deduplicatedList.get(i); + int relativeOffset = mInstructions.get(0).findMatchInDataBytes(bytes, 0, bytes.length); + if (relativeOffset < 0 || relativeOffset % 2 == 1 || relativeOffset > 510) { + failbackList.add(bytes); + continue; + } + ptrs.add(relativeOffset / 2); + } + final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0; + int totalPtrs = ptrs.size(); + for (int i = 0; i < totalPtrs; i += 16) { + final int currentCount = Math.min(totalPtrs - i, 16); + final Instruction instruction = new Instruction(Opcodes.JBSPTRMATCH, rbit) + .addU8(offset) + .addU8((currentCount - 1) * 16 + (compareLength - 1)) + .setTargetLabel(tgt); + for (int j = 0; j < currentCount; j++) { + instruction.addU8(ptrs.get(i + j)); + } + append(instruction); + } + return failbackList; + } + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesList}. + */ + public Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt, + true /* jumpOnMatch */); + if (failbackList.isEmpty()) { + return self(); + } + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(failbackList, tgt); + } + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesList}. + */ + public Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt, + false /* jumpOnMatch */); + if (failbackList.isEmpty()) { + return self(); + } + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(failbackList, tgt); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpDropLabel()); + } + + @Override + public Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpDropLabel()); + } + + @Override + public Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel()); + } + /** * 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 @@ -217,4 +337,12 @@ return append(new Instruction(ExtendedOpcodes.JDNSQMATCH2, Rbit1).setTargetLabel(tgt) .addU8(qtype1).addU8(qtype2).setBytesImm(qnames)); } + + /** + * Preload the content of the data region. + */ + public Type addPreloadData(@NonNull byte[] data) throws IllegalInstructionException { + mInstructions.get(0).maybeUpdateBytesImm(data, 0, data.length); + return self(); + } }
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java index cb95fbe..045423b 100644 --- a/src/android/net/apf/ApfV6Generator.java +++ b/src/android/net/apf/ApfV6Generator.java
@@ -234,6 +234,34 @@ } @Override + public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsAnyOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsAnyOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsNoneOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsNoneOf(bytesList, cnt); + } + + @Override public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) { @@ -259,4 +287,15 @@ // Rbit1 means the extra be16 immediate is present return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size)); } + + @Override + public ApfV6Generator addTransmitWithoutChecksum() { + return addTransmit(-1 /* ipOfs */); + } + + @Override + protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, + int partialCsum, boolean isUdp) { + return false; + } }
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java index 00f05c0..68a29c5 100644 --- a/src/android/net/apf/ApfV6GeneratorBase.java +++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -150,9 +150,7 @@ * Add an instruction to the end of the program to transmit the allocated buffer without * checksum. */ - public final Type addTransmitWithoutChecksum() { - return addTransmit(-1 /* ipOfs */); - } + public abstract Type addTransmitWithoutChecksum(); /** * Add an instruction to the end of the program to transmit the allocated buffer. @@ -165,6 +163,8 @@ return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255)); } + protected abstract boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp); + /** * Add an instruction to the end of the program to transmit the allocated buffer. */ @@ -178,6 +178,8 @@ throw new IllegalArgumentException("L4 checksum requires csum offset of " + csumOfs + " < 255"); } + if (handleOptimizedTransmit(ipOfs, csumOfs, csumStart, partialCsum, isUdp)) + return self(); return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0) .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum)); } @@ -394,6 +396,45 @@ short tgt); /** + * Add an instruction to the end of the program to count and drop if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to count and pass if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to count and drop if the bytes of the + * packet at an offset specified by {@code offset} match none the elements in {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to count and pass if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype * equals {@code qtype}. Examines the payload starting at the offset in R0.
diff --git a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt index 03d360f..1c383bc 100644 --- a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt +++ b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
@@ -987,6 +987,95 @@ } @Test + fun testJBSPTRMATCHOpcodeEncoding() { + assumeTrue(apfInterpreterVersion != ApfJniUtils.APF_INTERPRETER_VERSION_V6) + val dataBytes = HexDump.hexStringToByteArray( + "01020304050607080910111213141516171819202122232425262728293031323334353637383940" + ) + val bytes1 = HexDump.hexStringToByteArray("0102") + val bytes2 = HexDump.hexStringToByteArray("0304") + val bytes3 = HexDump.hexStringToByteArray("0506") + val bytes4 = HexDump.hexStringToByteArray("0708") + val bytes5 = HexDump.hexStringToByteArray("0910") + val bytes6 = HexDump.hexStringToByteArray("1112") + val bytes7 = HexDump.hexStringToByteArray("1314") + val bytes8 = HexDump.hexStringToByteArray("1516") + val bytes9 = HexDump.hexStringToByteArray("1718") + val bytes10 = HexDump.hexStringToByteArray("1920") + val bytes11 = HexDump.hexStringToByteArray("2122") + val bytes12 = HexDump.hexStringToByteArray("2324") + val bytes13 = HexDump.hexStringToByteArray("2526") + val bytes14 = HexDump.hexStringToByteArray("2728") + val bytes15 = HexDump.hexStringToByteArray("2930") + val bytes16 = HexDump.hexStringToByteArray("3132") + val bytes17 = HexDump.hexStringToByteArray("3334") + val bytesAtOddIndex = HexDump.hexStringToByteArray("0203") + val notExistBytes = HexDump.hexStringToByteArray("ffff") + val total17BytesList = listOf( + bytes1, + bytes2, + bytes3, + bytes4, + bytes5, + bytes6, + bytes7, + bytes8, + bytes9, + bytes10, + bytes11, + bytes12, + bytes13, + bytes14, + bytes15, + bytes16, + bytes17, + ) + val joinedBytes: ByteArray = total17BytesList.flatMap { it.toList() }.toByteArray() + var program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize) + .addPreloadData(dataBytes) + .addJumpIfBytesAtOffsetEqualsNoneOf(0, listOf(bytes1, notExistBytes), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsNoneOf(2, listOf(notExistBytes), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsAnyOf(3, total17BytesList, PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsNoneOf(4, listOf(bytesAtOddIndex), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsNoneOf(6, listOf(joinedBytes), PASS_LABEL) + .generate() + var debugBufferSize = ramSize - program.size - Counter.totalSize() + assertContentEquals(listOf( + "0: data 40, ${HexDump.toHexString(dataBytes)}", + "43: debugbuf size=$debugBufferSize", + "47: jbsptrne pktofs=0, (2), PASS, @0[0102]", + "52: li r0, 0", + "53: jbsne r0, (2), PASS, ffff", + "58: jbsptreq pktofs=1, (2), PASS, { @0[0102], @2[0304] }[2]", + "64: li r0, 2", + "66: jbsne r0, (2), PASS, ffff", + "71: jbsptreq pktofs=3, (2), PASS, { @0[0102], @2[0304], @4[0506], @6[0708], " + + "@8[0910], @10[1112], @12[1314], @14[1516], @16[1718], @18[1920], @20[2122], " + + "@22[2324], @24[2526], @26[2728], @28[2930], @30[3132] }[16]", + "91: jbsptreq pktofs=3, (2), PASS, @32[3334]", + "96: li r0, 4", + "98: jbsne r0, (2), PASS, 0203", + "103: li r0, 6", + "105: jbsne r0, (34), PASS, ${HexDump.toHexString(joinedBytes)}", + ), apfTestHelpers.disassembleApf(program).map{ it.trim() }) + + val largePrefix = ByteArray(510) { 0 } + program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize) + .addPreloadData(largePrefix + dataBytes) + .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL) + .generate() + debugBufferSize = ramSize - program.size - Counter.totalSize() + assertContentEquals(listOf( + "0: data 550, ${HexDump.toHexString(largePrefix + dataBytes)}", + "553: debugbuf size=$debugBufferSize", + "557: jbsptreq pktofs=1, (2), PASS, @510[0102]", + "562: li r0, 1", + "564: jbseq r0, (2), PASS, 0304", + ), apfTestHelpers.disassembleApf(program).map{ it.trim() }) + } + + @Test fun testPassDrop() { var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize) .addDrop()