plugin/header: Add support for query modification (#5548) (#5556)

This commit is contained in:
Christoph Heer
2022-08-12 13:46:06 +02:00
committed by GitHub
parent c7fe4a0c4d
commit 5c1447e0b0
6 changed files with 176 additions and 51 deletions

View File

@@ -2,22 +2,24 @@
## Name ## Name
*header* - modifies the header for responses. *header* - modifies the header for queries and responses.
## Description ## Description
*header* ensures that the flags are in the desired state for responses. The modifications are made transparently for *header* ensures that the flags are in the desired state for queries and responses.
the client. The modifications are made transparently for the client and subsequent plugins.
## Syntax ## Syntax
~~~ ~~~
header { header {
ACTION FLAGS... [SELECTOR] ACTION FLAGS...
ACTION FLAGS... [SELECTOR] ACTION FLAGS...
} }
~~~ ~~~
* **SELECTOR** defines if the action should be applied on `query` or `response`. In future CoreDNS version the selector will be mandatory. For backwards compatibility the action will be applied on `response` if the selector is undefined.
* **ACTION** defines the state for DNS message header flags. Actions are evaluated in the order they are defined so last one has the * **ACTION** defines the state for DNS message header flags. Actions are evaluated in the order they are defined so last one has the
most precedence. Allowed values are: most precedence. Allowed values are:
* `set` * `set`
@@ -34,7 +36,7 @@ Make sure recursive available `ra` flag is set in all the responses:
~~~ corefile ~~~ corefile
. { . {
header { header {
set ra response set ra
} }
} }
~~~ ~~~
@@ -44,8 +46,18 @@ Make sure "recursion available" `ra` and "authoritative answer" `aa` flags are s
~~~ corefile ~~~ corefile
. { . {
header { header {
set ra aa response set ra aa
clear rd response clear rd
}
}
~~~
Make sure "recursion desired" `rd` is set for all subsequent plugins::
~~~ corefile
. {
header {
query set rd
} }
} }
~~~ ~~~

View File

@@ -8,15 +8,18 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
// Header modifies dns.MsgHdr in the responses // Header modifies flags of dns.MsgHdr in queries and / or responses
type Header struct { type Header struct {
Rules []Rule QueryRules []Rule
Next plugin.Handler ResponseRules []Rule
Next plugin.Handler
} }
// ServeDNS implements the plugin.Handler interface. // ServeDNS implements the plugin.Handler interface.
func (h Header) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { func (h Header) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
wr := ResponseHeaderWriter{ResponseWriter: w, Rules: h.Rules} applyRules(r, h.QueryRules)
wr := ResponseHeaderWriter{ResponseWriter: w, Rules: h.ResponseRules}
return plugin.NextOrFailure(h.Name(), h.Next, ctx, &wr, r) return plugin.NextOrFailure(h.Name(), h.Next, ctx, &wr, r)
} }

View File

