middleware/log: allows logging based on response classes (#325)
Add the ability to add a class of responses to be logged; success, denial or error. The default is to log everything (all). Fixes #258
This commit is contained in:
parent
caa3976bfe
commit
c22b7b2252
13 changed files with 348 additions and 108 deletions
2
middleware/cache/README.md
vendored
2
middleware/cache/README.md
vendored
|
@ -20,7 +20,7 @@ Or if you want more control:
|
||||||
|
|
||||||
~~~ txt
|
~~~ txt
|
||||||
cache [ttl] [zones...] {
|
cache [ttl] [zones...] {
|
||||||
noerror capacity [ttl]
|
success capacity [ttl]
|
||||||
denial capacity [ttl]
|
denial capacity [ttl]
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
4
middleware/cache/cache.go
vendored
4
middleware/cache/cache.go
vendored
|
@ -60,7 +60,7 @@ type ResponseWriter struct {
|
||||||
// WriteMsg implements the dns.ResponseWriter interface.
|
// WriteMsg implements the dns.ResponseWriter interface.
|
||||||
func (c *ResponseWriter) WriteMsg(res *dns.Msg) error {
|
func (c *ResponseWriter) WriteMsg(res *dns.Msg) error {
|
||||||
do := false
|
do := false
|
||||||
mt, opt := response.Classify(res)
|
mt, opt := response.Typify(res)
|
||||||
if opt != nil {
|
if opt != nil {
|
||||||
do = opt.Do()
|
do = opt.Do()
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ func (c *ResponseWriter) set(m *dns.Msg, key string, mt response.Type, duration
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mt {
|
switch mt {
|
||||||
case response.Success, response.Delegation:
|
case response.NoError, response.Delegation:
|
||||||
i := newItem(m, duration)
|
i := newItem(m, duration)
|
||||||
c.pcache.Add(key, i)
|
c.pcache.Add(key, i)
|
||||||
|
|
||||||
|
|
2
middleware/cache/cache_test.go
vendored
2
middleware/cache/cache_test.go
vendored
|
@ -94,7 +94,7 @@ func TestCache(t *testing.T) {
|
||||||
m = cacheMsg(m, tc)
|
m = cacheMsg(m, tc)
|
||||||
do := tc.in.Do
|
do := tc.in.Do
|
||||||
|
|
||||||
mt, _ := response.Classify(m)
|
mt, _ := response.Typify(m)
|
||||||
k := key(m, mt, do)
|
k := key(m, mt, do)
|
||||||
crr.set(m, k, mt, c.pttl)
|
crr.set(m, k, mt, c.pttl)
|
||||||
|
|
||||||
|
|
4
middleware/cache/item.go
vendored
4
middleware/cache/item.go
vendored
|
@ -87,7 +87,7 @@ func setMsgTTL(m *dns.Msg, ttl uint32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func minMsgTTL(m *dns.Msg, mt response.Type) time.Duration {
|
func minMsgTTL(m *dns.Msg, mt response.Type) time.Duration {
|
||||||
if mt != response.Success && mt != response.NameError && mt != response.NoData {
|
if mt != response.NoError && mt != response.NameError && mt != response.NoData {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ func minMsgTTL(m *dns.Msg, mt response.Type) time.Duration {
|
||||||
if r.Header().Rrtype == dns.TypeSOA {
|
if r.Header().Rrtype == dns.TypeSOA {
|
||||||
return time.Duration(r.(*dns.SOA).Minttl) * time.Second
|
return time.Duration(r.(*dns.SOA).Minttl) * time.Second
|
||||||
}
|
}
|
||||||
case response.Success, response.Delegation:
|
case response.NoError, response.Delegation:
|
||||||
if r.Header().Ttl < uint32(minTTL.Seconds()) {
|
if r.Header().Ttl < uint32(minTTL.Seconds()) {
|
||||||
minTTL = time.Duration(r.Header().Ttl) * time.Second
|
minTTL = time.Duration(r.Header().Ttl) * time.Second
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ func New(zones []string, keys []*DNSKEY, next middleware.Handler) Dnssec {
|
||||||
func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg {
|
func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg {
|
||||||
req := state.Req
|
req := state.Req
|
||||||
|
|
||||||
mt, _ := response.Classify(req) // TODO(miek): need opt record here?
|
mt, _ := response.Typify(req) // TODO(miek): need opt record here?
|
||||||
if mt == response.Delegation {
|
if mt == response.Delegation {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,53 @@
|
||||||
# log
|
# log
|
||||||
|
|
||||||
`log` enables request logging. The request log is also known in some vernacular as an access log.
|
`log` enables query logging.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
~~~
|
~~~ txt
|
||||||
log
|
log
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* With no arguments, a query log entry is written to query.log in the common log format for all requests
|
* With no arguments, a query log entry is written to query.log in the common log format for all requests
|
||||||
(base name = .).
|
(base name = .).
|
||||||
|
|
||||||
~~~
|
~~~ txt
|
||||||
log file
|
log file
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* file is the log file to create (or append to). The base path is assumed to be . .
|
* file is the log file to create (or append to). The base name is assumed to be '.' .
|
||||||
|
|
||||||
~~~
|
~~~ txt
|
||||||
log name file [format]
|
log [name] [file] [format]
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* `name` is the base name to match in order to be logged
|
* `name` is the base name to match in order to be logged
|
||||||
* `file` is the log file to create (or append to)
|
* `file` is the log file to create (or append to)
|
||||||
* `format` is the log format to use (default is Common Log Format)
|
* `format` is the log format to use (default is Common Log Format)
|
||||||
|
|
||||||
|
You can further specify the class of responses that get logged:
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
log [name] [file] [format] {
|
||||||
|
class [success|denial|error|all]
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Here *success*, *denial* and *error* denotes the class of responses that should be logged. The
|
||||||
|
classes have the following meaning:
|
||||||
|
|
||||||
|
* `success`: successful response
|
||||||
|
* `denial`: either NXDOMAIN or NODATA (name exists, type does not)
|
||||||
|
* `error`: SERVFAIL, NOTIMP, REFUSED, etc. Any that indicated the remove server is not willing to
|
||||||
|
resolve the request.
|
||||||
|
* `all`: the default is nothing is specified.
|
||||||
|
|
||||||
|
If no class is specified it defaults to *all*.
|
||||||
|
|
||||||
## Log File
|
## Log File
|
||||||
|
|
||||||
The log file can be any filename. It could also be stdout or stderr to write the log to the console,
|
The log file can be any filename. It could also be *stdout* or *stderr* to write the log to the console,
|
||||||
or syslog to write to the system log (except on Windows). If the log file does not exist beforehand,
|
or *syslog* to write to the system log (except on Windows). If the log file does not exist beforehand,
|
||||||
CoreDNS will create it before appending to it.
|
CoreDNS will create it before appending to it.
|
||||||
|
|
||||||
## Log Format
|
## Log Format
|
||||||
|
@ -53,6 +72,11 @@ The following place holders are supported:
|
||||||
* `{>id}`: query ID
|
* `{>id}`: query ID
|
||||||
* `{>opcode}`: query OPCODE
|
* `{>opcode}`: query OPCODE
|
||||||
|
|
||||||
|
The default Common Log Format is:
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
`{remote} - [{when}] "{type} {class} {name} {proto} {>do} {>bufsize}" {rcode} {size} {duration}`
|
||||||
|
~~~
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -67,3 +91,11 @@ Custom log format:
|
||||||
~~~
|
~~~
|
||||||
log . ../query.log "{proto} Request: {name} {type} {>id}"
|
log . ../query.log "{proto} Request: {name} {type} {>id}"
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
Only log denials for example.org (and below to a file)
|
||||||
|
|
||||||
|
~~~
|
||||||
|
log example.org example-query-log {
|
||||||
|
class denial
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/miekg/coredns/middleware/pkg/dnsrecorder"
|
"github.com/miekg/coredns/middleware/pkg/dnsrecorder"
|
||||||
"github.com/miekg/coredns/middleware/pkg/rcode"
|
"github.com/miekg/coredns/middleware/pkg/rcode"
|
||||||
"github.com/miekg/coredns/middleware/pkg/replacer"
|
"github.com/miekg/coredns/middleware/pkg/replacer"
|
||||||
|
"github.com/miekg/coredns/middleware/pkg/response"
|
||||||
"github.com/miekg/coredns/request"
|
"github.com/miekg/coredns/request"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -27,7 +28,10 @@ type Logger struct {
|
||||||
func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
state := request.Request{W: w, Req: r}
|
state := request.Request{W: w, Req: r}
|
||||||
for _, rule := range l.Rules {
|
for _, rule := range l.Rules {
|
||||||
if middleware.Name(rule.NameScope).Matches(state.Name()) {
|
if !middleware.Name(rule.NameScope).Matches(state.Name()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
responseRecorder := dnsrecorder.New(w)
|
responseRecorder := dnsrecorder.New(w)
|
||||||
rc, err := l.Next.ServeDNS(ctx, responseRecorder, r)
|
rc, err := l.Next.ServeDNS(ctx, responseRecorder, r)
|
||||||
|
|
||||||
|
@ -42,22 +46,28 @@ func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
state.SizeAndDo(answer)
|
state.SizeAndDo(answer)
|
||||||
|
|
||||||
metrics.Report(state, metrics.Dropped, rcode.ToString(rc), answer.Len(), time.Now())
|
metrics.Report(state, metrics.Dropped, rcode.ToString(rc), answer.Len(), time.Now())
|
||||||
|
|
||||||
w.WriteMsg(answer)
|
w.WriteMsg(answer)
|
||||||
}
|
}
|
||||||
rc = 0
|
rc = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class, _ := response.Classify(responseRecorder.Msg)
|
||||||
|
if rule.Class == response.All || rule.Class == class {
|
||||||
rep := replacer.New(r, responseRecorder, CommonLogEmptyValue)
|
rep := replacer.New(r, responseRecorder, CommonLogEmptyValue)
|
||||||
rule.Log.Println(rep.Replace(rule.Format))
|
rule.Log.Println(rep.Replace(rule.Format))
|
||||||
|
}
|
||||||
|
|
||||||
return rc, err
|
return rc, err
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return l.Next.ServeDNS(ctx, w, r)
|
return l.Next.ServeDNS(ctx, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule configures the logging middleware.
|
// Rule configures the logging middleware.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
NameScope string
|
NameScope string
|
||||||
|
Class response.Class
|
||||||
OutputFile string
|
OutputFile string
|
||||||
Format string
|
Format string
|
||||||
Log *log.Logger
|
Log *log.Logger
|
||||||
|
|
|
@ -7,21 +7,15 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/miekg/coredns/middleware/pkg/dnsrecorder"
|
"github.com/miekg/coredns/middleware/pkg/dnsrecorder"
|
||||||
|
"github.com/miekg/coredns/middleware/pkg/response"
|
||||||
"github.com/miekg/coredns/middleware/test"
|
"github.com/miekg/coredns/middleware/test"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type erroringMiddleware struct{}
|
|
||||||
|
|
||||||
func (erroringMiddleware) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
||||||
return dns.RcodeServerFailure, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoggedStatus(t *testing.T) {
|
func TestLoggedStatus(t *testing.T) {
|
||||||
var f bytes.Buffer
|
var f bytes.Buffer
|
||||||
var next erroringMiddleware
|
|
||||||
rule := Rule{
|
rule := Rule{
|
||||||
NameScope: ".",
|
NameScope: ".",
|
||||||
Format: DefaultLogFormat,
|
Format: DefaultLogFormat,
|
||||||
|
@ -30,7 +24,7 @@ func TestLoggedStatus(t *testing.T) {
|
||||||
|
|
||||||
logger := Logger{
|
logger := Logger{
|
||||||
Rules: []Rule{rule},
|
Rules: []Rule{rule},
|
||||||
Next: next,
|
Next: test.ErrorHandler(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
@ -41,11 +35,67 @@ func TestLoggedStatus(t *testing.T) {
|
||||||
|
|
||||||
rcode, _ := logger.ServeDNS(ctx, rec, r)
|
rcode, _ := logger.ServeDNS(ctx, rec, r)
|
||||||
if rcode != 0 {
|
if rcode != 0 {
|
||||||
t.Error("Expected rcode to be 0 - was", rcode)
|
t.Errorf("Expected rcode to be 0 - was: %d", rcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
logged := f.String()
|
logged := f.String()
|
||||||
if !strings.Contains(logged, "A IN example.org. udp false 512") {
|
if !strings.Contains(logged, "A IN example.org. udp false 512") {
|
||||||
t.Error("Expected it to be logged. Logged string -", logged)
|
t.Errorf("Expected it to be logged. Logged string: %s", logged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggedClassDenial(t *testing.T) {
|
||||||
|
var f bytes.Buffer
|
||||||
|
rule := Rule{
|
||||||
|
NameScope: ".",
|
||||||
|
Format: DefaultLogFormat,
|
||||||
|
Log: log.New(&f, "", 0),
|
||||||
|
Class: response.Denial,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := Logger{
|
||||||
|
Rules: []Rule{rule},
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
|
r := new(dns.Msg)
|
||||||
|
r.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
|
||||||
|
rec := dnsrecorder.New(&test.ResponseWriter{})
|
||||||
|
|
||||||
|
logger.ServeDNS(ctx, rec, r)
|
||||||
|
|
||||||
|
logged := f.String()
|
||||||
|
if len(logged) != 0 {
|
||||||
|
t.Errorf("Expected it not to be logged, but got string: %s", logged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoggedClassError(t *testing.T) {
|
||||||
|
var f bytes.Buffer
|
||||||
|
rule := Rule{
|
||||||
|
NameScope: ".",
|
||||||
|
Format: DefaultLogFormat,
|
||||||
|
Log: log.New(&f, "", 0),
|
||||||
|
Class: response.Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := Logger{
|
||||||
|
Rules: []Rule{rule},
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.TODO()
|
||||||
|
r := new(dns.Msg)
|
||||||
|
r.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
|
||||||
|
rec := dnsrecorder.New(&test.ResponseWriter{})
|
||||||
|
|
||||||
|
logger.ServeDNS(ctx, rec, r)
|
||||||
|
|
||||||
|
logged := f.String()
|
||||||
|
if !strings.Contains(logged, "SERVFAIL") {
|
||||||
|
t.Errorf("Expected it to be logged. Logged string: %s", logged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/miekg/coredns/core/dnsserver"
|
"github.com/miekg/coredns/core/dnsserver"
|
||||||
"github.com/miekg/coredns/middleware"
|
"github.com/miekg/coredns/middleware"
|
||||||
|
"github.com/miekg/coredns/middleware/pkg/response"
|
||||||
|
|
||||||
"github.com/hashicorp/go-syslog"
|
"github.com/hashicorp/go-syslog"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
|
@ -105,6 +106,26 @@ func logParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
Format: format,
|
Format: format,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Class refinements in an extra block.
|
||||||
|
for c.NextBlock() {
|
||||||
|
switch c.Val() {
|
||||||
|
// class followed by all, denial, error or success.
|
||||||
|
case "class":
|
||||||
|
classes := c.RemainingArgs()
|
||||||
|
if len(classes) == 0 {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
cls, err := response.ClassFromString(classes[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// update class and the last added Rule (bit icky)
|
||||||
|
rules[len(rules)-1].Class = cls
|
||||||
|
default:
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rules, nil
|
return rules, nil
|
||||||
|
|
|
@ -3,6 +3,8 @@ package log
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/miekg/coredns/middleware/pkg/response"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,6 +64,31 @@ func TestLogParse(t *testing.T) {
|
||||||
OutputFile: "log.txt",
|
OutputFile: "log.txt",
|
||||||
Format: "{when}",
|
Format: "{when}",
|
||||||
}}},
|
}}},
|
||||||
|
|
||||||
|
{`log example.org log.txt {
|
||||||
|
class all
|
||||||
|
}`, false, []Rule{{
|
||||||
|
NameScope: "example.org.",
|
||||||
|
OutputFile: "log.txt",
|
||||||
|
Format: CommonLogFormat,
|
||||||
|
Class: response.All,
|
||||||
|
}}},
|
||||||
|
{`log example.org log.txt {
|
||||||
|
class denial
|
||||||
|
}`, false, []Rule{{
|
||||||
|
NameScope: "example.org.",
|
||||||
|
OutputFile: "log.txt",
|
||||||
|
Format: CommonLogFormat,
|
||||||
|
Class: response.Denial,
|
||||||
|
}}},
|
||||||
|
{`log {
|
||||||
|
class denial
|
||||||
|
}`, false, []Rule{{
|
||||||
|
NameScope: ".",
|
||||||
|
OutputFile: DefaultLogFilename,
|
||||||
|
Format: CommonLogFormat,
|
||||||
|
Class: response.Denial,
|
||||||
|
}}},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
c := caddy.NewTestController("dns", test.inputLogRules)
|
c := caddy.NewTestController("dns", test.inputLogRules)
|
||||||
|
@ -92,6 +119,11 @@ func TestLogParse(t *testing.T) {
|
||||||
t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s",
|
t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s",
|
||||||
i, j, test.expectedLogRules[j].Format, actualLogRule.Format)
|
i, j, test.expectedLogRules[j].Format, actualLogRule.Format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if actualLogRule.Class != test.expectedLogRules[j].Class {
|
||||||
|
t.Errorf("Test %d expected %dth LogRule Class to be %s , but got %s",
|
||||||
|
i, j, test.expectedLogRules[j].Class, actualLogRule.Class)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,74 +1,73 @@
|
||||||
package response
|
package response
|
||||||
|
|
||||||
import "github.com/miekg/dns"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
// Type is the type of the message
|
"github.com/miekg/dns"
|
||||||
type Type int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Success indicates a positive reply
|
|
||||||
Success Type = iota
|
|
||||||
// NameError is a NXDOMAIN in header, SOA in auth.
|
|
||||||
NameError
|
|
||||||
// NoData indicated name found, but not the type: NOERROR in header, SOA in auth.
|
|
||||||
NoData
|
|
||||||
// Delegation is a msg with a pointer to another nameserver: NOERROR in header, NS in auth, optionally fluff in additional (not checked).
|
|
||||||
Delegation
|
|
||||||
// OtherError indicated any other error: don't cache these.
|
|
||||||
OtherError
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t Type) String() string {
|
// Class holds sets of Types
|
||||||
switch t {
|
type Class int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// All is a meta class encompassing all the classes.
|
||||||
|
All Class = iota
|
||||||
|
// Success is a class for a successful response.
|
||||||
|
Success
|
||||||
|
// Denial is a class for denying existence (NXDOMAIN, or a nodata: type does not exist)
|
||||||
|
Denial
|
||||||
|
// Error is a class for errors, right now defined as not Success and not Denial
|
||||||
|
Error
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Class) String() string {
|
||||||
|
switch c {
|
||||||
|
case All:
|
||||||
|
return "all"
|
||||||
case Success:
|
case Success:
|
||||||
return "NOERROR"
|
return "success"
|
||||||
case NameError:
|
case Denial:
|
||||||
return "NXDOMAIN"
|
return "denial"
|
||||||
case NoData:
|
case Error:
|
||||||
return "NODATA"
|
return "error"
|
||||||
case Delegation:
|
|
||||||
return "DELEGATION"
|
|
||||||
case OtherError:
|
|
||||||
return "OTHERERROR"
|
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Classify classifies a message, it returns the Type.
|
// ClassFromString returns the class from the string s. If not class matches
|
||||||
func Classify(m *dns.Msg) (Type, *dns.OPT) {
|
// the All class and an error are returned
|
||||||
opt := m.IsEdns0()
|
func ClassFromString(s string) (Class, error) {
|
||||||
|
switch s {
|
||||||
if len(m.Answer) > 0 && m.Rcode == dns.RcodeSuccess {
|
case "all":
|
||||||
return Success, opt
|
return All, nil
|
||||||
|
case "success":
|
||||||
|
return Success, nil
|
||||||
|
case "denial":
|
||||||
|
return Denial, nil
|
||||||
|
case "error":
|
||||||
|
return Error, nil
|
||||||
}
|
}
|
||||||
|
return All, fmt.Errorf("invalid Class: %s", s)
|
||||||
soa := false
|
}
|
||||||
ns := 0
|
|
||||||
for _, r := range m.Ns {
|
// Classify classifies a dns message: it returns its Class.
|
||||||
if r.Header().Rrtype == dns.TypeSOA {
|
func Classify(m *dns.Msg) (Class, *dns.OPT) {
|
||||||
soa = true
|
t, o := Typify(m)
|
||||||
continue
|
return classify(t), o
|
||||||
}
|
}
|
||||||
if r.Header().Rrtype == dns.TypeNS {
|
|
||||||
ns++
|
// Does need to be exported?
|
||||||
}
|
func classify(t Type) Class {
|
||||||
}
|
switch t {
|
||||||
|
case NoError, Delegation:
|
||||||
// Check length of different sections, and drop stuff that is just to large? TODO(miek).
|
return Success
|
||||||
if soa && m.Rcode == dns.RcodeSuccess {
|
case NameError, NoData:
|
||||||
return NoData, opt
|
return Denial
|
||||||
}
|
case OtherError:
|
||||||
if soa && m.Rcode == dns.RcodeNameError {
|
fallthrough
|
||||||
return NameError, opt
|
default:
|
||||||
}
|
return Error
|
||||||
|
}
|
||||||
if ns > 0 && ns == len(m.Ns) && m.Rcode == dns.RcodeSuccess {
|
// never reached
|
||||||
return Delegation, opt
|
return All
|
||||||
}
|
|
||||||
|
|
||||||
if m.Rcode == dns.RcodeSuccess {
|
|
||||||
return Success, opt
|
|
||||||
}
|
|
||||||
|
|
||||||
return OtherError, opt
|
|
||||||
}
|
}
|
||||||
|
|
96
middleware/pkg/response/typify.go
Normal file
96
middleware/pkg/response/typify.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type is the type of the message
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Success indicates a positive reply
|
||||||
|
NoError Type = iota
|
||||||
|
// NameError is a NXDOMAIN in header, SOA in auth.
|
||||||
|
NameError
|
||||||
|
// NoData indicated name found, but not the type: NOERROR in header, SOA in auth.
|
||||||
|
NoData
|
||||||
|
// Delegation is a msg with a pointer to another nameserver: NOERROR in header, NS in auth, optionally fluff in additional (not checked).
|
||||||
|
Delegation
|
||||||
|
// OtherError indicated any other error: don't cache these.
|
||||||
|
OtherError
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Type) String() string {
|
||||||
|
switch t {
|
||||||
|
case NoError:
|
||||||
|
return "NOERROR"
|
||||||
|
case NameError:
|
||||||
|
return "NXDOMAIN"
|
||||||
|
case NoData:
|
||||||
|
return "NODATA"
|
||||||
|
case Delegation:
|
||||||
|
return "DELEGATION"
|
||||||
|
case OtherError:
|
||||||
|
return "OTHERERROR"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeFromString returns the type from the string s. If not type matches
|
||||||
|
// the OtherError type and an error are returned.
|
||||||
|
func TypeFromString(s string) (Type, error) {
|
||||||
|
switch s {
|
||||||
|
case "NOERROR":
|
||||||
|
return NoError, nil
|
||||||
|
case "NXDOMAIN":
|
||||||
|
return NameError, nil
|
||||||
|
case "NODATA":
|
||||||
|
return NoData, nil
|
||||||
|
case "DELEGATION":
|
||||||
|
return Delegation, nil
|
||||||
|
case "OTHERERROR":
|
||||||
|
return OtherError, nil
|
||||||
|
}
|
||||||
|
return NoError, fmt.Errorf("invalid Type: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typify classifies a message, it returns the Type.
|
||||||
|
func Typify(m *dns.Msg) (Type, *dns.OPT) {
|
||||||
|
opt := m.IsEdns0()
|
||||||
|
|
||||||
|
if len(m.Answer) > 0 && m.Rcode == dns.RcodeSuccess {
|
||||||
|
return NoError, opt
|
||||||
|
}
|
||||||
|
|
||||||
|
soa := false
|
||||||
|
ns := 0
|
||||||
|
for _, r := range m.Ns {
|
||||||
|
if r.Header().Rrtype == dns.TypeSOA {
|
||||||
|
soa = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.Header().Rrtype == dns.TypeNS {
|
||||||
|
ns++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check length of different sections, and drop stuff that is just to large? TODO(miek).
|
||||||
|
if soa && m.Rcode == dns.RcodeSuccess {
|
||||||
|
return NoData, opt
|
||||||
|
}
|
||||||
|
if soa && m.Rcode == dns.RcodeNameError {
|
||||||
|
return NameError, opt
|
||||||
|
}
|
||||||
|
|
||||||
|
if ns > 0 && ns == len(m.Ns) && m.Rcode == dns.RcodeSuccess {
|
||||||
|
return Delegation, opt
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Rcode == dns.RcodeSuccess {
|
||||||
|
return NoError, opt
|
||||||
|
}
|
||||||
|
|
||||||
|
return OtherError, opt
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
func TestClassifyDelegation(t *testing.T) {
|
func TestClassifyDelegation(t *testing.T) {
|
||||||
m := delegationMsg()
|
m := delegationMsg()
|
||||||
mt, _ := Classify(m)
|
mt, _ := Typify(m)
|
||||||
if mt != Delegation {
|
if mt != Delegation {
|
||||||
t.Errorf("message is wrongly classified, expected delegation, got %d", mt)
|
t.Errorf("message is wrongly classified, expected delegation, got %d", mt)
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue