Add bufsize plugin for preparing the DNS Flag Day and avoiding IP fragmentation (#3401)

* add bufsize plugin

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* add docstring and comment

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* delete stdout messages when get an error

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* change to context.Background from TODO

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* define default bufsize as defaultBufSize constant

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* fix some comments

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* function name change: parse

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>

* function name change: parse

Signed-off-by: ykhr53 <yukihira.lab@gmail.com>
This commit is contained in:
Kohei Yoshida 2019-11-10 08:10:12 +00:00 committed by Miek Gieben
parent 113783ed91
commit e23a34abb4
8 changed files with 234 additions and 0 deletions

View file

@ -15,6 +15,7 @@ var Directives = []string{
"tls", "tls",
"reload", "reload",
"nsid", "nsid",
"bufsize",
"root", "root",
"bind", "bind",
"debug", "debug",

View file

@ -11,6 +11,7 @@ import (
_ "github.com/coredns/coredns/plugin/autopath" _ "github.com/coredns/coredns/plugin/autopath"
_ "github.com/coredns/coredns/plugin/azure" _ "github.com/coredns/coredns/plugin/azure"
_ "github.com/coredns/coredns/plugin/bind" _ "github.com/coredns/coredns/plugin/bind"
_ "github.com/coredns/coredns/plugin/bufsize"
_ "github.com/coredns/coredns/plugin/cache" _ "github.com/coredns/coredns/plugin/cache"
_ "github.com/coredns/coredns/plugin/cancel" _ "github.com/coredns/coredns/plugin/cancel"
_ "github.com/coredns/coredns/plugin/chaos" _ "github.com/coredns/coredns/plugin/chaos"

View file

@ -24,6 +24,7 @@ cancel:cancel
tls:tls tls:tls
reload:reload reload:reload
nsid:nsid nsid:nsid
bufsize:bufsize
root:root root:root
bind:bind bind:bind
debug:debug debug:debug

30
plugin/bufsize/README.md Normal file
View file

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

31
plugin/bufsize/bufsize.go Normal file
View file

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

View file

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

52
plugin/bufsize/setup.go Normal file
View file

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

View file

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