@@ -26,18 +26,7 @@ type ResponseHeaderWriter struct {
// WriteMsg implements the dns.ResponseWriter interface. // WriteMsg implements the dns.ResponseWriter interface.
func (r *ResponseHeaderWriter) WriteMsg(res *dns.Msg) error { func (r *ResponseHeaderWriter) WriteMsg(res *dns.Msg) error {
// handle all supported flags applyRules(res, r.Rules)
for _, rule := range r.Rules {
switch rule.Flag {
case authoritative:
res.Authoritative = rule.State
case recursionAvailable:
res.RecursionAvailable = rule.State
case recursionDesired:
res.RecursionDesired = rule.State
}
}
return r.ResponseWriter.WriteMsg(res) return r.ResponseWriter.WriteMsg(res)
} }
@@ -90,3 +79,17 @@ func newRules(key string, args []string) ([]Rule, error) {
return rules, nil return rules, nil
} }
func applyRules(res *dns.Msg, rules []Rule) {
// handle all supported flags
for _, rule := range rules {
switch rule.Flag {
case authoritative:
res.Authoritative = rule.State
case recursionAvailable:
res.RecursionAvailable = rule.State
case recursionDesired:
res.RecursionDesired = rule.State
}
}
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
) )
func TestHeader(t *testing.T) { func TestHeaderResponseRules(t *testing.T) {
wr := dnstest.NewRecorder(&test.ResponseWriter{}) wr := dnstest.NewRecorder(&test.ResponseWriter{})
next := plugin.HandlerFunc(func(ctx context.Context, writer dns.ResponseWriter, msg *dns.Msg) (int, error) { next := plugin.HandlerFunc(func(ctx context.Context, writer dns.ResponseWriter, msg *dns.Msg) (int, error) {
writer.WriteMsg(msg) writer.WriteMsg(msg)
@@ -25,8 +25,8 @@ func TestHeader(t *testing.T) {
}{ }{
{ {
handler: Header{ handler: Header{
Rules: []Rule{{Flag: recursionAvailable, State: true}}, ResponseRules: []Rule{{Flag: recursionAvailable, State: true}},
Next: next, Next: next,
}, },
got: func(msg *dns.Msg) bool { got: func(msg *dns.Msg) bool {
return msg.RecursionAvailable return msg.RecursionAvailable
@@ -35,18 +35,18 @@ func TestHeader(t *testing.T) {
}, },
{ {
handler: Header{ handler: Header{
Rules: []Rule{{Flag: recursionAvailable, State: true}}, ResponseRules: []Rule{{Flag: recursionAvailable, State: false}},
Next: next, Next: next,
}, },
got: func(msg *dns.Msg) bool { got: func(msg *dns.Msg) bool {
return msg.RecursionAvailable return msg.RecursionAvailable
}, },
expected: true, expected: false,
}, },
{ {
handler: Header{ handler: Header{
Rules: []Rule{{Flag: recursionDesired, State: true}}, ResponseRules: []Rule{{Flag: recursionDesired, State: true}},
Next: next, Next: next,
}, },
got: func(msg *dns.Msg) bool { got: func(msg *dns.Msg) bool {
return msg.RecursionDesired return msg.RecursionDesired
@@ -55,8 +55,8 @@ func TestHeader(t *testing.T) {
}, },
{ {
handler: Header{ handler: Header{
Rules: []Rule{{Flag: authoritative, State: true}}, ResponseRules: []Rule{{Flag: authoritative, State: true}},
Next: next, Next: next,
}, },
got: func(msg *dns.Msg) bool { got: func(msg *dns.Msg) bool {
return msg.Authoritative return msg.Authoritative
@@ -80,3 +80,73 @@ func TestHeader(t *testing.T) {
} }
} }
} }
func TestHeaderQueryRules(t *testing.T) {
wr := dnstest.NewRecorder(&test.ResponseWriter{})
next := plugin.HandlerFunc(func(ctx context.Context, writer dns.ResponseWriter, msg *dns.Msg) (int, error) {
writer.WriteMsg(msg)
return dns.RcodeSuccess, nil
})
tests := []struct {
handler plugin.Handler
got func(msg *dns.Msg) bool
expected bool
}{
{
handler: Header{
QueryRules: []Rule{{Flag: recursionAvailable, State: true}},
Next: next,
},
got: func(msg *dns.Msg) bool {
return msg.RecursionAvailable
},
expected: true,
},
{
handler: Header{
QueryRules: []Rule{{Flag: recursionDesired, State: true}},
Next: next,
},
got: func(msg *dns.Msg) bool {
return msg.RecursionDesired
},
expected: true,
},
{
handler: Header{
QueryRules: []Rule{{Flag: recursionDesired, State: false}},
Next: next,
},
got: func(msg *dns.Msg) bool {
return msg.RecursionDesired
},
expected: false,
},
{
handler: Header{
QueryRules: []Rule{{Flag: authoritative, State: true}},
Next: next,
},
got: func(msg *dns.Msg) bool {
return msg.Authoritative
},
expected: true,
},
}
for i, tc := range tests {
m := new(dns.Msg)
_, err := tc.handler.ServeDNS(context.TODO(), wr, m)
if err != nil {
t.Errorf("Test %d: Expected no error, but got %s", i, err)
continue
}
if tc.got(m) != tc.expected {
t.Errorf("Test %d: Expected flag state=%t, but got %t", i, tc.expected, tc.got(m))
continue
}
}
}

View File

@@ -2,6 +2,7 @@ package header
import ( import (
"fmt" "fmt"
"strings"
"github.com/coredns/caddy" "github.com/coredns/caddy"
"github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/core/dnsserver"
@@ -11,39 +12,63 @@ import (
func init() { plugin.Register("header", setup) } func init() { plugin.Register("header", setup) }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
rules, err := parse(c) queryRules, responseRules, err := parse(c)
if err != nil { if err != nil {
return plugin.Error("header", err) return plugin.Error("header", err)
} }
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return Header{ return Header{
Rules: rules, QueryRules: queryRules,
Next: next, ResponseRules: responseRules,
Next: next,
} }
}) })
return nil return nil
} }
func parse(c *caddy.Controller) ([]Rule, error) { func parse(c *caddy.Controller) ([]Rule, []Rule, error) {
for c.Next() { for c.Next() {
var all []Rule var queryRules []Rule
var responseRules []Rule
for c.NextBlock() { for c.NextBlock() {
v := c.Val() selector := strings.ToLower(c.Val())
args := c.RemainingArgs()
// set up rules var action string
rules, err := newRules(v, args) if selector == "set" || selector == "clear" {
if err != nil { log.Warningf("The selector for header rule in line %d isn't explicit defined. "+
return nil, fmt.Errorf("seting up rule: %w", err) "Assume rule applies for selector 'response'. This syntax is deprecated. "+
"In future versions of CoreDNS the selector must be explicit defined.",
c.Line())
action = selector
selector = "response"
} else if selector == "query" || selector == "response" {
if c.NextArg() {
action = c.Val()
}
} else {
return nil, nil, fmt.Errorf("setting up rule: invalid selector=%s should be query or response", selector)
}
args := c.RemainingArgs()
rules, err := newRules(action, args)
if err != nil {
return nil, nil, fmt.Errorf("setting up rule: %w", err)
}
if selector == "response" {
responseRules = append(responseRules, rules...)
} else {
queryRules = append(queryRules, rules...)
} }
all = append(all, rules...)
} }
// return combined rules if len(queryRules) > 0 || len(responseRules) > 0 {
if len(all) > 0 { return queryRules, responseRules, nil
return all, nil
} }
} }
return nil, c.ArgErr() return nil, nil, c.ArgErr()
} }

View File

@@ -19,13 +19,25 @@ func TestSetupHeader(t *testing.T) {
}`, true, "invalid length for flags, at least one should be provided"}, }`, true, "invalid length for flags, at least one should be provided"},
{`header { {`header {
foo foo
}`, true, "invalid selector=foo should be query or response"},
{`header {
query foo
}`, true, "invalid length for flags, at least one should be provided"}, }`, true, "invalid length for flags, at least one should be provided"},
{`header { {`header {
foo bar query foo rd
}`, true, "unknown flag action=foo, should be set or clear"}, }`, true, "unknown flag action=foo, should be set or clear"},
{`header { {`header {
set ra set ra
}`, false, ""}, }`, false, ""},
{`header {
clear ra
}`, false, ""},
{`header {
query set rd
}`, false, ""},
{`header {
response set aa
}`, false, ""},
{`header { {`header {
set ra aa set ra aa
clear rd clear rd