Skip to content

Commit

Permalink
Fix EDNS0 compliance
Browse files Browse the repository at this point in the history
Do SizeAndDo in the server (ScrubWriter) and remove all uses of this
from the plugins. Also *always* do it. This is to get into compliance
for https://dnsflagday.net/.

The pkg/edns0 now exports the EDNS0 options we understand; this is
exported to allow plugins add things there. The *rewrite* plugin used
this to add custom EDNS0 option codes that the server needs to
understand.

This also needs a new release of miekg/dns because it triggered a
race-condition that was basicly there forever.

See:
* miekg/dns#857
* miekg/dns#859

Running a test instance and pointing the https://ednscomp.isc.org/ednscomp
to it shows the tests are now fixed:

~~~
EDNS Compliance Tester
Checking: 'miek.nl' as at 2018-12-01T17:53:15Z

miek.nl. @147.75.204.203 (drone.coredns.io.): dns=ok edns=ok edns1=ok edns@512=ok ednsopt=ok edns1opt=ok do=ok ednsflags=ok docookie=ok edns512tcp=ok optlist=ok
miek.nl. @2604:1380:2002:a000::1 (drone.coredns.io.): dns=ok edns=ok edns1=ok edns@512=ok ednsopt=ok edns1opt=ok do=ok ednsflags=ok docookie=ok edns512tcp=ok optlist=ok

All Ok
Codes
ok - test passed.
~~~

Signed-off-by: Miek Gieben <miek@miek.nl>

Signed-off-by: Miek Gieben <miek@miek.nl>
  • Loading branch information
miekg committed Dec 5, 2018
1 parent 9a393ac commit a474858
Show file tree
Hide file tree
Showing 16 changed files with 113 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -31,7 +31,7 @@ godeps:
go get -u github.com/prometheus/client_golang/prometheus/promhttp
go get -u github.com/prometheus/client_golang/prometheus
(cd $(GOPATH)/src/github.com/mholt/caddy && git checkout -q v0.11.1)
(cd $(GOPATH)/src/github.com/miekg/dns && git checkout -q v1.1.0)
(cd $(GOPATH)/src/github.com/miekg/dns && git checkout -q v1.1.1)
(cd $(GOPATH)/src/github.com/prometheus/client_golang && git checkout -q v0.9.1)

.PHONY: travis
Expand Down
1 change: 0 additions & 1 deletion plugin/backend_lookup.go
Expand Up @@ -425,7 +425,6 @@ func BackendError(b ServiceBackend, zone string, rcode int, state request.Reques
m.Authoritative, m.RecursionAvailable = true, true
m.Ns, _ = SOA(b, zone, state, opt)

state.SizeAndDo(m)
state.W.WriteMsg(m)
// Return success as the rcode to signal we have written to the client.
return dns.RcodeSuccess, err
Expand Down
1 change: 0 additions & 1 deletion plugin/chaos/chaos.go
Expand Up @@ -46,7 +46,6 @@ func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
}
m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(hostname)}}}
}
state.SizeAndDo(m)
w.WriteMsg(m)
return 0, nil
}
Expand Down
1 change: 0 additions & 1 deletion plugin/dnssec/handler.go
Expand Up @@ -33,7 +33,6 @@ func (d Dnssec) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
if qname == z {
resp := d.getDNSKEY(state, z, do, server)
resp.Authoritative = true
state.SizeAndDo(resp)
w.WriteMsg(resp)
return dns.RcodeSuccess, nil
}
Expand Down
2 changes: 1 addition & 1 deletion plugin/dnssec/handler_test.go
Expand Up @@ -26,7 +26,7 @@ var dnssecTestCases = []test.Case{
test.DNSKEY("miek.nl. 3600 IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4"),
test.RRSIG("miek.nl. 3600 IN RRSIG DNSKEY 13 2 3600 20160503150844 20160425120844 18512 miek.nl. Iw/kNOyM"),
},
Extra: []dns.RR{test.OPT(4096, true)},
/* Extra: []dns.RR{test.OPT(4096, true)}, this has moved to the server and can't be test here */
},
}

