-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdns_packet.ts
231 lines (191 loc) · 6.39 KB
/
dns_packet.ts
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import { hex } from "./utils.ts";
import { DNSRecordType, ResourceRecord } from "./dns_record_type.ts";
import { DNSRecordClass } from "./dns_record_class.ts";
// Handy picture: https://www.securityartwork.es/wp-content/uploads/2013/02/DNS.jpg
/** DNS Packet Header. */
export class DNSHeader {
Identification = 0;
Flags = 0;
TotalQuestions = 0;
TotalAnswers = 0;
TotalAuthorityResourceRecords = 0;
TotalAdditionalResourceRecords = 0;
public toString(): string {
return `
Identification: ${hex(this.Identification)}
Flags: ${hex(this.Flags)}
Total Questions: ${hex(this.TotalQuestions)}
Total Answers: ${hex(this.TotalAnswers)}
Total Auth RR: ${hex(this.TotalAuthorityResourceRecords)}
Total Additional RR: ${hex(this.TotalAdditionalResourceRecords)}`;
}
/** Get the protocol bytes for the header. */
get Bytes(): Uint8Array {
const result = new Uint8Array(12);
const view = new DataView(result.buffer);
view.setUint16(0, this.Identification);
view.setUint16(2, this.Flags);
view.setUint16(4, this.TotalQuestions);
view.setUint16(6, this.TotalAnswers);
view.setUint16(8, this.TotalAuthorityResourceRecords);
view.setUint16(10, this.TotalAdditionalResourceRecords);
return result;
}
/** Parse the DNS header out of the raw packet bytes. */
static Parse (data: DataView): DNSHeader {
const header = new DNSHeader();
header.Identification = data.getInt16(0);
header.Flags = data.getInt16(2);
header.TotalQuestions = data.getInt16(4);
header.TotalAnswers = data.getInt16(6);
header.TotalAuthorityResourceRecords = data.getInt16(8);
header.TotalAdditionalResourceRecords = data.getInt16(10);
return header;
}
}
/** A DNS Packet's Question. */
export class DNSQuestion {
/** The human-friendly name */
Name = "";
/** The separate parts of the name. */
NameParts: string[] = [];
/** The Record Type (e.g. A, AAAA etc). */
RecordType = 0;
/** The Record Class - typically only IN. */
RecordClass = 0;
constructor(name = "", type = DNSRecordType.A, cls = DNSRecordClass.IN) {
if (name === "") return;
this.Name = name;
this.NameParts = name.split('.');
this.RecordType = type;
this.RecordClass = cls;
}
public toString() {
const recordType = DNSRecordType[this.RecordType];
const recordClass = DNSRecordClass[this.RecordClass];
return `Name: ${this.Name} Type: ${recordType} Class: ${recordClass}`;
}
/** Get the protocol bytes for the question. */
get Bytes(): Uint8Array {
const result = new Uint8Array(this.Name.length + 6);
const view = new DataView(result.buffer);
let index = 0;
for (const part of this.NameParts) {
view.setUint8(index, part.length);
for (let i = 0; i < part.length; i++) {
view.setUint8(index + 1 + i, part.charCodeAt(i));
}
index = index + 1 + part.length;
}
view.setUint16(index += 1, this.RecordType);
view.setUint16(index += 2, this.RecordClass);
return result;
}
/** Parse the DNS question out of the raw packet bytes. */
static Parse(data: DataView): DNSQuestion {
const question = new DNSQuestion();
let index = 12; // DNS header is always 12 bytes
// Questions always contain the name split into separate parts, with a
// leading byte per part indicating its length. A 0x00 byte indicates the
// end of the name section.
//
// E.g. www.example.com ends up as:
// Size Name Part
// 0x03 www
// 0x07 example
// 0x03 com
// 0x00
let length = data.getUint8(index);
while (length != 0) {
const labelPart = new Uint8Array(data.buffer, index + 1, length);
const labelPartString = String.fromCharCode.apply(
null,
Array.from(labelPart),
);
question.NameParts.push(labelPartString);
index += length + 1;
length = data.getUint8(index);
}
question.Name = question.NameParts.join(".");
question.RecordType = data.getUint16(index += 1);
question.RecordClass = data.getUint16(index += 2);
return question;
}
}
/** Represents a DNS packet. */
export class DNSPacket {
/** Copy of the raw data. */
private rawData!: Uint8Array;
/** Data view onto the raw data. */
private data!: DataView;
/** Private copy of the header. */
private header!: DNSHeader;
/** Private copy of the question. */
private question!: DNSQuestion;
/** Private copy of the answer (there may not be an answer). */
private answers: ResourceRecord[] = [];
/** Get the header for this packet. */
get Header(): DNSHeader {
return this.header;
}
/** Get the question for this packet. */
get Question(): DNSQuestion {
return this.question;
}
/** Sets the question for this packet. */
set Question(question:DNSQuestion) {
this.question = question;
this.Header.TotalQuestions++;
}
/** Get the answer for this packet, if available. */
get Answers(): ResourceRecord[] {
return this.answers;
}
/** Sets the answer for this packet. */
set Answers(answers:ResourceRecord[]) {
this.answers = answers;
this.Header.TotalAnswers++;
}
/**
* Get the protocol bytes for this packet. Set any packet fields before
* calling.
*/
get Bytes(): Uint8Array {
const header = this.Header?.Bytes;
const question = this.Question?.Bytes;
if (!header || !question) {
console.warn('Potentially invalid DNSPacket - missing header or question section');
return new Uint8Array();
}
const parts = [header, question];
let length = header.length + question.length;
for (const answer of this.Answers) {
const bytes = answer.Bytes;
length += bytes.length;
parts.push(bytes);
}
const result = new Uint8Array(length);
let offset = 0;
for (const array of parts) {
result.set(array, offset);
offset += array.length;
}
return result;
}
constructor() {
this.header = new DNSHeader();
this.question = new DNSQuestion();
}
/**
* Construct a new DNSPacket from the provided UInt8Array byte array. Use this to convert
* data from the network into a DNSPacket.
*/
static fromBytes(data:Uint8Array): DNSPacket {
const packet = new DNSPacket();
packet.rawData = data;
packet.data = new DataView(data.buffer);
packet.header = DNSHeader.Parse(packet.data);
packet.question = DNSQuestion.Parse(packet.data);
return packet;
}
}