-
Notifications
You must be signed in to change notification settings - Fork 2
/
request.go
151 lines (132 loc) · 5.06 KB
/
request.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
package main
import (
"fmt"
"net"
"strings"
"github.com/markdingo/rrl"
"github.com/miekg/dns"
"github.com/markdingo/autoreverse/database"
"github.com/markdingo/autoreverse/dnsutil"
"github.com/markdingo/autoreverse/log"
)
// request contains the DNS request, related material and accumulated response data which
// is gradually extracted as request processing progresses. Rather than pass all of this
// data around as a fleet of function parameters it all gets accumulated into this request
// struct. The main purpose is readability and simplicity of adding variables as
// needed. The other main purpose is to accumulate values for log reporting. A request is
// only ever accessed by a single go-routine and only lives for the life of a single DNS
// query.
type request struct {
db *database.Database
query *dns.Msg
response *dns.Msg
question dns.Question
qName string
opt *dns.OPT // Optionally present
cookiesPresent bool // Cookie sub-option is present in opt
cookieWellFormed bool // Lengths are valid - only ever set if cookiesPresent is true
cookieValid bool // Server cookie is valid *and* current - only ever set if cookieWellFormed is true
clientCookie []byte // Copied and hex decoded from OPT regardless of cookieWellFormed
serverCookie []byte // Ditto
nsidOut string // Output nsid if len > 0
cookieOut []byte // If len > 0, this is the entire cookie to add to the out-going OPT
mutables // Copied from server under mutex protection
auth *authority // Match for current request
src net.Addr // From here on down is log data
network string
logQName string // Short but recognizable qName to keep log entries shorter
logNote []string // Mixed in with log message, if set
logError error // Append to log message, if set
msgSize int
maxSize uint16 // EDNS0 or zero which will cause dns.WriteMsg() to default
compressed bool
truncated bool
rrlOriginName string // Only set for synthesized answers
rrlAction rrl.Action // Returned from rrl.Debit for logging purposes
// To avoid holding a lock for the whole query, stats are accumulated in a
// separate copy and added back into the aggregate server stats at the end. This
// means that most of the dns query runs lock free, but it's at the expense of a
// chunk of memory and a churn thru all the stats at the end of each request. I've
// never found a statisfactory way of dealing with efficiently aggregating stats
// in a concurrency safe way that doesn't involve heavy operations. Well, heavy
// compared to just the ++ operation.
stats serverStats
}
// newRequest is a nice-to-use constructor. The zero form works just fine.
func newRequest(query *dns.Msg, src net.Addr, network string) *request {
return &request{query: query, response: new(dns.Msg), src: src, network: network, rrlAction: rrl.ActionLast}
}
// addNote does nothing more than append the supplied string to the note slice. That
// ultimately gets appended to line generated by log()
func (t *request) addNote(n string) {
t.logNote = append(t.logNote, n)
}
// log is called for --log-queries. It produces a one-line summary of the request that is
// intended to be suited to both automated scanning as well as human viewing. It tries to
// succinctly convey as many details as possible in as small a log-line as possible.
func (t *request) log() {
var note []string
if len(t.logNote) > 0 {
note = append(note, t.logNote...)
}
if t.logError != nil {
note = append(note, t.logError.Error())
}
var noteStr string
if len(note) > 0 {
noteStr = " " + strings.Join(note, ":")
}
var rcodeStr string
if t.response.MsgHdr.Rcode == dns.RcodeSuccess {
if len(t.response.Answer) == 0 {
rcodeStr = "ne" // NoError with no answers
} else {
rcodeStr = "ok"
}
} else {
rcodeStr = dnsutil.RcodeToString(t.response.MsgHdr.Rcode)
}
switch t.rrlAction {
case rrl.Drop:
rcodeStr += "/D"
case rrl.Slip:
rcodeStr += "/S"
}
hFlags := make([]byte, 0, 20) // 'h' = humongous?
if t.network == dnsutil.TCPNetwork {
hFlags = append(hFlags, 'T')
} else {
hFlags = append(hFlags, 'U') // Superfluous but ensures h= doesn't dangle
}
if t.compressed {
hFlags = append(hFlags, 'z')
}
if len(t.nsidOut) > 0 {
hFlags = append(hFlags, 'n')
}
if t.truncated {
hFlags = append(hFlags, 't')
}
if t.cookiesPresent {
hFlags = append(hFlags, 'e')
}
if t.cookieWellFormed {
hFlags = append(hFlags, 'E') // OPT is well formed
}
if t.cookieValid {
hFlags = append(hFlags, 'c') // At a minimum the client cookie is valid
}
if len(t.serverCookie) > 0 {
hFlags = append(hFlags, 's')
}
fmt.Fprintf(log.Out(), "ru=%s q=%s/%s s=%s id=%d h=%s sz=%d/%d C=%d/%d/%d%s\n",
rcodeStr, dnsutil.TypeToString(t.question.Qtype), t.logQName,
t.src.String(),
t.response.MsgHdr.Id, string(hFlags), t.msgSize, t.maxSize,
len(t.response.Answer), len(t.response.Ns), len(t.response.Extra), noteStr)
}
// setAuthority sets the authority for the current query if it's in-bailwick of any of our
// Authority domains.
func (t *request) setAuthority() {
t.auth = t.authorities.findInDomain(t.qName)
}