diff --git a/plugin.go b/plugin.go index 391d6a1..8891dfb 100644 --- a/plugin.go +++ b/plugin.go @@ -75,7 +75,6 @@ func (p *RFC2136) Name() string { return "rfc2136" } // Anything else → pass through to Next (the auto plugin handles // queries against the zone files we maintain). func (p *RFC2136) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - log.Infof("ServeDNS: opcode=%d (UPDATE=5) qcount=%d", r.Opcode, len(r.Question)) if r.Opcode == dns.OpcodeUpdate { if err := p.checkTSIG(w, r); err != nil { log.Warningf("UPDATE rejected: %v", err) diff --git a/setup.go b/setup.go index e868c98..51cf9a8 100644 --- a/setup.go +++ b/setup.go @@ -11,6 +11,7 @@ import ( "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/plugin" clog "github.com/coredns/coredns/plugin/pkg/log" + "github.com/miekg/dns" ) // log is the package logger, scoped so messages are prefixed `[rfc2136]`. @@ -18,6 +19,59 @@ var log = clog.NewWithPlugin("rfc2136") func init() { plugin.Register("rfc2136", setup) + + // miekg/dns's default MsgAcceptFunc rejects UPDATE opcode messages + // with NOTIMP before they ever reach the plugin chain (see the + // comment in miekg/dns/acceptfunc.go: "Don't allow dynamic updates, + // because then the sections can contain a whole bunch of RRs"). + // + // CoreDNS constructs its dns.Server instances without setting a + // per-server MsgAcceptFunc, so the package-level default is the + // one that runs. We override it here at plugin init() time -- well + // before any dns.Server starts listening -- to permit UPDATE + // through. The plugin itself does proper validation in the + // UPDATE handler, so opening this gate doesn't lower security. + dns.DefaultMsgAcceptFunc = msgAcceptFunc +} + +// msgAcceptFunc mirrors miekg/dns's defaultMsgAcceptFunc but additionally +// allows OpcodeUpdate. For UPDATE messages, the conservative Ancount/ +// Nscount limits in the default function don't apply -- per RFC 2136 +// those sections (Prerequisite / Update) can carry many RRs. +func msgAcceptFunc(dh dns.Header) dns.MsgAcceptAction { + // Responses are silently ignored regardless of opcode (default behaviour). + if isResponse := dh.Bits&0x8000 != 0; isResponse { + return dns.MsgIgnore + } + + opcode := int(dh.Bits>>11) & 0xF + switch opcode { + case dns.OpcodeQuery, dns.OpcodeNotify, dns.OpcodeUpdate: + // allowed + default: + return dns.MsgRejectNotImplemented + } + + if dh.Qdcount != 1 { + return dns.MsgReject + } + + // UPDATE messages legitimately carry multiple RRs in the + // Prerequisite (Ancount) and Update (Nscount) sections -- skip the + // "exactly 1" check that the default function applies for queries. + if opcode != dns.OpcodeUpdate { + if dh.Ancount > 1 { + return dns.MsgReject + } + if dh.Nscount > 1 { + return dns.MsgReject + } + } + + if dh.Arcount > 2 { + return dns.MsgReject + } + return dns.MsgAccept } // setup is invoked by the CoreDNS plugin registry once per Corefile