-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.go
600 lines (522 loc) · 16.3 KB
/
parser.go
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
package dnsmsg
import (
"errors"
)
var (
// ErrSectionDone is returned by [Parser.Question] and [Parser.ResourceHeader] when
// no more questions/resources are available to parse in the current section.
ErrSectionDone = errors.New("parsing of current section done")
errInvalidOperation = errors.New("invalid operation")
errInvalidDNSMessage = errors.New("invalid dns message")
errInvalidDNSName = errors.New("invalid dns name encoding")
errPtrLoop = errors.New("compression pointer loop")
)
// Parse starts parsing a DNS message.
//
// This function sets the parsing section of the Parser to questions.
func Parse(msg []byte) (Parser, Header, error) {
if len(msg) < headerLen {
return Parser{}, Header{}, errInvalidDNSMessage
}
var hdr Header
hdr.unpack([headerLen]byte(msg[:headerLen]))
return Parser{
msg: msg,
curOffset: headerLen,
remainingQuestions: hdr.QDCount,
remainingAnswers: hdr.ANCount,
remainingAuthorites: hdr.NSCount,
remainingAddtitionals: hdr.ARCount,
}, hdr, nil
}
// Parser is an incremental DNS message parser.
//
// Internally the Parser contains a parsing section field, that can be changed
// using one of these methods: [Parser.StartAnswers], [Parser.StartAuthorities], [Parser.StartAdditionals].
// By default the parsing section is set to questions, this allows parsing the questions section by the
// use of the [Parser.Question] method.
// After changing the parsing section (using one of the Start* methods described above) the [Parser.ResourceHeader]
// method in conjunction with resource parsing methods [Parser.ResourceA], [Parser.ResourceAAAA], [Parser.ResourceNS],
// [Parser.ResourceCNAME], [Parser.ResourceSOA], [Parser.ResourcePTR] [Parser.ResourceMX], [Parser.RawResourceTXT],
// [Parser.SkipResourceData] or [Parser.RDParser] can be used to parse the resource data.
//
// Parser can be copied to preserve the current parsing state.
type Parser struct {
msg []byte
curOffset int
nextResourceDataLength uint16
nextResourceType Type
resourceData bool
curSection section
remainingQuestions uint16
remainingAnswers uint16
remainingAuthorites uint16
remainingAddtitionals uint16
}
// StartAnswers changes the parsing section from questions to answers.
//
// Returns error when the parsing of the current section is not yet completed.
func (p *Parser) StartAnswers() error {
if p.curSection != sectionQuestions || p.resourceData || p.remainingQuestions != 0 {
return errInvalidOperation
}
p.curSection = sectionAnswers
return nil
}
// StartAuthorities changes the parsing section from answers to authorities.
//
// Returns error when the parsing of the current section is not yet completed.
func (p *Parser) StartAuthorities() error {
if p.curSection != sectionAnswers || p.resourceData || p.remainingAnswers != 0 {
return errInvalidOperation
}
p.curSection = sectionAuthorities
return nil
}
// StartAdditionals changes the parsing section from authorities to additionals.
//
// Returns error when the parsing of the current section is not yet completed.
func (p *Parser) StartAdditionals() error {
if p.curSection != sectionAuthorities || p.resourceData || p.remainingAuthorites != 0 {
return errInvalidOperation
}
p.curSection = sectionAdditionals
return nil
}
// SkipQuestions skips all questions.
//
// The parsing section must be set to questions.
func (p *Parser) SkipQuestions() error {
for {
_, err := p.Question()
if err != nil {
if err == ErrSectionDone {
return nil
}
return err
}
}
}
// SkipResources skips all resources in the current parsing section.
//
// The parsing section must not be set to questions.
func (p *Parser) SkipResources() error {
for {
_, err := p.ResourceHeader()
if err != nil {
if err == ErrSectionDone {
return nil
}
return err
}
if err := p.SkipResourceData(); err != nil {
return err
}
}
}
// End should be called after parsing every question and resource.
// It returns an error when there are remaining bytes after the end of the message.
//
// This method should only be called when parsing of all sections is completed, when
// there is nothing left to parse.
func (p *Parser) End() error {
if p.resourceData || p.remainingQuestions != 0 || p.remainingAnswers != 0 ||
p.remainingAuthorites != 0 || p.remainingAddtitionals != 0 {
return errInvalidOperation
}
if len(p.msg) != p.curOffset {
return errInvalidDNSMessage
}
return nil
}
// Question parses a single question.
// Returns [ErrSectionDone] when no more questions are available to parse.
//
// The parsing section must be set to questions.
func (m *Parser) Question() (Question, error) {
if m.curSection != sectionQuestions {
return Question{}, errInvalidOperation
}
if m.remainingQuestions == 0 {
return Question{}, ErrSectionDone
}
var name Name
offset, err := name.unpack(m.msg, m.curOffset)
if err != nil {
return Question{}, err
}
tmpOffset := m.curOffset + int(offset)
if len(m.msg)-tmpOffset < 4 {
return Question{}, errInvalidDNSMessage
}
m.curOffset = tmpOffset + 4
m.remainingQuestions--
return Question{
Name: name,
Type: Type(unpackUint16(m.msg[tmpOffset : tmpOffset+2])),
Class: Class(unpackUint16(m.msg[tmpOffset+2 : tmpOffset+4])),
}, nil
}
// ResourceHeader parses a single header of a resource record.
//
// Every call to ResourceHeader must be followed by a appropriate
// Resource Data parsing method ([Parser.ResourceA], [Parser.ResourceAAAA],
// [Parser.ResourceCNAME], [Parser.ResourceMX], [Parser.RawResourceTXT]) depending
// on the returned [ResourceHeader] Type field or skipped by [Parser.SkipResourceData]
// (even when the [ResourceHeader] Length field is equal to zero).
//
// Returns [ErrSectionDone] when no more resources are available to parse in the
// current section.
//
// The parsing section must not be set to questions.
func (m *Parser) ResourceHeader() (ResourceHeader, error) {
if m.resourceData {
return ResourceHeader{}, errInvalidOperation
}
var count *uint16
switch m.curSection {
case sectionAnswers:
count = &m.remainingAnswers
case sectionAuthorities:
count = &m.remainingAuthorites
case sectionAdditionals:
count = &m.remainingAddtitionals
default:
return ResourceHeader{}, errInvalidOperation
}
if *count == 0 {
return ResourceHeader{}, ErrSectionDone
}
var name Name
offset, err := name.unpack(m.msg, m.curOffset)
if err != nil {
return ResourceHeader{}, err
}
tmpOffset := m.curOffset + int(offset)
if len(m.msg)-tmpOffset < 10 {
return ResourceHeader{}, errInvalidDNSMessage
}
hdr := ResourceHeader{
Name: name,
Type: Type(unpackUint16(m.msg[tmpOffset : tmpOffset+2])),
Class: Class(unpackUint16(m.msg[tmpOffset+2 : tmpOffset+4])),
TTL: unpackUint32(m.msg[tmpOffset+4 : tmpOffset+8]),
Length: unpackUint16(m.msg[tmpOffset+8 : tmpOffset+10]),
}
m.nextResourceDataLength = hdr.Length
m.nextResourceType = hdr.Type
m.resourceData = true
m.curOffset = tmpOffset + 10
*count--
return hdr, nil
}
// ResourceA parses a single A resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeA].
func (m *Parser) ResourceA() (ResourceA, error) {
if !m.resourceData || m.nextResourceType != TypeA {
return ResourceA{}, errInvalidOperation
}
if m.nextResourceDataLength != 4 || len(m.msg)-m.curOffset < 4 {
return ResourceA{}, errInvalidDNSMessage
}
a := [4]byte(m.msg[m.curOffset:])
m.resourceData = false
m.curOffset += 4
return ResourceA{
A: a,
}, nil
}
// ResourceAAAA parses a single AAAA resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeAAAA].
func (m *Parser) ResourceAAAA() (ResourceAAAA, error) {
if !m.resourceData || m.nextResourceType != TypeAAAA {
return ResourceAAAA{}, errInvalidOperation
}
if m.nextResourceDataLength != 16 || len(m.msg)-m.curOffset < 16 {
return ResourceAAAA{}, errInvalidDNSMessage
}
aaaa := [16]byte(m.msg[m.curOffset:])
m.resourceData = false
m.curOffset += 16
return ResourceAAAA{
AAAA: aaaa,
}, nil
}
// ResourceNS parses a single NS resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeNS].
func (m *Parser) ResourceNS() (ResourceNS, error) {
if !m.resourceData || m.nextResourceType != TypeNS {
return ResourceNS{}, errInvalidOperation
}
var ns Name
offset, err := ns.unpack(m.msg, m.curOffset)
if err != nil {
return ResourceNS{}, err
}
if offset != m.nextResourceDataLength {
return ResourceNS{}, errInvalidDNSMessage
}
m.resourceData = false
m.curOffset += int(offset)
return ResourceNS{ns}, nil
}
// ResourceCNAME parses a single CNAME resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeCNAME].
func (m *Parser) ResourceCNAME() (ResourceCNAME, error) {
if !m.resourceData || m.nextResourceType != TypeCNAME {
return ResourceCNAME{}, errInvalidOperation
}
var cname Name
offset, err := cname.unpack(m.msg, m.curOffset)
if err != nil {
return ResourceCNAME{}, err
}
if offset != m.nextResourceDataLength {
return ResourceCNAME{}, errInvalidDNSMessage
}
m.resourceData = false
m.curOffset += int(offset)
return ResourceCNAME{cname}, nil
}
// ResourceSOA parses a single SOA resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeSOA].
func (m *Parser) ResourceSOA() (ResourceSOA, error) {
if !m.resourceData || m.nextResourceType != TypeSOA {
return ResourceSOA{}, errInvalidOperation
}
var ns Name
var mbox Name
tmpOffset := m.curOffset
offset, err := ns.unpack(m.msg, tmpOffset)
if err != nil {
return ResourceSOA{}, err
}
tmpOffset += int(offset)
offset, err = mbox.unpack(m.msg, tmpOffset)
if err != nil {
return ResourceSOA{}, err
}
tmpOffset += int(offset)
if len(m.msg)-tmpOffset < 20 {
return ResourceSOA{}, errInvalidDNSMessage
}
serial := unpackUint32(m.msg[tmpOffset:])
refresh := unpackUint32(m.msg[tmpOffset+4:])
retry := unpackUint32(m.msg[tmpOffset+8:])
expire := unpackUint32(m.msg[tmpOffset+12:])
minimum := unpackUint32(m.msg[tmpOffset+16:])
tmpOffset += 20
if tmpOffset-m.curOffset != int(m.nextResourceDataLength) {
return ResourceSOA{}, errInvalidDNSMessage
}
m.resourceData = false
m.curOffset = tmpOffset
return ResourceSOA{
NS: ns,
Mbox: mbox,
Serial: serial,
Refresh: refresh,
Retry: retry,
Expire: expire,
Minimum: minimum,
}, nil
}
// ResourcePTR parses a single PTR resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypePTR].
func (m *Parser) ResourcePTR() (ResourcePTR, error) {
if !m.resourceData || m.nextResourceType != TypePTR {
return ResourcePTR{}, errInvalidOperation
}
var ptr Name
offset, err := ptr.unpack(m.msg, m.curOffset)
if err != nil {
return ResourcePTR{}, err
}
if offset != m.nextResourceDataLength {
return ResourcePTR{}, errInvalidDNSMessage
}
m.resourceData = false
m.curOffset += int(offset)
return ResourcePTR{ptr}, nil
}
// ResourceMX parses a single MX resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeMX].
func (m *Parser) ResourceMX() (ResourceMX, error) {
if !m.resourceData || m.nextResourceType != TypeMX {
return ResourceMX{}, errInvalidOperation
}
if len(m.msg)-m.curOffset < 2 {
return ResourceMX{}, errInvalidDNSMessage
}
pref := unpackUint16(m.msg[m.curOffset:])
var mx Name
offset, err := mx.unpack(m.msg, m.curOffset+2)
if err != nil {
return ResourceMX{}, err
}
if m.nextResourceDataLength != offset+2 {
return ResourceMX{}, errInvalidDNSMessage
}
m.resourceData = false
m.curOffset += int(m.nextResourceDataLength)
return ResourceMX{
Pref: pref,
MX: mx,
}, nil
}
// RawResourceTXT parses a single TXT resouce data.
//
// This method can only be used after [Parser.ResourceHeader]
// returns a [ResourceHeader] with a Type field equal to [TypeTXT].
func (m *Parser) RawResourceTXT() (RawResourceTXT, error) {
if !m.resourceData || m.nextResourceType != TypeTXT {
return RawResourceTXT{}, errInvalidOperation
}
if len(m.msg)-m.curOffset < int(m.nextResourceDataLength) {
return RawResourceTXT{}, errInvalidDNSMessage
}
r := RawResourceTXT{m.msg[m.curOffset : m.curOffset+int(m.nextResourceDataLength)]}
if !r.isValid() {
return RawResourceTXT{}, errInvalidDNSMessage
}
m.resourceData = false
m.curOffset += int(m.nextResourceDataLength)
return r, nil
}
// SkipResourceData skips the resource data, without parsing it in any way.
//
// This method can only be called after calling the [Parser.ResourceHeader] method.
func (m *Parser) SkipResourceData() error {
if !m.resourceData {
return errInvalidOperation
}
if len(m.msg)-m.curOffset < int(m.nextResourceDataLength) {
return errInvalidDNSMessage
}
m.curOffset += int(m.nextResourceDataLength)
m.resourceData = false
return nil
}
// RDParser is a resource data parser used to parse custom resources.
type RDParser struct {
m *Parser
offset int
maxOffset int
}
// Length returns the remaining bytes in the resource data.
func (p *RDParser) Length() uint16 {
return uint16(p.maxOffset - p.offset)
}
// End checks if there is any remaining data in the resource data being parsed.
// It is used to ensure that the entire resource data has been successfully parsed and
// no unexpected data remains.
func (p *RDParser) End() error {
if p.Length() == 0 {
return nil
}
return errInvalidDNSMessage
}
// Name parses a single DNS name.
func (p *RDParser) Name() (Name, error) {
var n Name
offset, err := n.unpack(p.m.msg, p.offset)
if err != nil {
return Name{}, err
}
if p.offset+int(offset) > p.maxOffset {
return Name{}, errInvalidDNSMessage
}
p.offset += int(offset)
return n, nil
}
// AllBytes returns all remaining bytes in p.
// The length of the byte slice is equal to [RDParser.Length].
//
// The returned slice references the underlying message pased to [Parse].
func (p *RDParser) AllBytes() []byte {
offset := p.offset
p.offset = p.maxOffset
return p.m.msg[offset:p.maxOffset]
}
// Bytes returns a n-length slice, errors when [RDParser.Length} < n.
//
// The returned slice references the underlying message pased to [Parse].
func (p *RDParser) Bytes(n int) ([]byte, error) {
if p.offset+n > p.maxOffset {
return nil, errInvalidDNSMessage
}
offset := p.offset
p.offset += n
return p.m.msg[offset:p.offset], nil
}
// Uint8 parses a single uint8 value.
// It requires at least one byte to be available in the RDParser to successfully parse.
func (p *RDParser) Uint8() (uint8, error) {
if p.offset+1 > p.maxOffset {
return 0, errInvalidDNSMessage
}
offset := p.offset
p.offset++
return p.m.msg[offset], nil
}
// Uint16 parses a single Big-Endian uint16 value.
// It requires at least two bytes to be available in the RDParser to successfully parse.
func (p *RDParser) Uint16() (uint16, error) {
if p.offset+2 > p.maxOffset {
return 0, errInvalidDNSMessage
}
offset := p.offset
p.offset += 2
return unpackUint16(p.m.msg[offset:]), nil
}
// Uint32 parses a single Big-Endian uint32 value.
// It requires at least four bytes to be available in the RDParser to successfully parse.
func (p *RDParser) Uint32() (uint32, error) {
if p.offset+4 > p.maxOffset {
return 0, errInvalidDNSMessage
}
offset := p.offset
p.offset += 4
return unpackUint32(p.m.msg[offset:]), nil
}
// Uint64 parses a single Big-Endian uint64 value.
// It requires at least eight bytes to be available in the RDParser to successfully parse.
func (p *RDParser) Uint64() (uint64, error) {
if p.offset+8 > p.maxOffset {
return 0, errInvalidDNSMessage
}
offset := p.offset
p.offset += 8
return unpackUint64(p.m.msg[offset:]), nil
}
// RDParser craeates a new [RDParser], used for parsing custom resource data.
func (m *Parser) RDParser() (RDParser, error) {
if !m.resourceData {
return RDParser{}, errInvalidOperation
}
if len(m.msg)-m.curOffset < int(m.nextResourceDataLength) {
return RDParser{}, errInvalidDNSMessage
}
offset := m.curOffset
m.curOffset += int(m.nextResourceDataLength)
m.resourceData = false
return RDParser{
m: m,
offset: offset,
maxOffset: m.curOffset,
}, nil
}