Expand Down
1 change: 0 additions & 1 deletion plugin/erratic/erratic.go
Expand Up @@ -97,7 +97,6 @@ func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
time.Sleep(e.duration)
}

state.SizeAndDo(m)
w.WriteMsg(m)

return 0, nil
Expand Down
1 change: 0 additions & 1 deletion plugin/file/file.go
Expand Up @@ -51,7 +51,6 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative, m.RecursionAvailable = true, true
state.SizeAndDo(m)
w.WriteMsg(m)

log.Infof("Notify from %s for %s: checking transfer", state.IP(), zone)
Expand Down
1 change: 0 additions & 1 deletion plugin/log/log.go
Expand Up @@ -43,7 +43,6 @@ func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
} else {
answer := new(dns.Msg)
answer.SetRcode(r, rc)
state.SizeAndDo(answer)

vars.Report(ctx, state, vars.Dropped, rcode.ToString(rc), answer.Len(), time.Now())

Expand Down
27 changes: 27 additions & 0 deletions plugin/pkg/edns/edns.go
Expand Up @@ -3,10 +3,37 @@ package edns

import (
"errors"
"sync"

"github.com/miekg/dns"
)

var sup = &supported{m: make(map[uint16]struct{})}

type supported struct {
m map[uint16]struct{}
sync.RWMutex
}

// SetSupportedOption adds a new supported option the set of EDNS0 options that we supported. Plugins typically call
// this in their setup code to signal support for a new option.
// By default we supoort:
// dns.EDNS0NSID, dns.EDNS0EXPIRE, dns.EDNS0COOKIE, dns.EDNS0TCPKEEPALIVE, dns.EDNS0PADDING. These
// values are not in this map and checked directly in the server.
func SetSupportedOption(option uint16) {
sup.Lock()
sup.m[option] = struct{}{}
sup.Unlock()
}

// SupportedOption returns true if e is supported as an extra EDNS0 option.
func SupportedOption(option uint16) bool {
sup.RLock()
_, ok := sup.m[option]
sup.RUnlock()
return ok
}

// Version checks the EDNS version in the request. If error
// is nil everything is OK and we can invoke the plugin. If non-nil, the
// returned Msg is valid to be returned to the client (and should). For some
Expand Down
9 changes: 9 additions & 0 deletions plugin/rewrite/edns0.go
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/coredns/coredns/plugin/metadata"
"github.com/coredns/coredns/plugin/pkg/edns"
"github.com/coredns/coredns/request"

"github.com/miekg/dns"
Expand Down Expand Up @@ -159,6 +160,10 @@ func newEdns0LocalRule(mode, action, code, data string) (*edns0LocalRule, error)
return nil, err
}
}

// Add this code to the ones the server supports.
edns.SetSupportedOption(uint16(c))

return &edns0LocalRule{mode: mode, action: action, code: uint16(c), data: decoded}, nil
}

Expand All @@ -172,6 +177,10 @@ func newEdns0VariableRule(mode, action, code, variable string) (*edns0VariableRu
if !isValidVariable(variable) {
return nil, fmt.Errorf("unsupported variable name %q", variable)
}

// Add this code to the ones the server supports.
edns.SetSupportedOption(uint16(c))

return &edns0VariableRule{mode: mode, action: action, code: uint16(c), variable: variable}, nil
}

Expand Down
1 change: 0 additions & 1 deletion plugin/whoami/whoami.go
Expand Up @@ -49,7 +49,6 @@ func (wh Whoami) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)

a.Extra = []dns.RR{rr, srv}

state.SizeAndDo(a)
w.WriteMsg(a)

return 0, nil
Expand Down
31 changes: 31 additions & 0 deletions request/edns0.go
@@ -0,0 +1,31 @@
package request

import (
"github.com/coredns/coredns/plugin/pkg/edns"

"github.com/miekg/dns"
)

