blob: aed9dfd9e948d71b5cd62c09ae5a4c1cd362a654 [file] [log] [blame]
/**
* Compares a (Q)NAME starting at udp[*ofs] with the target name.
*
* @param needle - non-NULL - pointer to DNS encoded target name to match against.
* example: [11]_googlecast[4]_tcp[5]local[0] (where [11] is a byte with value 11)
* @param needle_bound - non-NULL - points at first invalid byte past needle.
* @param udp - non-NULL - pointer to the start of the UDP payload (DNS header).
* @param udp_len - length of the UDP payload.
* @param ofs - non-NULL - pointer to the offset of the beginning of the (Q)NAME.
* On non-error return will be updated to point to the first unread offset,
* ie. the next position after the (Q)NAME.
*
* @return 1 if matched, 0 if not matched, -1 if error in packet, -2 if error in program.
*/
FUNC(match_result_type match_single_name(const u8* needle,
const u8* const needle_bound,
const u8* const udp,
const u32 udp_len,
u32* const ofs)) {
u32 first_unread_offset = *ofs;
bool is_qname_match = true;
int lvl;
/* DNS names are <= 255 characters including terminating 0, since >= 1 char + '.' per level => max. 127 levels */
for (lvl = 1; lvl <= 127; ++lvl) {
u8 v;
if (*ofs >= udp_len) return error_packet;
v = udp[(*ofs)++];
if (v >= 0xC0) { /* RFC 1035 4.1.4 - handle message compression */
u8 w;
u32 new_ofs;
if (*ofs >= udp_len) return error_packet;
w = udp[(*ofs)++];
if (*ofs > first_unread_offset) first_unread_offset = *ofs;
new_ofs = (v - 0xC0) * 256u + w;
if (new_ofs >= *ofs) return error_packet; /* RFC 1035 4.1.4 allows only backward pointers */
*ofs = new_ofs;
} else if (v > 63) {
return error_packet; /* RFC 1035 2.3.4 - label size is 1..63. */
} else if (v) {
u8 label_size = v;
if (*ofs + label_size > udp_len) return error_packet;
if (needle >= needle_bound) return error_program;
if (is_qname_match) {
u8 len = *needle++;
if (len == label_size) {
if (needle + label_size > needle_bound) return error_program;
while (label_size--) {
u8 w = udp[(*ofs)++];
is_qname_match &= (uppercase(w) == *needle++);
}
} else {
if (len != 0xFF) is_qname_match = false;
*ofs += label_size;
}
} else {
is_qname_match = false;
*ofs += label_size;
}
} else { /* reached the end of the name */
if (first_unread_offset > *ofs) *ofs = first_unread_offset;
return (is_qname_match && *needle == 0) ? match : nomatch;
}
}
return error_packet; /* too many dns domain name levels */
}
/**
* Check if DNS packet contains any of the target names with the provided
* question_type.
*
* @param needles - non-NULL - pointer to DNS encoded target nameS to match against.
* example: [3]foo[3]com[0][3]bar[3]net[0][0] -- note ends with an extra NULL byte.
* @param needle_bound - non-NULL - points at first invalid byte past needles.
* @param udp - non-NULL - pointer to the start of the UDP payload (DNS header).
* @param udp_len - length of the UDP payload.
* @param question_type - question type to match against or -1 to match answers.
*
* @return 1 if matched, 0 if not matched, -1 if error in packet, -2 if error in program.
*/
FUNC(match_result_type match_names(const u8* needles,
const u8* const needle_bound,
const u8* const udp,
const u32 udp_len,
const int question_type)) {
u32 num_questions, num_answers;
if (udp_len < 12) return error_packet; /* lack of dns header */
/* dns header: be16 tid, flags, num_{questions,answers,authority,additional} */
num_questions = read_be16(udp + 4);
num_answers = read_be16(udp + 6) + read_be16(udp + 8) + read_be16(udp + 10);
/* loop until we hit final needle, which is a null byte */
while (true) {
u32 i, ofs = 12; /* dns header is 12 bytes */
if (needles >= needle_bound) return error_program;
if (!*needles) return nomatch; /* we've run out of needles without finding a match */
/* match questions */
for (i = 0; i < num_questions; ++i) {
match_result_type m = match_single_name(needles, needle_bound, udp, udp_len, &ofs);
int qtype;
if (m < nomatch) return m;
if (ofs + 2 > udp_len) return error_packet;
qtype = (int)read_be16(udp + ofs);
ofs += 4; /* skip be16 qtype & qclass */
if (question_type == -1) continue;
if (m == nomatch) continue;
if (qtype == 0xFF /* QTYPE_ANY */ || qtype == question_type) return match;
}
/* match answers */
if (question_type == -1) for (i = 0; i < num_answers; ++i) {
match_result_type m = match_single_name(needles, needle_bound, udp, udp_len, &ofs);
if (m < nomatch) return m;
ofs += 8; /* skip be16 type, class & be32 ttl */
if (ofs + 2 > udp_len) return error_packet;
ofs += 2 + read_be16(udp + ofs); /* skip be16 rdata length field, plus length bytes */
if (m == match) return match;
}
/* move needles pointer to the next needle. */
do {
u8 len = *needles++;
if (len == 0xFF) continue;
if (len > 63) return error_program;
needles += len;
if (needles >= needle_bound) return error_program;
} while (*needles);
needles++; /* skip the NULL byte at the end of *a* DNS name */
}
}