Add JDNSAMATCH opcode support to ApfGenerator
* design doc: go/apf-v6-proposal
Bug: 293811969
Test: TH
Change-Id: I9171386442c0ed9a7be76afcac3de7e63a005247
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java
index 05b6484..6346a02 100644
--- a/src/android/net/apf/ApfGenerator.java
+++ b/src/android/net/apf/ApfGenerator.java
@@ -136,15 +136,25 @@
// e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1"
EPKTCOPY(41),
EDATACOPY(42),
- // Jumps if the UDP payload content (starting at R0) does not contain the specified QNAME,
- // applying MDNS case insensitivity.
+ // Jumps if the UDP payload content (starting at R0) does not contain ont
+ // of the specified QNAME, applying case insensitivity.
// R0: Offset to UDP payload content
+ // R=0/1 meanining 'does not match' vs '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);
+ JDNSQMATCH(43), // Jumps if the UDP payload content (starting at R0) does not contain one
+ // of the specified NAME in answers/authority/additional records, applying
+ // case insensitivity.
+ // R=0/1 meanining 'does not match' vs '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;
@@ -1218,16 +1228,16 @@
return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_';
}
- private static void validateQnames(@NonNull byte[] qnames) {
- final int len = qnames.length;
+ 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(qnames)
+ 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 = qnames[i++];
+ 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");
@@ -1236,16 +1246,16 @@
throw new IllegalArgumentException(errorMessage);
}
while (label_len-- > 0) {
- if (!isValidDnsCharacter(qnames[i++])) {
- throw new IllegalArgumentException("qname: " + HexDump.toHexString(qnames)
+ if (!isValidDnsCharacter(names[i++])) {
+ throw new IllegalArgumentException("qname: " + HexDump.toHexString(names)
+ " contains invalid character");
}
}
- if (qnames[i] == 0) {
+ if (names[i] == 0) {
i++; // skip null terminator.
}
}
- if (qnames[len - 1] != 0) {
+ if (names[len - 1] != 0) {
throw new IllegalArgumentException(errorMessage);
}
}
@@ -1259,7 +1269,7 @@
public ApfGenerator addJumpIfPktAtR0DoesNotContainDnsQ(@NonNull byte[] qnames, int qtype,
@NonNull String tgt) throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- validateQnames(qnames);
+ validateNames(qnames);
return append(new Instruction(ExtendedOpcodes.JDNSQMATCH).setTargetLabel(tgt).addU8(
qtype).setBytesImm(qnames));
}
@@ -1273,11 +1283,39 @@
public ApfGenerator addJumpIfPktAtR0ContainDnsQ(@NonNull byte[] qnames, int qtype,
@NonNull String tgt) throws IllegalInstructionException {
requireApfVersion(MIN_APF_VERSION_IN_DEV);
- validateQnames(qnames);
+ 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 NAME
+ * specified in {@code Names}. Examines the payload starting at the offset in R0.
+ * R = 0 means check for "does not contain".
+ */
+ public ApfGenerator 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 NAME
+ * specified in {@code Names}. Examines the payload starting at the offset in R0.
+ * R = 1 means check for "contain".
+ */
+ public ApfGenerator 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) {
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt
index c02ede9..162feef 100644
--- a/tests/unit/src/android/net/apf/ApfV5Test.kt
+++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -65,6 +65,10 @@
byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfGenerator.DROP_LABEL) }
assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsQ(
byteArrayOf(1, 'A'.code.toByte()), 0x0c, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalInstructionException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
}
@Test
@@ -127,6 +131,40 @@
assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ(
byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
0xc0, ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'a'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, '.'.code.toByte(), 0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(0, 0), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte()), ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0),
+ ApfGenerator.DROP_LABEL) }
+ assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA(
+ byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()),
+ ApfGenerator.DROP_LABEL) }
}
@Test
@@ -307,6 +345,16 @@
) + qnames + byteArrayOf(
encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(),
) + qnames, program)
+
+ gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+ gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfGenerator.DROP_LABEL)
+ gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfGenerator.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 {