[APFv6 2023-01] Add helper function to match names in DNS packets

Introduces a new helper function called match_name. This function
checks if any names in a given list match the content of a DNS packet.

Bug: 293811969
Test: TH
Change-Id: I55ebe5141477a6b831bd1ecc20bf3a8ec458ad96
diff --git a/v5/apf_interpreter.c b/v5/apf_interpreter.c
index 73eb794..86b3f79 100644
--- a/v5/apf_interpreter.c
+++ b/v5/apf_interpreter.c
@@ -23,6 +23,14 @@
 
 #define TO_UPPER(a) ((a) >= 'a' && (a) <= 'z' ? ((a) - ('a' - 'A')) : (a))
 #define ASSERT_POINTER_IN_BOUND(p, l, r)  if ((p) < (l) || (p) >= (r)) return -1
+#define DECODE_BYTES(value, len, p, l, r)                     \
+    do {                                                      \
+        value = 0;                                            \
+        ASSERT_POINTER_IN_BOUND(((p) + (len) - 1), (l), (r)); \
+        uint32_t i;                                           \
+        for (i = 0; i < (len); i++)                           \
+            value = (value << 8) | *p++;                      \
+    } while (0)
 
 #ifdef __cplusplus
 extern "C" {
@@ -106,6 +114,100 @@
     return -1;
 }
 
+
+
+ /**
+ * Checks if a DNS packet contains any of the target names with the provided
+ * question type.
+ *
+ * @param target_names - TLV encoded names to match against.
+ * @param remain_program_len - Remaining program length.
+ * @param udp_payload - Pointer to the start of the UDP payload (DNS header).
+ * @param udp_payload_len - Length of the UDP payload.
+ * @param question_type - Question type to match against. Use -1 to match answers.
+ *
+ *
+ * @return 1 if matched, 0 if not matched, -1 if an error occurs.
+ */
+int match_name(const uint8_t* const target_names,
+               const uint32_t remain_program_len,
+               const uint8_t* const udp_payload,
+               const uint32_t udp_payload_len,
+               const int question_type) {
+    const uint8_t* src = udp_payload;
+    uint32_t value;
+    // skip tid and flags
+    DECODE_BYTES(value, 4, src, udp_payload, udp_payload + udp_payload_len);
+    uint32_t num_questions;
+    DECODE_BYTES(num_questions, 2, src, udp_payload, udp_payload + udp_payload_len);
+
+    uint32_t num_answers = 0;
+    DECODE_BYTES(value, 2, src, udp_payload, udp_payload + udp_payload_len);
+    num_answers += value;
+    DECODE_BYTES(value, 2, src, udp_payload, udp_payload + udp_payload_len);
+    num_answers += value;
+    DECODE_BYTES(value, 2, src, udp_payload, udp_payload + udp_payload_len);
+    num_answers += value;
+
+    const uint8_t* q = target_names;
+    while (*q != 0) {
+        src = udp_payload + 12;
+        ASSERT_POINTER_IN_BOUND(src, udp_payload, udp_payload + udp_payload_len);
+        uint32_t j;
+        const uint32_t checked_label_size = (uint32_t) (q - target_names);
+        // match questions
+        for (j = 0; j < num_questions; ++j) {
+            int rst = match_labels(q, remain_program_len - checked_label_size,
+                                   udp_payload, udp_payload_len, &src);
+            if (rst == -1) {
+                return -1;
+            }
+            int qtype;
+            // read qtype
+            DECODE_BYTES(qtype, 2, src, udp_payload, udp_payload + udp_payload_len);
+            // skip qclass
+            DECODE_BYTES(value, 2, src, udp_payload, udp_payload + udp_payload_len);
+            if (rst != 1) {
+                continue;
+            }
+            if ((question_type != -1)
+                && (qtype == 0xff /* TYPE_ANY */ || qtype == question_type)) {
+                return 1;
+            }
+        }
+        // match answers
+        for (j = 0; j < num_answers; ++j) {
+            int rst = match_labels(q, remain_program_len - checked_label_size,
+                                   udp_payload, udp_payload_len, &src);
+            if (rst == -1) {
+                return -1;
+            }
+            // skip type, class, ttl
+            DECODE_BYTES(value, 8, src, udp_payload, udp_payload + udp_payload_len);
+            // decode rdata length
+            uint32_t len;
+            DECODE_BYTES(len, 2, src, udp_payload, udp_payload + udp_payload_len);
+            // skip rdata
+            src += len;
+            if (rst == 1) {
+                return rst;
+            }
+        }
+        // move the pointer to the next name.
+        while (*q != 0) {
+            uint32_t len = *q++;
+            if (len < 1 || len > 63) {
+                return -1;
+            }
+            q += len;
+            ASSERT_POINTER_IN_BOUND(q, target_names, target_names + remain_program_len);
+        }
+        q++;
+        ASSERT_POINTER_IN_BOUND(q, target_names, target_names + remain_program_len);
+    }
+    return 0;
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/v5/apf_interpreter_test.cc b/v5/apf_interpreter_test.cc
index 329d724..39eee52 100644
--- a/v5/apf_interpreter_test.cc
+++ b/v5/apf_interpreter_test.cc
@@ -8,6 +8,9 @@
 int match_labels(const uint8_t* const target_name,
                  const int target_name_max_len, const uint8_t* const udp_payload,
                  const int udp_payload_len, const uint8_t** src);
+int match_name(const uint8_t* const target_names, const int remain_program_len,
+               const uint8_t* const udp_payload, const int udp_payload_len,
+               const int question_type);
 
 #ifdef __cplusplus
 }
