-
Notifications
You must be signed in to change notification settings - Fork 2
/
dns.go
624 lines (551 loc) · 19.7 KB
/
dns.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
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
package main
import (
"fmt"
"net"
"strings"
"time"
"github.com/markdingo/miekgrrl"
"github.com/markdingo/rrl"
"github.com/miekg/dns"
"github.com/markdingo/autoreverse/dnsutil"
"github.com/markdingo/autoreverse/log"
)
// Many serve* functions either serve their answer or return a pending result as they do
// not know whether the caller has other options available to them. serveResult conveys
// that info back to the caller.
type serveResult int
const (
serveDone serveResult = iota // Caller concludes request processing
NoError // Caller calls serveNoError() if no options remain
NXDomain // Caller calls serveNxDomain() if no options remain
FormErr // Caller calls serveFormErr()
)
func (t *serveResult) String() string {
switch *t {
case serveDone:
return "serveDone"
case NoError:
return "NoError"
case NXDomain:
return "NXDomain"
case FormErr:
return "FormErr"
}
return fmt.Sprintf("?? sr %d", *t)
}
// Called from miekg - handles all DNS queries. All query logic is embedded in this one
// rather large function.
func (t *server) ServeDNS(wtr dns.ResponseWriter, query *dns.Msg) {
req := newRequest(query, wtr.RemoteAddr(), t.network)
req.stats.gen.queries++
if t.cfg.logQueriesFlag {
defer req.log()
}
defer t.addStats(&req.stats) // Add req.stats to t.stats
// Validate query. Extra can have EDNS options so don't length check that slice.
// As of RFC7873#5.4 a query with no questions and a well formed COOKIE OPT.
//
// miekg.DefaultMsgAcceptFunc does some checking prior to the query arriving here,
// but we are slightly more paranoid.
if len(req.query.Question) > 0 {
req.question = req.query.Question[0] // Populate early for logger
req.qName = strings.ToLower(req.question.Name) // Normalize
req.logQName = req.qName // Can override
}
req.opt = req.query.IsEdns0() // Extract Opt values nice and early
if (len(t.cfg.nsid) > 0) && (req.findNSID() != nil) {
req.nsidOut = t.cfg.nsidAsHex
req.stats.gen.nsid++
}
// Check for valid cookies and generate a server cookie if requested. A valid
// inbound server cookie bypasses RRL test in WriteMsg().
req.findCookies()
if req.cookiesPresent {
req.stats.gen.cookie++
if !req.cookieWellFormed { // Specifically this means the OPT is malformed
t.serveFormErr(wtr, req)
req.addNote("Malformed cookie")
req.stats.gen.malformedCookie++
return
}
req.validateOrGenerateCookie(t.cookieSecrets, time.Now().Unix())
if !req.cookieValid {
if len(req.serverCookie) > 0 {
req.addNote("Server cookie mismatch")
req.stats.gen.wrongCookie++
}
}
}
// Is this a "Query for a Server Cookie"?
if len(req.clientCookie) > 0 && len(req.serverCookie) == 0 && len(req.query.Question) == 0 {
req.response.SetReply(query)
t.writeMsg(wtr, req)
req.addNote("Query Server Cookie")
req.stats.gen.cookieOnly++
return
}
// Subsequent to the weird non-request cookie-request, we only accept "normal"
// queries. Pretty much all of the following tests are performed by miekg prior to
// calling ServeDNS(), but precisely what validation will be performed, is
// undocumented and perhaps may vary over time thus the "belts and braces"
// approach.
if len(req.query.Question) != 1 ||
len(req.query.Answer) != 0 ||
len(req.query.Ns) != 0 ||
req.query.Opcode != dns.OpcodeQuery {
t.serveFormErr(wtr, req)
req.addNote("Malformed Query")
req.stats.gen.badRequest++
return
}
// If query contains a UDP size value, use it if it's reasonable
if t.network == dnsutil.UDPNetwork {
req.maxSize = dnsutil.MaxUDPSize // Default unless over-ridden
if req.opt != nil {
mz := req.opt.UDPSize()
if (mz > 512) && (mz <= dnsutil.MaxUDPSize) { // Reasonable?
req.maxSize = mz
}
}
}
req.db = t.dbGetter.Current() // Final setup for request prior to dispatching
req.mutables = t.getMutables() // Get current mutables from server instance
// Pre-processing complete. Dispatch order:
//
// 1. Probe
// 2. Chaos via database
// 3. In-domain or Passthru
// 4. Not ClassINET
// 5. Special Authority Queries (SOA, NS, ANY)
// 6. Database
// 7. Synthesis
// 8. Pending serveResult
//
// Dispatch 1. Probe
// Probes can be sent multiple times and this function responds possitively each
// time. Whether probes are oneshot or multishot process is determined by probe
// senders not probe receivers. And probe senders do that be modifying the
// mutables.
//
// It's important that if the probe doesn't match, the query continues on with the
// regular query processing as a partially established autoreverse instance may be
// necessary to answer forward queries while working out authority for the reverse
// zone. This is particularly likely when the forward and reverse are serviced by
// the same name server - which is expected to be common in the autoreverse case.
if req.probe != nil {
if req.probe.QuestionMatches(req.question) {
log.Minor("Valid Probe received from ", req.src)
req.addNote("Probe match")
req.response.SetReply(req.query)
req.response.Answer = append(req.response.Answer, req.probe.Answer())
t.writeMsg(wtr, req)
return
}
req.addNote("Non-probe query during probe")
}
// Dispatch 2. Chaos via database
// nsd returns "Refused" for any non-matching CHAOS. I'm not sure I agree with
// this since our CHAOS RRs are in a hierarchy database. But it's such an
// edge-case that for now I'll mostly go along with the group-think. Another
// factor is that we have not Zone Of Authority and thus no SOA so other responses
// such as NoError cannot be generated properly, so "Refused" is our
// get-out-of-jail card.
if t.cfg.chaosFlag && req.question.Qclass == dns.ClassCHAOS {
req.stats.gen.chaos++
if t.serveDatabase(wtr, req) != serveDone {
t.serveRefused(wtr, req)
req.stats.gen.chaosRefused++
}
return
}
// Dispatch 3. In-domain or Passthru
req.setAuthority()
if req.auth == nil { // One of our domains?
if len(t.cfg.passthru) > 0 { // Nope - do we passthru?
t.passthru(wtr, req)
return
}
req.addNote("not in-domain")
t.serveRefused(wtr, req)
req.stats.gen.noAuthority++
return
}
// Dispatch 4. Not ClassINET
if req.question.Qclass != dns.ClassINET {
t.serveRefused(wtr, req)
req.addNote(fmt.Sprintf("Wrong class %s",
dnsutil.ClassToString(dns.Class(req.question.Qclass))))
req.stats.gen.wrongClass++
return
}
// Dispatch 5. Special Authority Queries
// Handle queries which require special treatment such as populating Extra or
// Authority RRs or oddball qTypes. Otherwise fall thru to try serveDatabase which
// can server all regular RRs for the Authority Zone.
if req.qName == req.auth.Domain {
switch req.question.Qtype {
case dns.TypeANY:
req.response.SetRcode(req.query, dns.RcodeSuccess)
req.response.Answer = append(req.response.Answer, &req.auth.SOA)
req.stats.gen.authZoneANY++
t.writeMsg(wtr, req)
return
case dns.TypeSOA:
req.response.SetRcode(req.query, dns.RcodeSuccess)
req.response.Answer = append(req.response.Answer, &req.auth.SOA)
req.response.Ns = append(req.response.Ns, req.auth.NS...)
req.response.Extra = append(req.response.Extra, req.auth.A...)
req.response.Extra = append(req.response.Extra, req.auth.AAAA...)
req.stats.gen.authZoneSOA++
t.writeMsg(wtr, req)
return
case dns.TypeNS:
req.response.SetRcode(req.query, dns.RcodeSuccess)
req.response.Answer = append(req.response.Ns, req.auth.NS...)
req.response.Extra = append(req.response.Extra, req.auth.A...)
req.response.Extra = append(req.response.Extra, req.auth.AAAA...)
req.stats.gen.authZoneNS++
t.writeMsg(wtr, req)
return
}
}
// Dispatch 6. Database - remember result in case synthesis is not enabled
pending := t.serveDatabase(wtr, req)
switch pending {
case serveDone:
req.addNote("DB")
if len(req.response.Answer) >= 1 { // Log first label of PTRs found in the DB
if prr, ok := req.response.Answer[0].(*dns.PTR); ok {
ar := strings.SplitN(prr.Ptr, ".", 2)
if len(ar) > 0 { // which it always should be
req.addNote(ar[0])
}
}
}
req.stats.gen.dbDone++
return
case NoError:
req.stats.gen.dbNoError++
case NXDomain:
req.stats.gen.dbNXDomain++
case FormErr:
req.stats.gen.dbFormErr++
}
// Dispatch 7. Synthesis.
// If synthesize is allowed the pending results of the previous call to
// serveDatabase() are overridden, otherwise they'll stand. Synthesis is only
// allowed if configured *and* if the qName is a child of the Authority.
//
// Regardless of the outcome, from an RRL perspective the origin name now needs to
// be set to indicate a synthentic name below the authoritative domain.
req.rrlOriginName = "*." + req.auth.Domain
if t.cfg.synthesizeFlag && len(req.qName) > len(req.auth.Domain) {
if req.auth.forward {
pending = t.serveForward(wtr, req)
req.stats.gen.synthForward++
} else {
pending = t.serveReverse(wtr, req)
req.stats.gen.synthReverse++
}
switch pending {
case serveDone:
req.stats.gen.synthDone++
case NoError:
req.stats.gen.synthNoError++
case NXDomain:
req.stats.gen.synthNXDomain++
case FormErr:
req.stats.gen.synthFormErr++
}
} else {
req.addNote("No Synth")
req.stats.gen.noSynth++
}
// Dispatch 8. Pending serveResult
switch pending {
case NoError:
t.serveNoError(wtr, req)
case NXDomain:
t.serveNXDomain(wtr, req)
case FormErr:
t.serveFormErr(wtr, req)
}
}
func (t *server) serveNoError(wtr dns.ResponseWriter, req *request) {
req.response.SetRcode(req.query, dns.RcodeSuccess)
req.response.Ns = append(req.response.Ns, &req.auth.SOA)
t.writeMsg(wtr, req)
}
// I don't know why miekg has a specific function for FormErr and a generic one for all
// other returns, but I'll use the specific one just in case there's a good reason beyond
// being an historical artifact.
func (t *server) serveFormErr(wtr dns.ResponseWriter, req *request) {
req.response.SetRcodeFormatError(req.query)
t.writeMsg(wtr, req)
}
func (t *server) serveNXDomain(wtr dns.ResponseWriter, req *request) {
req.response.SetRcode(req.query, dns.RcodeNameError)
req.response.Ns = append(req.response.Ns, &req.auth.SOA)
t.writeMsg(wtr, req)
}
func (t *server) serveRefused(wtr dns.ResponseWriter, req *request) {
req.response.SetRcode(req.query, dns.RcodeRefused)
t.writeMsg(wtr, req)
}
func (t *server) serveDatabase(wtr dns.ResponseWriter, req *request) serveResult {
ar, nx := req.db.LookupRR(req.question.Qclass, req.question.Qtype, req.qName)
if len(ar) == 0 {
if nx {
return NXDomain
}
return NoError
}
req.response.SetReply(req.query)
for _, rr := range ar {
if t.cfg.maxAnswers <= 0 || len(req.response.Answer) < t.cfg.maxAnswers {
if rr.Header().Ttl == 0 {
rr.Header().Ttl = t.cfg.TTLAsSecs
}
req.response.Answer = append(req.response.Answer, rr)
}
}
// Truncate msg to fit max size. Only relevant if connection is UDP.
if req.maxSize > 0 {
req.response.Truncate(int(req.maxSize)) // Removes excess RRs and sets TC=1
}
t.writeMsg(wtr, req)
return serveDone
}
// The qName is in a known forward domain and given we're called after the database
// lookup, that means the query can only legitimately be an address query of a reverse of
// a synthesized PTR and thus should be something like:
//
// dig -t $qType fd2d-ffff-1234-fe--1.$forward // fd2d:ffff:1234:fe::/64
// dig -t $qType 192-168-0-123.$forward // 192.168.0.0/24
// dig -t $qType 192-168--1.$forward // 192:168::/64
//
// The IP address needs to be extract from the qName and checked against our reverse
// authorities to ensure it's an IP address that we could have concievably generated a
// PTR. Note that because this is all algorithmic, all legitimate IP addresses can be
// queried against the forward authorities at any time.
//
// To extract the IP address we need to first know whether it's an ipv4 or ipv6 address.
// It's subtle but ipv4 qNames have a unique pattern because they don't compress zero
// octets, unlike ipv6. Specifically, well formed ipv4 forwards are always four non-zero
// length decimals separated by '-'. Anything else has to either be an ipv6 address or
// invalid. We take advantage of this distinction to determine how to dispatch to the
// appropriate handler.
//
// This convolution is necessary because we need to distinguish between NoError and
// NXDomain. The naive (and wrong) approach is to use the $qType to decide how to decode
// the qName prefix.
func (t *server) serveForward(wtr dns.ResponseWriter, req *request) serveResult {
ipStr := strings.TrimSuffix(req.qName, "."+req.auth.Domain)
ar := strings.SplitN(ipStr, "-", 4)
is4 := true
if len(ar) != 4 || strings.Contains(ar[3], "-") { // A legit ipv4?
is4 = false
} else {
for _, v := range ar {
if len(v) == 0 {
is4 = false
break
}
}
}
if is4 {
return t.serveA(wtr, req)
}
return t.serveAAAA(wtr, req)
}
// Expecting 192-0-2-1.$forward. Unlike ipv6, there is no compression of the IP address to
// cover multiple zero octets, which makes parsing simpler. IOWs, 192.0.0.1 does not get
// converted into 192--1.
func (t *server) serveA(wtr dns.ResponseWriter, req *request) serveResult {
req.stats.AForward.queries++
ipStr := strings.TrimSuffix(req.qName, "."+req.auth.Domain)
if strings.Index(ipStr, ".") >= 0 { // Don't allow 192.0.2.0.domain - should be 192-0-2-0.domain
return NXDomain
}
ipStr = strings.ReplaceAll(ipStr, "-", ".")
ip := net.ParseIP(ipStr)
if ip == nil { // Couldn't convert back into an ip address
return NXDomain
}
ip = ip.To4()
if ip == nil || len(ip) != net.IPv4len { // serveForward() checking should catch this
return NXDomain
}
// If ip is not in-bailwick of our reverse zones then NXDomain
if req.authorities.findIPInDomain(ip) == nil {
return NXDomain
}
if req.question.Qtype != dns.TypeA { // If wrong type, NoError
return NoError
}
req.response.SetReply(req.query)
rr := new(dns.A)
rr.Hdr.Name = req.question.Name
rr.Hdr.Class = req.question.Qclass
rr.Hdr.Rrtype = req.question.Qtype
rr.Hdr.Ttl = t.cfg.TTLAsSecs
rr.A = ip
req.response.Answer = append(req.response.Answer, rr)
t.writeMsg(wtr, req)
req.stats.AForward.good++
req.stats.AForward.answers += len(req.response.Answer)
return serveDone
}
// Expecting 2001-db83--1.domain. Convert the synthetic name back into an IP address and
// reply with an AAAA.
func (t *server) serveAAAA(wtr dns.ResponseWriter, req *request) serveResult {
req.stats.AAAAForward.queries++
ipStr := strings.TrimSuffix(req.qName, "."+req.auth.Domain)
if strings.Index(ipStr, ":") >= 0 { // Don't allow fd00::1.domain - should be fd00--1.domain
return NXDomain
}
ipStr = strings.ReplaceAll(ipStr, "-", ":")
ip := net.ParseIP(ipStr)
if ip == nil { // Couldn't convert back into an ip address
return NXDomain
}
if len(ip) != net.IPv6len {
return NXDomain
}
// If ip is not in-bailwick of our reverse zones then NXDomain
if req.authorities.findIPInDomain(ip) == nil {
return NXDomain
}
if req.question.Qtype != dns.TypeAAAA { // If wrong type, NoError
return NoError
}
req.response.SetReply(req.query)
rr := new(dns.AAAA)
rr.Hdr.Name = req.question.Name
rr.Hdr.Class = req.question.Qclass
rr.Hdr.Rrtype = req.question.Qtype
rr.Hdr.Ttl = t.cfg.TTLAsSecs
rr.AAAA = ip
req.response.Answer = append(req.response.Answer, rr)
t.writeMsg(wtr, req)
req.stats.AAAAForward.good++
req.stats.AAAAForward.answers += len(req.response.Answer)
return serveDone
}
// The domain is a known reverse domain which means that a well formed query should be a
// query such as:
//
// dig -t $qType 168.192.in-addr.arpa.
// dig -t $qType 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.
//
// An invertible, but truncated IP is of the form:
//
// dig -t $qType 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.
//
// As with serveForward() most of this code is dealing with malformed queries and
// determining what the correct response is. E.g. chopping a few nibbles off the front of
// an ip6.arpa query can easily still be in-domain resulting in NOError whereas the
// same thing for in-addpr.arpa will almost certainly be NXDomain.
//
// Since qName is known to be in-domain of a reverse authority and since this function
// is called *after* database lookup attempts, cases to handle are:
//
// 1. Uninvertible IPs such as those with impossible hex characters - serve NXDomain
// 2. An invertible, but truncated IP - serve NoError - most likely qname minimization probe
// 3. An invertible IP with $qType!=PTR - serve NoError
// 4. An invertible IP with $qType=PTR - serve the synth answer
func (t *server) serveReverse(wtr dns.ResponseWriter, req *request) serveResult {
var (
reverseIPStr string
ip net.IP
err error
statsp *qTypeStats
truncated bool
)
switch {
case strings.HasSuffix(req.qName, dnsutil.V6Suffix):
statsp = &req.stats.AAAAPtr
statsp.queries++
reverseIPStr = strings.TrimSuffix(req.qName, dnsutil.V6Suffix)
ip, truncated, err = dnsutil.InvertPtrToIPv6(reverseIPStr)
if truncated {
req.stats.gen.truncatedV6++
}
case strings.HasSuffix(req.qName, dnsutil.V4Suffix):
statsp = &req.stats.APtr
statsp.queries++
reverseIPStr = strings.TrimSuffix(req.qName, dnsutil.V4Suffix)
ip, truncated, err = dnsutil.InvertPtrToIPv4(reverseIPStr)
if truncated {
req.stats.gen.truncatedV4++
}
default: // Unexpected suffix - Dispatcher should not have let this in
log.Major("Danger:Dispatcher should never let in ", req.qName)
req.addNote("bad Mux")
return FormErr
}
if err != nil { // Case 1: qName contains uninvertible IP
statsp.invertError++
return NXDomain
}
if truncated { // Case 2: Well-formed, but incomplete - qname minimization?
req.addNote("Trunc-qmin")
statsp.truncated++
return NoError
}
if req.question.Qtype != dns.TypePTR { // Case 3: Invertible, but not a PTR
req.addNote("Not PTR")
return NoError
}
req.addNote("Synth") // Case 4: Synthesize
ptr := dnsutil.SynthesizePTR(req.qName, req.mutables.ptrSuffix, ip)
req.response.SetReply(req.query)
ptr.Hdr.Ttl = t.cfg.TTLAsSecs
req.response.Answer = append(req.response.Answer, ptr)
t.writeMsg(wtr, req)
statsp.good++
statsp.answers += len(req.response.Answer)
return serveDone
}
// writeMsg finalized the output message with all of the common processing, checks with
// RRL to see if the response is rate-limited and then potentially calls the response
// writer to send the message. Any error is recorded in req.logError
func (t *server) writeMsg(wtr dns.ResponseWriter, req *request) {
opt := req.genOpt()
if opt != nil {
req.response.Extra = append(req.response.Extra, opt)
}
req.response.Authoritative = true
req.msgSize = req.response.Len() // Transfer to Stats for reporting purposes
req.compressed = req.response.Compress
req.truncated = req.response.MsgHdr.Truncated
// Only call RRL (if it's active) and for sources which can be spoofed
action := rrl.Send
if t.rrlHandler != nil && req.network != dnsutil.TCPNetwork && !req.cookieValid {
rt := miekgrrl.Derive(req.response, req.rrlOriginName) // Make ResponseTuple from response Msg
req.rrlAction, _, _ = t.rrlHandler.Debit(req.src, rt)
if !t.cfg.rrlDryRun {
action = req.rrlAction // We're doing RRL for reals
}
}
switch action {
case rrl.Send:
err := wtr.WriteMsg(req.response)
if err != nil {
req.logError = fmt.Errorf("WriteMsg failed: %s", dnsutil.ShortenLookupError(err))
}
case rrl.Drop:
case rrl.Slip:
if req.cookieWellFormed { // Override whatever the original rcode was
req.response.SetRcode(req.query, dns.RcodeBadCookie)
} else {
req.response.MsgHdr.Truncated = true // Not sure if this should be set with bad cookie
}
req.response.Ns = []dns.RR{} // In all cases remove any req.response material
req.response.Answer = []dns.RR{}
err := wtr.WriteMsg(req.response)
if err != nil {
req.logError = fmt.Errorf("WriteMsg failed: %s", dnsutil.ShortenLookupError(err))
}
}
}