func supportedOptions(o []dns.EDNS0) []dns.EDNS0 {
var supported = make([]dns.EDNS0, 0, 3)
// For as long as possible try looking up in the map, because that need an Rlock.
for _, opt := range o {
switch code := opt.Option(); code {
case dns.EDNS0NSID:
fallthrough
case dns.EDNS0EXPIRE:
fallthrough
case dns.EDNS0COOKIE:
fallthrough
case dns.EDNS0TCPKEEPALIVE:
fallthrough
case dns.EDNS0PADDING:
supported = append(supported, opt)
default:
if edns.SupportedOption(code) {
supported = append(supported, opt)
}
}
}
return supported
}
12 changes: 9 additions & 3 deletions request/request.go
Expand Up @@ -194,7 +194,7 @@ func (r *Request) Size() int {
// SizeAndDo adds an OPT record that the reflects the intent from request.
// The returned bool indicated if an record was found and normalised.
func (r *Request) SizeAndDo(m *dns.Msg) bool {
o := r.Req.IsEdns0() // TODO(miek): speed this up
o := r.Req.IsEdns0()
if o == nil {
return false
}
Expand All @@ -208,6 +208,10 @@ func (r *Request) SizeAndDo(m *dns.Msg) bool {
mo.SetUDPSize(o.UDPSize())
mo.Hdr.Ttl &= 0xff00 // clear flags

if len(o.Option) > 0 {
o.Option = supportedOptions(o.Option)
}

if odo {
mo.SetDo()
}
Expand All @@ -219,6 +223,10 @@ func (r *Request) SizeAndDo(m *dns.Msg) bool {
o.SetVersion(0)
o.Hdr.Ttl &= 0xff00 // clear flags

if len(o.Option) > 0 {
o.Option = supportedOptions(o.Option)
}

if odo {
o.SetDo()
}
Expand Down Expand Up @@ -305,7 +313,6 @@ func (r *Request) Scrub(reply *dns.Msg) *dns.Msg {
}

if rl <= size {
r.SizeAndDo(reply)
return reply
}

Expand Down Expand Up @@ -341,7 +348,6 @@ func (r *Request) Scrub(reply *dns.Msg) *dns.Msg {
// this extra m-1 step does make it fit in the client's buffer however.
}

r.SizeAndDo(reply)
reply.Truncated = true
return reply
}
Expand Down
8 changes: 0 additions & 8 deletions request/request_test.go
Expand Up @@ -123,10 +123,6 @@ func TestRequestScrubExtraEdns0(t *testing.T) {
if reply.Truncated {
t.Errorf("Want scrub to not set truncated bit")
}
opt := reply.Extra[len(reply.Extra)-1]
if opt.Header().Rrtype != dns.TypeOPT {
t.Errorf("Last RR must be OPT record")
}
}

func TestRequestScrubExtraRegression(t *testing.T) {
Expand All @@ -153,10 +149,6 @@ func TestRequestScrubExtraRegression(t *testing.T) {
if reply.Truncated {
t.Errorf("Want scrub to not set truncated bit")
}
opt := reply.Extra[len(reply.Extra)-1]
if opt.Header().Rrtype != dns.TypeOPT {
t.Errorf("Last RR must be OPT record")
}
}

func TestTruncation(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions request/writer.go
Expand Up @@ -15,6 +15,8 @@ func NewScrubWriter(req *dns.Msg, w dns.ResponseWriter) *ScrubWriter { return &S
// scrub on the message m and will then write it to the client.
func (s *ScrubWriter) WriteMsg(m *dns.Msg) error {
state := Request{Req: s.req, W: s.ResponseWriter}

n := state.Scrub(m)
state.SizeAndDo(n)
return s.ResponseWriter.WriteMsg(n)
}
33 changes: 33 additions & 0 deletions test/edns0_test.go
@@ -0,0 +1,33 @@
package test

import (
"testing"

"github.com/miekg/dns"
)

func TestEDNS0(t *testing.T) {
corefile := `.:0 {
whoami
}
`

i, udp, _, err := CoreDNSServerAndPorts(corefile)
if err != nil {
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
}
defer i.Stop()

m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeSOA)
m.SetEdns0(4096, true)

resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %v", err)
}
opt := resp.Extra[len(resp.Extra)-1]
if opt.Header().Rrtype != dns.TypeOPT {
t.Errorf("Last RR must be OPT record")
}
}

0 comments on commit a474858

Please sign in to comment.