Skip to content

Commit

Permalink
Added minimal-responses plugin (coredns#4417)
Browse files Browse the repository at this point in the history
* Added minimal-responses plugin

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Removed unnecessary comments

* Updated tests

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Reformated imports

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Updated package name

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Removed  unnecessary comments

Co-authored-by: Miek Gieben <miek@miek.nl>

* Added changes

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* updated

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Updated comment for NextOrFailure

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Updated to test.Case for testing

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* Formated imports using goimports

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

Co-authored-by: Miek Gieben <miek@miek.nl>
  • Loading branch information
gdsoumya and miekg authored Mar 15, 2021
1 parent 74ef6e0 commit 7651e6c
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 1 deletion.
1 change: 1 addition & 0 deletions core/dnsserver/zdirectives.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var Directives = []string{
"rewrite",
"dnssec",
"autopath",
"minimal",
"template",
"transfer",
"hosts",
Expand Down
1 change: 1 addition & 0 deletions core/plugin/zplugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
_ "github.com/coredns/coredns/plugin/loop"
_ "github.com/coredns/coredns/plugin/metadata"
_ "github.com/coredns/coredns/plugin/metrics"
_ "github.com/coredns/coredns/plugin/minimal"
_ "github.com/coredns/coredns/plugin/nsid"
_ "github.com/coredns/coredns/plugin/pprof"
_ "github.com/coredns/coredns/plugin/ready"
Expand Down
1 change: 1 addition & 0 deletions plugin.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ cache:cache
rewrite:rewrite
dnssec:dnssec
autopath:autopath
minimal:minimal
template:template
transfer:transfer
hosts:hosts
Expand Down
36 changes: 36 additions & 0 deletions plugin/minimal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# minimal

## Name

*minimal* - minimizes size of the DNS response message whenever possible.

## Description

The *minimal* plugin tries to minimize the size of the response. Depending on the response type it
removes resource records from the AUTHORITY and ADDITIONAL sections.

Specifically this plugin looks at successful responses (this excludes negative responses, i.e.
nodata or name error). If the successful response isn't a delegation only the RRs in the answer
section are written to the client.

## Syntax

~~~ txt
minimal
~~~

## Examples

Enable minimal responses:

~~~ corefile
example.org {
whoami
forward . 8.8.8.8
minimal
}
~~~

## See Also

[BIND 9 Configuration Reference](https://bind9.readthedocs.io/en/latest/reference.html#boolean-options)
54 changes: 54 additions & 0 deletions plugin/minimal/minimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package minimal

import (
"context"
"fmt"
"time"

"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/nonwriter"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/miekg/dns"
)

// minimalHandler implements the plugin.Handler interface.
type minimalHandler struct {
Next plugin.Handler
}

func (m *minimalHandler) Name() string { return "minimal" }

func (m *minimalHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
nw := nonwriter.New(w)

rcode, err := plugin.NextOrFailure(m.Name(), m.Next, ctx, nw, r)
if err != nil {
return rcode, err
}

ty, _ := response.Typify(nw.Msg, time.Now().UTC())
cl := response.Classify(ty)

// if response is Denial or Error pass through also if the type is Delegation pass through
if cl == response.Denial || cl == response.Error || ty == response.Delegation {
w.WriteMsg(nw.Msg)
return 0, nil
}
if ty != response.NoError {
w.WriteMsg(nw.Msg)
return 0, plugin.Error("minimal", fmt.Errorf("unhandled response type %q for %q", ty, nw.Msg.Question[0].Name))
}

// copy over the original Msg params, deep copy not required as RRs are not modified
d := &dns.Msg{
MsgHdr: nw.Msg.MsgHdr,
Compress: nw.Msg.Compress,
Question: nw.Msg.Question,
Answer: nw.Msg.Answer,
Ns: nil,
Extra: nil,
}

w.WriteMsg(d)
return 0, nil
}
152 changes: 152 additions & 0 deletions plugin/minimal/minimal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package minimal

import (
"context"
"testing"

"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
)

// testHandler implements plugin.Handler and will be used to create a stub handler for the test
type testHandler struct {
Response *test.Case
Next plugin.Handler
}

func (t *testHandler) Name() string { return "test-handler" }

func (t *testHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
d := new(dns.Msg)
d.SetReply(r)
if t.Response != nil {
d.Answer = t.Response.Answer
d.Ns = t.Response.Ns
d.Extra = t.Response.Extra
d.Rcode = t.Response.Rcode
}
w.WriteMsg(d)
return 0, nil
}

func TestMinimizeResponse(t *testing.T) {
baseAnswer := []dns.RR{
test.A("example.com. 293 IN A 142.250.76.46"),
}
baseNs := []dns.RR{
test.NS("example.com. 157127 IN NS ns2.example.com."),
test.NS("example.com. 157127 IN NS ns1.example.com."),
test.NS("example.com. 157127 IN NS ns3.example.com."),
test.NS("example.com. 157127 IN NS ns4.example.com."),
}

baseExtra := []dns.RR{
test.A("ns2.example.com. 316273 IN A 216.239.34.10"),
test.AAAA("ns2.example.com. 157127 IN AAAA 2001:4860:4802:34::a"),
test.A("ns3.example.com. 316274 IN A 216.239.36.10"),
test.AAAA("ns3.example.com. 157127 IN AAAA 2001:4860:4802:36::a"),
test.A("ns1.example.com. 165555 IN A 216.239.32.10"),
test.AAAA("ns1.example.com. 165555 IN AAAA 2001:4860:4802:32::a"),
test.A("ns4.example.com. 190188 IN A 216.239.38.10"),
test.AAAA("ns4.example.com. 157127 IN AAAA 2001:4860:4802:38::a"),
}

tests := []struct {
active bool
original test.Case
minimal test.Case
}{
{ // minimization possible NoError case
original: test.Case{
Answer: baseAnswer,
Ns: nil,
Extra: baseExtra,
Rcode: 0,
},
minimal: test.Case{
Answer: baseAnswer,
Ns: nil,
Extra: nil,
Rcode: 0,
},
},
{ // delegate response case
original: test.Case{
Answer: nil,
Ns: baseNs,
Extra: baseExtra,
Rcode: 0,
},
minimal: test.Case{
Answer: nil,
Ns: baseNs,
Extra: baseExtra,
Rcode: 0,
},
}, { // negative response case
original: test.Case{
Answer: baseAnswer,
Ns: baseNs,
Extra: baseExtra,
Rcode: 2,
},
minimal: test.Case{
Answer: baseAnswer,
Ns: baseNs,
Extra: baseExtra,
Rcode: 2,
},
},
}

for i, tc := range tests {
req := new(dns.Msg)
req.SetQuestion("example.com", dns.TypeA)

tHandler := &testHandler{
Response: &tc.original,
Next: nil,
}
o := &minimalHandler{Next: tHandler}
rec := dnstest.NewRecorder(&test.ResponseWriter{})
_, err := o.ServeDNS(context.TODO(), rec, req)

if err != nil {
t.Errorf("Expected no error, but got %q", err)
}

if len(tc.minimal.Answer) != len(rec.Msg.Answer) {
t.Errorf("Test %d: Expected %d Answer, but got %d", i, len(tc.minimal.Answer), len(req.Answer))
continue
}
if len(tc.minimal.Ns) != len(rec.Msg.Ns) {
t.Errorf("Test %d: Expected %d Ns, but got %d", i, len(tc.minimal.Ns), len(req.Ns))
continue
}

if len(tc.minimal.Extra) != len(rec.Msg.Extra) {
t.Errorf("Test %d: Expected %d Extras, but got %d", i, len(tc.minimal.Extra), len(req.Extra))
continue
}

for j, a := range rec.Msg.Answer {
if tc.minimal.Answer[j].String() != a.String() {
t.Errorf("Test %d: Expected Answer %d to be %v, but got %v", i, j, tc.minimal.Answer[j], a)
}
}

for j, a := range rec.Msg.Ns {
if tc.minimal.Ns[j].String() != a.String() {
t.Errorf("Test %d: Expected NS %d to be %v, but got %v", i, j, tc.minimal.Ns[j], a)
}
}

for j, a := range rec.Msg.Extra {
if tc.minimal.Extra[j].String() != a.String() {
t.Errorf("Test %d: Expected Extra %d to be %v, but got %v", i, j, tc.minimal.Extra[j], a)
}
}
}
}
24 changes: 24 additions & 0 deletions plugin/minimal/setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package minimal

import (
"github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
)

func init() {
plugin.Register("minimal", setup)
}

func setup(c *caddy.Controller) error {
c.Next()
if c.NextArg() {
return plugin.Error("minimal", c.ArgErr())
}

dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return &minimalHandler{Next: next}
})

return nil
}
19 changes: 19 additions & 0 deletions plugin/minimal/setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package minimal

import (
"testing"

"github.com/coredns/caddy"
)

func TestSetup(t *testing.T) {
c := caddy.NewTestController("dns", `minimal-response`)
if err := setup(c); err != nil {
t.Fatalf("Expected no errors, but got: %v", err)
}

c = caddy.NewTestController("dns", `minimal-response example.org`)
if err := setup(c); err == nil {
t.Fatalf("Expected errors, but got: %v", err)
}
}
2 changes: 1 addition & 1 deletion plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (f HandlerFunc) Name() string { return "handlerfunc" }
// Error returns err with 'plugin/name: ' prefixed to it.
func Error(name string, err error) error { return fmt.Errorf("%s/%s: %s", "plugin", name, err) }

// NextOrFailure calls next.ServeDNS when next is not nil, otherwise it will return, a ServerFailure and a nil error.
// NextOrFailure calls next.ServeDNS when next is not nil, otherwise it will return, a ServerFailure and a `no next plugin found` error.
func NextOrFailure(name string, next Handler, ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { // nolint: golint
if next != nil {
if span := ot.SpanFromContext(ctx); span != nil {
Expand Down

0 comments on commit 7651e6c

Please sign in to comment.