Added minimal-responses plugin (#4417)

* 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>
This commit is contained in:
Soumya Ghosh Dastidar 2021-03-15 20:07:55 +05:30 committed by GitHub
parent 74ef6e00f1
commit 7651e6c4de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 289 additions and 1 deletions

View file

@ -37,6 +37,7 @@ var Directives = []string{
"rewrite",
"dnssec",
"autopath",
"minimal",
"template",
"transfer",
"hosts",

View file

@ -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"

View file

@ -46,6 +46,7 @@ cache:cache
rewrite:rewrite
dnssec:dnssec
autopath:autopath
minimal:minimal
template:template
transfer:transfer
hosts:hosts

36
plugin/minimal/README.md Normal file
View file

@ -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
plugin/minimal/minimal.go Normal file
View file

@ -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
}

View file

@ -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
plugin/minimal/setup.go Normal file
View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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 {