summaryrefslogtreecommitdiff
path: root/src/dns.c
blob: f16f5685555600f328bdf48a93777bfae46d168c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <arpa/inet.h>

#include "dns.h"

/* DNS packet:
  +--------+-------+-----------------------------------------------+
  | Offset | Bytes  | Field                                        |
  +--------+--------+----------------------------------------------+
  | 0      | 2      | Transaction ID                               |
  | 2      | 2      | Flags                                        |
  | 4      | 2      | QDCOUNT (Number of questions)                |
  | 6      | 2      | ANCOUNT (Number of answers)                  |
  | 8      | 2      | NSCOUNT (Number of authority RRs)            |
  | 10     | 2      | ARCOUNT (Number of additional RRs)           |
  +--------+--------+----------------------------------------------+
  | 12     | var    | Questions (name + qtype + qclass)            |
  +--------+--------+----------------------------------------------+
  | ??     | var    | Answers (name+type+class+TTL+RDLENGTH+RDATA) |
  +--------+--------+----------------------------------------------+
  | ??     | var    | Authority RRs (same format)                  |
  +--------+--------+----------------------------------------------+
  | ??     | var    | Additional RRs (same format)                 |
  +--------+--------+----------------------------------------------+
*/
size_t parse_dns_udp(const uint8_t *payload,
					 size_t len,
					 struct dns_question *out, size_t max_qs) {
	if (len < 12) {
		// not long enough
		return 0;
	}

	uint16_t qdcount = ntohs(*(uint16_t *)(payload + 4));
	if (qdcount == 0) {
		return 0;
	}
	if (qdcount > max_qs) {
		qdcount = max_qs; // truncate safely
	}

	const uint8_t *ptr = payload + 12;
	const uint8_t *end = payload + len;

	size_t parsed = 0;
	for (int q = 0; q < qdcount && ptr < end; q++) {
		char name[MAX_DNS_NAME_LEN];
		int name_len = 0;

		while (ptr < end && *ptr != 0 && name_len < MAX_DNS_NAME_LEN - 1) {
			uint8_t label_len = *ptr++;

			if (label_len == 0 || ptr + label_len > end) {
				return 0;
			}

			if (name_len > 0) {
				name[name_len++] = '.';
			}

			memcpy(name + name_len, ptr, label_len);
			name_len += label_len;
			ptr += label_len;
		}

		name[name_len] = '\0';
		ptr++; // skip null terminator

		if (ptr + 4 > end) {
			return 0;
		}

		uint16_t qtype = ntohs(*(uint16_t *)(ptr));
		ptr += 2;
		ptr += 2; // qclass

		strncpy(out[parsed].name, name, MAX_DNS_NAME_LEN);
		out[parsed].name[MAX_DNS_NAME_LEN - 1] = '\0';
		out[parsed].qtype = qtype;

		parsed++;
	}

	return parsed;
}

// resolve DNS query type to human-readable string.
// TODO static buffer may yield incorrect resultsif multiple unknown
//      DNS types are encountered in a single message.
const char *dns_type_to_string(uint16_t type) {
	switch(type) {
	case 1:   return "A";
	case 2:   return "NS";
	case 5:   return "CNAME";
	case 6:   return "SOA";
	case 12:  return "PTR";
	case 15:  return "MX";
	case 16:  return "TXT";
	case 28:  return "AAAA";
	case 33:  return "SRV";
	case 64:  return "SVCB"; // RFC 9460 https://www.rfc-editor.org/rfc/rfc9460
	case 65:  return "HTTPS"; // RFC 9460 https://www.rfc-editor.org/rfc/rfc9460
	case 255: return "ANY";
	default: // return TYPEXXXX if type isn't known
		static char buf[16];
		snprintf(buf, sizeof(buf), "TYPE%d", type);
		return buf;
	}
}