diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go index 8e2153aad..61d96c633 100644 --- a/core/dnsserver/zdirectives.go +++ b/core/dnsserver/zdirectives.go @@ -15,6 +15,7 @@ var Directives = []string{ "tls", "reload", "nsid", + "bufsize", "root", "bind", "debug", diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go index 79892235f..90267f29b 100644 --- a/core/plugin/zplugin.go +++ b/core/plugin/zplugin.go @@ -11,6 +11,7 @@ import ( _ "github.com/coredns/coredns/plugin/autopath" _ "github.com/coredns/coredns/plugin/azure" _ "github.com/coredns/coredns/plugin/bind" + _ "github.com/coredns/coredns/plugin/bufsize" _ "github.com/coredns/coredns/plugin/cache" _ "github.com/coredns/coredns/plugin/cancel" _ "github.com/coredns/coredns/plugin/chaos" diff --git a/plugin.cfg b/plugin.cfg index 47b9ecb51..d40cf7a33 100644 --- a/plugin.cfg +++ b/plugin.cfg @@ -24,6 +24,7 @@ cancel:cancel tls:tls reload:reload nsid:nsid +bufsize:bufsize root:root bind:bind debug:debug diff --git a/plugin/bufsize/README.md b/plugin/bufsize/README.md new file mode 100644 index 000000000..bd1d232d1 --- /dev/null +++ b/plugin/bufsize/README.md @@ -0,0 +1,30 @@ +# bufsize +## Name +*bufsize* - sizes EDNS0 buffer size to prevent IP fragmentation. + +## Description +*bufsize* limits a requester's UDP payload size. +It prevents IP fragmentation so that to deal with DNS vulnerability. + +## Syntax +```txt +bufsize [SIZE] +``` + +**[SIZE]** is an int value for setting the buffer size. +The default value is 512, and the value must be within 512 - 4096. +Only one argument is acceptable, and it covers both IPv4 and IPv6. + +## Examples +```corefile +. { + bufsize 512 + forward . 172.31.0.10 + log +} +``` + +If you run a resolver on 172.31.0.10, the buffer size of incoming query on the resolver will be set to 512 bytes. + +## Considerations +For now, if a client does not use EDNS, this plugin adds OPT RR. \ No newline at end of file diff --git a/plugin/bufsize/bufsize.go b/plugin/bufsize/bufsize.go new file mode 100644 index 000000000..1522be894 --- /dev/null +++ b/plugin/bufsize/bufsize.go @@ -0,0 +1,31 @@ +// Package bufsize implements a plugin that modifies EDNS0 buffer size. +package bufsize + +import ( + "context" + + "github.com/coredns/coredns/plugin" + + "github.com/miekg/dns" +) + +// Bufsize implements bufsize plugin. +type Bufsize struct { + Next plugin.Handler + Size int +} + +// ServeDNS implements the plugin.Handler interface. +func (buf Bufsize) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + if option := r.IsEdns0(); option != nil { + option.SetUDPSize(uint16(buf.Size)) + } else { + // If a client does not use EDNS, add it + r.SetEdns0(uint16(buf.Size), false) + } + + return plugin.NextOrFailure(buf.Name(), buf.Next, ctx, w, r) +} + +// Name implements the Handler interface. +func (buf Bufsize) Name() string { return "bufsize" } diff --git a/plugin/bufsize/bufsize_test.go b/plugin/bufsize/bufsize_test.go new file mode 100644 index 000000000..3d714d2f1 --- /dev/null +++ b/plugin/bufsize/bufsize_test.go @@ -0,0 +1,72 @@ +package bufsize + +import ( + "context" + "testing" + + "github.com/coredns/coredns/plugin" + "github.com/coredns/coredns/plugin/test" + "github.com/coredns/coredns/plugin/whoami" + + "github.com/miekg/dns" +) + +func TestBufsize(t *testing.T) { + em := Bufsize{ + Size: 512, + } + + tests := []struct { + next plugin.Handler + qname string + inputBufsize uint16 + outgoingBufsize uint16 + expectedErr error + }{ + // This plugin is responsible for limiting outgoing query's bufize + { + next: whoami.Whoami{}, + qname: ".", + inputBufsize: 1200, + outgoingBufsize: 512, + expectedErr: nil, + }, + // If EDNS is not enabled, this plugin adds it + { + next: whoami.Whoami{}, + qname: ".", + outgoingBufsize: 512, + expectedErr: nil, + }, + } + + for i, tc := range tests { + req := new(dns.Msg) + req.SetQuestion(dns.Fqdn(tc.qname), dns.TypeA) + req.Question[0].Qclass = dns.ClassINET + em.Next = tc.next + + if tc.inputBufsize != 0 { + req.SetEdns0(tc.inputBufsize, false) + } + + _, err := em.ServeDNS(context.Background(), &test.ResponseWriter{}, req) + + if err != tc.expectedErr { + t.Errorf("Test %d: Expected error is %v, but got %v", i, tc.expectedErr, err) + } + + if tc.outgoingBufsize != 0 { + for _, extra := range req.Extra { + if option, ok := extra.(*dns.OPT); ok { + b := option.UDPSize() + if b != tc.outgoingBufsize { + t.Errorf("Test %d: Expected outgoing bufsize is %d, but got %d", i, tc.outgoingBufsize, b) + } + } else { + t.Errorf("Test %d: Not found OPT RR.", i) + } + } + } + } +} diff --git a/plugin/bufsize/setup.go b/plugin/bufsize/setup.go new file mode 100644 index 000000000..28586c76e --- /dev/null +++ b/plugin/bufsize/setup.go @@ -0,0 +1,52 @@ +package bufsize + +import ( + "strconv" + + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/plugin" + + "github.com/caddyserver/caddy" +) + +func init() { plugin.Register("bufsize", setup) } + +func setup(c *caddy.Controller) error { + bufsize, err := parse(c) + if err != nil { + return plugin.Error("bufsize", err) + } + + dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { + return Bufsize{Next: next, Size: bufsize} + }) + + return nil +} + +func parse(c *caddy.Controller) (int, error) { + const defaultBufSize = 512 + for c.Next() { + args := c.RemainingArgs() + switch len(args) { + case 0: + // Nothing specified; use 512 as default + return defaultBufSize, nil + case 1: + // Specified value is needed to verify + bufsize, err := strconv.Atoi(args[0]) + if err != nil { + return -1, plugin.Error("bufsize", c.ArgErr()) + } + // Follows RFC 6891 + if bufsize < 512 || bufsize > 4096 { + return -1, plugin.Error("bufsize", c.ArgErr()) + } + return bufsize, nil + default: + // Only 1 argument is acceptable + return -1, plugin.Error("bufsize", c.ArgErr()) + } + } + return -1, plugin.Error("bufsize", c.ArgErr()) +} diff --git a/plugin/bufsize/setup_test.go b/plugin/bufsize/setup_test.go new file mode 100644 index 000000000..4d1705a05 --- /dev/null +++ b/plugin/bufsize/setup_test.go @@ -0,0 +1,46 @@ +package bufsize + +import ( + "strings" + "testing" + + "github.com/caddyserver/caddy" +) + +func TestSetupBufsize(t *testing.T) { + tests := []struct { + input string + shouldErr bool + expectedData int + expectedErrContent string // substring from the expected error. Empty for positive cases. + }{ + {`bufsize`, false, 512, ""}, + {`bufsize "1232"`, false, 1232, ""}, + {`bufsize "5000"`, true, -1, "plugin"}, + {`bufsize "512 512"`, true, -1, "plugin"}, + {`bufsize "abc123"`, true, -1, "plugin"}, + } + + for i, test := range tests { + c := caddy.NewTestController("dns", test.input) + bufsize, err := parse(c) + + if test.shouldErr && err == nil { + t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) + } + + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Error found for input %s. Error: %v", i, test.input, err) + } + + if !strings.Contains(err.Error(), test.expectedErrContent) { + t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) + } + } + + if !test.shouldErr && bufsize != test.expectedData { + t.Errorf("Test %d: Bufsize not correctly set for input %s. Expected: %d, actual: %d", i, test.input, test.expectedData, bufsize) + } + } +}