aboutsummaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
authorGravatar Soumya Ghosh Dastidar <44349253+gdsoumya@users.noreply.github.com> 2021-03-15 20:07:55 +0530
committerGravatar GitHub <noreply@github.com> 2021-03-15 15:37:55 +0100
commit7651e6c4de38a1f41dc8bacba16656ef4fbcd008 (patch)
tree4672412c5d5aec9914cbde68a4ea26eda35c2427 /plugin
parent74ef6e00f189e6590185d0625d8cc0767511de46 (diff)
downloadcoredns-7651e6c4de38a1f41dc8bacba16656ef4fbcd008.tar.gz
coredns-7651e6c4de38a1f41dc8bacba16656ef4fbcd008.tar.zst
coredns-7651e6c4de38a1f41dc8bacba16656ef4fbcd008.zip
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>
Diffstat (limited to 'plugin')
-rw-r--r--plugin/minimal/README.md36
-rw-r--r--plugin/minimal/minimal.go54
-rw-r--r--plugin/minimal/minimal_test.go152
-rw-r--r--plugin/minimal/setup.go24
-rw-r--r--plugin/minimal/setup_test.go19
-rw-r--r--plugin/plugin.go2
6 files changed, 286 insertions, 1 deletions
diff --git a/plugin/minimal/README.md b/plugin/minimal/README.md
new file mode 100644
index 000000000..e667fee25
--- /dev/null
+++ b/plugin/minimal/README.md
@@ -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)
diff --git a/plugin/minimal/minimal.go b/plugin/minimal/minimal.go
new file mode 100644
index 000000000..6b5a7a731
--- /dev/null
+++ b/plugin/minimal/minimal.go
@@ -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
+}
diff --git a/plugin/minimal/minimal_test.go b/plugin/minimal/minimal_test.go
new file mode 100644
index 000000000..b579a9970
--- /dev/null
+++ b/plugin/minimal/minimal_test.go
@@ -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)
+ }
+ }
+ }
+}
diff --git a/plugin/minimal/setup.go b/plugin/minimal/setup.go
new file mode 100644
index 000000000..1bf37a652
--- /dev/null
+++ b/plugin/minimal/setup.go
@@ -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
+}
diff --git a/plugin/minimal/setup_test.go b/plugin/minimal/setup_test.go
new file mode 100644
index 000000000..49341c441
--- /dev/null
+++ b/plugin/minimal/setup_test.go
@@ -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)
+ }
+}
diff --git a/plugin/plugin.go b/plugin/plugin.go
index ee1bf8429..9bac48885 100644
--- a/plugin/plugin.go
+++ b/plugin/plugin.go
@@ -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 {