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:
parent
74ef6e00f1
commit
7651e6c4de
9 changed files with 289 additions and 1 deletions
|
@ -37,6 +37,7 @@ var Directives = []string{
|
||||||
"rewrite",
|
"rewrite",
|
||||||
"dnssec",
|
"dnssec",
|
||||||
"autopath",
|
"autopath",
|
||||||
|
"minimal",
|
||||||
"template",
|
"template",
|
||||||
"transfer",
|
"transfer",
|
||||||
"hosts",
|
"hosts",
|
||||||
|
|
|
@ -36,6 +36,7 @@ import (
|
||||||
_ "github.com/coredns/coredns/plugin/loop"
|
_ "github.com/coredns/coredns/plugin/loop"
|
||||||
_ "github.com/coredns/coredns/plugin/metadata"
|
_ "github.com/coredns/coredns/plugin/metadata"
|
||||||
_ "github.com/coredns/coredns/plugin/metrics"
|
_ "github.com/coredns/coredns/plugin/metrics"
|
||||||
|
_ "github.com/coredns/coredns/plugin/minimal"
|
||||||
_ "github.com/coredns/coredns/plugin/nsid"
|
_ "github.com/coredns/coredns/plugin/nsid"
|
||||||
_ "github.com/coredns/coredns/plugin/pprof"
|
_ "github.com/coredns/coredns/plugin/pprof"
|
||||||
_ "github.com/coredns/coredns/plugin/ready"
|
_ "github.com/coredns/coredns/plugin/ready"
|
||||||
|
|
|
@ -46,6 +46,7 @@ cache:cache
|
||||||
rewrite:rewrite
|
rewrite:rewrite
|
||||||
dnssec:dnssec
|
dnssec:dnssec
|
||||||
autopath:autopath
|
autopath:autopath
|
||||||
|
minimal:minimal
|
||||||
template:template
|
template:template
|
||||||
transfer:transfer
|
transfer:transfer
|
||||||
hosts:hosts
|
hosts:hosts
|
||||||
|
|
36
plugin/minimal/README.md
Normal file
36
plugin/minimal/README.md
Normal 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
54
plugin/minimal/minimal.go
Normal 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
|
||||||
|
}
|
152
plugin/minimal/minimal_test.go
Normal file
152
plugin/minimal/minimal_test.go
Normal 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
24
plugin/minimal/setup.go
Normal 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
|
||||||
|
}
|
19
plugin/minimal/setup_test.go
Normal file
19
plugin/minimal/setup_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ func (f HandlerFunc) Name() string { return "handlerfunc" }
|
||||||
// Error returns err with 'plugin/name: ' prefixed to it.
|
// 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) }
|
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
|
func NextOrFailure(name string, next Handler, ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { // nolint: golint
|
||||||
if next != nil {
|
if next != nil {
|
||||||
if span := ot.SpanFromContext(ctx); span != nil {
|
if span := ot.SpanFromContext(ctx); span != nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue