Commit 7651e6c4 authored by Soumya Ghosh Dastidar's avatar Soumya Ghosh Dastidar Committed by GitHub

Added minimal-responses plugin (#4417)

* Added minimal-responses plugin
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Removed unnecessary comments

* Updated tests
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Reformated imports
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Updated package name
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Removed  unnecessary comments
Co-authored-by: default avatarMiek Gieben <miek@miek.nl>

* Added changes
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* updated
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Updated comment for NextOrFailure
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Updated to test.Case for testing
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>

* Formated imports using goimports
Signed-off-by: default avatarSoumya Ghosh Dastidar <gdsoumya@gmail.com>
Co-authored-by: default avatarMiek Gieben <miek@miek.nl>
parent 74ef6e00
......@@ -37,6 +37,7 @@ var Directives = []string{
"rewrite",
"dnssec",
"autopath",
"minimal",
"template",
"transfer",
"hosts",
......
......@@ -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"
......
......@@ -46,6 +46,7 @@ cache:cache
rewrite:rewrite
dnssec:dnssec
autopath:autopath
minimal:minimal
template:template
transfer:transfer
hosts:hosts
......
# 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)
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
}
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)
}
}
}
}
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
}
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)
}
}
......@@ -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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment