commit 598b53e1e47686b8c47241b874109f3b70c5f2d1
parent f62764615c3e0fa8465b5bdf32fd067841e9fb91
Author: Oliver Lowe <o@olowe.co>
Date: Sat, 18 Dec 2021 21:22:56 +1100
dns: add Handlers for common server responses
this takes more stuff out of cmd/recursor that could be used by other
servers.
While here reject even more qtypes from recursive resolution
Diffstat:
| M | cmd/recursor/recursor.go | | | 53 | +++++++++++++++++++++++++++++++++++------------------ |
| M | server.go | | | 104 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
2 files changed, 125 insertions(+), 32 deletions(-)
diff --git a/cmd/recursor/recursor.go b/cmd/recursor/recursor.go
@@ -8,33 +8,50 @@ import (
"olowe.co/dns"
)
-func shouldReject(m *dnsmessage.Message) (bool, dnsmessage.RCode) {
- if !m.Header.RecursionDesired {
- return true, dnsmessage.RCodeRefused
- } else if m.Header.OpCode != dns.OpCodeQUERY {
- return true, dnsmessage.RCodeRefused
- } else if len(m.Questions) != 1 {
- return true, dnsmessage.RCodeFormatError
- } else if m.Questions[0].Type == dnsmessage.TypeALL {
- return true, dnsmessage.RCodeNotImplemented
- } else if m.Questions[0].Class != dnsmessage.ClassINET {
- return true, dnsmessage.RCodeNotImplemented
+// okQType returns true if t is a query type that we can resolve by
+// recursively querying nameservers.
+func okQType(t dnsmessage.Type) bool {
+ switch t {
+ case dnsmessage.TypeA, dnsmessage.TypeNS, dnsmessage.TypeCNAME, dnsmessage.TypeSOA, dnsmessage.TypePTR, dnsmessage.TypeMX, dnsmessage.TypeTXT, dnsmessage.TypeAAAA, dnsmessage.TypeSRV, dnsmessage.TypeOPT:
+ return true
}
- return false, dnsmessage.RCodeSuccess
+ return false
+}
+
+// rejectHandler is a safeguard to prevent queries we don't want (or support)
+// to be recursively resolved. It returns true if the message was rejected.
+func rejectHandler(w dns.ResponseWriter, qmsg *dnsmessage.Message) bool {
+ if !qmsg.Header.RecursionDesired {
+ dns.Refuse(w, qmsg)
+ return true
+ } else if qmsg.Header.OpCode != dns.OpCodeQUERY {
+ dns.Refuse(w, qmsg)
+ return true
+ } else if len(qmsg.Questions) != 1 {
+ dns.FormatError(w, qmsg)
+ return true
+ }
+ q := qmsg.Questions[0]
+ if !okQType(q.Type) {
+ dns.NotImplemented(w, qmsg)
+ return true
+ } else if q.Class != dnsmessage.ClassINET {
+ dns.NotImplemented(w, qmsg)
+ return true
+ }
+ return false
}
func handler(w dns.ResponseWriter, qmsg *dnsmessage.Message) {
+ if rejected := rejectHandler(w, qmsg); rejected {
+ return
+ }
+
var rmsg dnsmessage.Message
rmsg.Header.ID = qmsg.Header.ID
rmsg.Header.Response = true
rmsg.Header.RecursionAvailable = true
rmsg.Questions = qmsg.Questions
-
- if reject, rc := shouldReject(qmsg); reject {
- rmsg.Header.RCode = rc
- w.WriteMsg(rmsg)
- return
- }
rmsg.RecursionDesired = true
q := qmsg.Questions[0]
diff --git a/server.go b/server.go
@@ -132,21 +132,97 @@ func ListenAndServe(network, addr string, handler Handler) error {
return srv.ListenAndServe()
}
-// DefaultHandler responds to all DNS messages identically. Recursivew
-// queries are refused and all others are replied to with a "not
-// implemented" message. It is intended as a safe default for a Server
-// which does not set a Handler.
-func DefaultHandler(w ResponseWriter, msg *dnsmessage.Message) {
- var rmsg dnsmessage.Message
- rmsg.Header.ID = msg.Header.ID
- if msg.Header.RecursionDesired {
- rmsg.Header.RCode = dnsmessage.RCodeRefused
- w.WriteMsg(rmsg)
- return
+// DefaultHandler responds to all DNS messages identically; all message
+// are refused. It is intended as a safe default for a Server which
+// does not set a Handler.
+var DefaultHandler = Refuse
+
+// FormatError replies to the message with a Format Error message.
+func FormatError(w ResponseWriter, msg *dnsmessage.Message) {
+ w.WriteMsg(dnsmessage.Message{
+ Header: dnsmessage.Header{
+ ID: msg.Header.ID,
+ Response: true,
+ RecursionDesired: msg.Header.RecursionDesired,
+ RCode: dnsmessage.RCodeFormatError,
+ },
+ Questions: msg.Questions,
+ })
+}
+
+// ServerFailure replies to the message with a Server Failure (SERVFAIL) message.
+func ServerFailure(w ResponseWriter, msg *dnsmessage.Message) {
+ w.WriteMsg(dnsmessage.Message{
+ Header: dnsmessage.Header{
+ ID: msg.Header.ID,
+ Response: true,
+ RecursionDesired: msg.Header.RecursionDesired,
+ RCode: dnsmessage.RCodeServerFailure,
+ },
+ Questions: msg.Questions,
+ })
+}
+
+// NotImplemented replies to the message with a Format Error message.
+func NotImplemented(w ResponseWriter, msg *dnsmessage.Message) {
+ w.WriteMsg(dnsmessage.Message{
+ Header: dnsmessage.Header{
+ ID: msg.Header.ID,
+ Response: true,
+ RecursionDesired: msg.Header.RecursionDesired,
+ RCode: dnsmessage.RCodeNotImplemented,
+ },
+ Questions: msg.Questions,
+ })
+}
+
+// Refuse replies to the message with a Refused message.
+func Refuse(w ResponseWriter, msg *dnsmessage.Message) {
+ w.WriteMsg(dnsmessage.Message{
+ Header: dnsmessage.Header{
+ ID: msg.Header.ID,
+ Response: true,
+ RecursionDesired: msg.Header.RecursionDesired,
+ RCode: dnsmessage.RCodeRefused,
+ },
+ Questions: msg.Questions,
+ })
+}
+
+// NameError replies to the message with a Name error (NXDOMAIN) message.
+// The SOA resource and its resource header rh are included in the reply.
+// Servers performing recursive resolution should set authoritative to false and
+// authoritative servers should set this to true.
+func NameError(w ResponseWriter, msg *dnsmessage.Message, rh dnsmessage.ResourceHeader, soa dnsmessage.SOAResource, authoritative bool) {
+ buf := make([]byte, 2, 512)
+ header := dnsmessage.Header{
+ ID: msg.Header.ID,
+ Response: true,
+ RecursionDesired: msg.Header.RecursionDesired,
+ Authoritative: authoritative,
+ RCode: dnsmessage.RCodeNameError,
+ }
+ builder := dnsmessage.NewBuilder(buf, header)
+ builder.EnableCompression()
+ if err := builder.StartQuestions(); err != nil {
+ panic(err)
+ }
+ for _, q := range msg.Questions {
+ if err := builder.Question(q); err != nil {
+ panic(err)
+ }
+ }
+ if err := builder.StartAuthorities; err != nil {
+ panic(err)
+ }
+ if err := builder.SOAResource(rh, soa); err != nil {
+ panic(err)
+ }
+ buf, err := builder.Finish()
+ if err != nil {
+ panic(err)
}
- rmsg.Questions = msg.Questions
- rmsg.Header.RCode = dnsmessage.RCodeNotImplemented
- w.WriteMsg(rmsg)
+ w.Write(buf[2:])
}
// ExtractIPs extracts any IP addresses from resources. An empty slice is