@@ -97,4 +100,79 @@
   EXPECT_EQ(match_labels(matched_target_name, matched_target_name_len, udp_payload, udp_payload_len, &src), -1);
 }
 
+TEST(ApfInterpreterTest, MatchQuetions) {
+  // target names = { A.B.LOCAL }
+  const uint8_t target_names1[] = {0x01, 0x41, 0x01, 0x42, 0x05, 0x4c,
+                                  0x4f, 0x43, 0x41, 0x4c, 0x00, 0x00};
+  const int target_names_len1 = sizeof(target_names1);
+  const uint8_t udp_payload[] = {
+      0x00, 0x00, 0x00, 0x00,// tid = 0x00, flags = 0x00,
+      0x00, 0x02,            // qdcount = 2
+      0x00, 0x00,            // ancount = 0
+      0x00, 0x00,            // nscount = 0
+      0x00, 0x00,            // arcount = 0
+      0x01, 0x61, 0x01, 0x62,// qname1 = a.b.local
+      0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00,
+      0x00, 0x01, 0x00, 0x01,// type=A, class=0x0001
+      0xc0, 0x0e,                  // qname2 = b.local (name compression)
+      0x00, 0x01, 0x00, 0x01};     // type=A, class=0x0001
+  const int udp_payload_len = sizeof(udp_payload);
+  EXPECT_EQ(match_name(target_names1, target_names_len1, udp_payload,
+                       udp_payload_len, 0x01), 1);
+
+  // target names = { A, B.LOCAL }
+  const uint8_t target_names2[] = {0x01, 0x41, 0x00, 0x01, 0x42, 0x05, 0x4c,
+                                     0x4f, 0x43, 0x41, 0x4c, 0x00, 0x00};
+  const int target_names_len2 = sizeof(target_names2);
+  EXPECT_EQ(match_name(target_names2, target_names_len2, udp_payload,
+                       udp_payload_len, 0x01), 1);
+
+  // target names = { C.LOCAL }
+  const uint8_t target_names3[] = {0x01, 0x43, 0x05, 0x4c, 0x4f,
+                                   0x43, 0x41, 0x4c, 0x00, 0x00};
+  const int target_names_len3 = sizeof(target_names2);
+  EXPECT_EQ(match_name(target_names3, target_names_len3, udp_payload,
+                       udp_payload_len, 0x01), 0);
+}
+
+TEST(ApfInterpreterTest, MatchAnswers) {
+    // target names = { A.B.LOCAL }
+    const uint8_t target_names1[] = {0x01, 0x41, 0x01, 0x42, 0x05, 0x4c,
+                                     0x4f, 0x43, 0x41, 0x4c, 0x00, 0x00};
+    const int target_names_len1 = sizeof(target_names1);
+    const uint8_t udp_payload[] = {
+        0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400,
+        0x00, 0x00, // qdcount = 0
+        0x00, 0x02, // ancount = 2
+        0x00, 0x00, // nscount = 0
+        0x00, 0x00, // arcount = 0
+        0x01, 0x61, 0x01, 0x62, // qname1 = a.b.local
+        0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00,
+        0x00, 0x01, 0x80, 0x01, // type=A, class=0x8001
+        0x00, 0x00, 0x00, 0x78, // ttl = 120
+        0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata=192.168.1.9
+        0xc0, 0x0e, // qname2 = b.local (name compression)
+        0x00, 0x01, 0x80, 0x01, // type=A, class=0x8001
+        0x00, 0x00, 0x00, 0x78, // ttl = 120
+        0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09  // rdlengh = 4, rdata=192.168.1.9
+    };
+    const int udp_payload_len = sizeof(udp_payload);
+    EXPECT_EQ(match_name(target_names1, target_names_len1, udp_payload,
+                         udp_payload_len, -1), 1);
+
+    // target names = { A, B.LOCAL }
+    const uint8_t target_names2[] = {0x01, 0x41, 0x00, 0x01, 0x42, 0x05, 0x4c,
+                                     0x4f, 0x43, 0x41, 0x4c, 0x00, 0x00};
+    const int target_names_len2 = sizeof(target_names2);
+    EXPECT_EQ(match_name(target_names2, target_names_len2, udp_payload,
+                         udp_payload_len, -1), 1);
+
+    // target names = { C.LOCAL }
+    const uint8_t target_names3[] = {0x01, 0x43, 0x05, 0x4c, 0x4f,
+                                     0x43, 0x41, 0x4c, 0x00, 0x00};
+    const int target_names_len3 = sizeof(target_names2);
+    EXPECT_EQ(match_name(target_names3, target_names_len3, udp_payload,
+                         udp_payload_len, -1), 0);
+}
+
 }  // namespace apf