aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Francois Tur <ftur@infoblox.com> 2018-11-13 14:20:49 -0500
committerGravatar John Belamaric <jbelamaric@google.com> 2018-11-13 11:20:49 -0800
commit94c9aae323bc544fa96693d24a24a4059d6f9b78 (patch)
tree705e05b4c99f5160b12e11dd0f1eb38825de4691
parent35c5474660ab091b96c26e9b152033139d3f6117 (diff)
downloadcoredns-94c9aae323bc544fa96693d24a24a4059d6f9b78.tar.gz
coredns-94c9aae323bc544fa96693d24a24a4059d6f9b78.tar.zst
coredns-94c9aae323bc544fa96693d24a24a4059d6f9b78.zip
plugin/log - Support for Metadata (#2251)
* - add metadata support to Log * - adapt ctx after rebase
Diffstat (limited to '')
-rw-r--r--plugin/log/README.md5
-rw-r--r--plugin/log/log.go2
-rw-r--r--plugin/pkg/replacer/replacer.go61
-rw-r--r--plugin/pkg/replacer/replacer_test.go71
-rw-r--r--plugin/rewrite/condition.go3
5 files changed, 123 insertions, 19 deletions
diff --git a/plugin/log/README.md b/plugin/log/README.md
index 48e5acb6c..f2d89741c 100644
--- a/plugin/log/README.md
+++ b/plugin/log/README.md
@@ -61,6 +61,7 @@ The following place holders are supported:
* `{class}`: qclass of the request
* `{proto}`: protocol used (tcp or udp)
* `{remote}`: client's IP address, for IPv6 addresses these are enclosed in brackets: `[::1]`
+* `{local}`: server's IP address, for IPv6 addresses these are enclosed in brackets: `[::1]`
* `{size}`: request size in bytes
* `{port}`: client's port
* `{duration}`: response duration
@@ -72,6 +73,10 @@ The following place holders are supported:
* `{>do}`: is the EDNS0 DO (DNSSEC OK) bit set in the query
* `{>id}`: query ID
* `{>opcode}`: query OPCODE
+* `{/[LABEL]}`: any metadata label is accepted as a place holder if it is enclosed between `{/` and `}`.
+the place holder will be replaced by the corresponding metadata value or the default value `-` if label is not defined.
+
+
The default Common Log Format is:
diff --git a/plugin/log/log.go b/plugin/log/log.go
index 685c55191..98f00d762 100644
--- a/plugin/log/log.go
+++ b/plugin/log/log.go
@@ -57,7 +57,7 @@ func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
// If we don't set up a class in config, the default "all" will be added
// and we shouldn't have an empty rule.Class.
if rule.Class[response.All] || rule.Class[class] {
- rep := replacer.New(r, rrw, CommonLogEmptyValue)
+ rep := replacer.New(ctx, r, rrw, CommonLogEmptyValue)
clog.Infof(rep.Replace(rule.Format))
}
diff --git a/plugin/pkg/replacer/replacer.go b/plugin/pkg/replacer/replacer.go
index 695e9e392..8c8032c76 100644
--- a/plugin/pkg/replacer/replacer.go
+++ b/plugin/pkg/replacer/replacer.go
@@ -1,10 +1,12 @@
package replacer
import (
+ "context"
"strconv"
"strings"
"time"
+ "github.com/coredns/coredns/plugin/metadata"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/request"
@@ -21,6 +23,7 @@ type Replacer interface {
}
type replacer struct {
+ ctx context.Context
replacements map[string]string
emptyValue string
}
@@ -31,9 +34,10 @@ type replacer struct {
// values into the replacer. rr may be nil if it is not
// available. emptyValue should be the string that is used
// in place of empty string (can still be empty string).
-func New(r *dns.Msg, rr *dnstest.Recorder, emptyValue string) Replacer {
+func New(ctx context.Context, r *dns.Msg, rr *dnstest.Recorder, emptyValue string) Replacer {
req := request.Request{W: rr, Req: r}
rep := replacer{
+ ctx: ctx,
replacements: map[string]string{
"{type}": req.Type(),
"{name}": req.Name(),
@@ -43,6 +47,7 @@ func New(r *dns.Msg, rr *dnstest.Recorder, emptyValue string) Replacer {
"{size}": strconv.Itoa(req.Len()),
"{remote}": addrToRFC3986(req.IP()),
"{port}": req.Port(),
+ "{local}": addrToRFC3986(req.LocalIP()),
},
emptyValue: emptyValue,
}
@@ -71,23 +76,36 @@ func New(r *dns.Msg, rr *dnstest.Recorder, emptyValue string) Replacer {
// Replace performs a replacement of values on s and returns
// the string with the replaced values.
func (r replacer) Replace(s string) string {
- // Header replacements - these are case-insensitive, so we can't just use strings.Replace()
- for strings.Contains(s, headerReplacer) {
- idxStart := strings.Index(s, headerReplacer)
- endOffset := idxStart + len(headerReplacer)
- idxEnd := strings.Index(s[endOffset:], "}")
- if idxEnd > -1 {
- placeholder := strings.ToLower(s[idxStart : endOffset+idxEnd+1])
- replacement := r.replacements[placeholder]
- if replacement == "" {
- replacement = r.emptyValue
+
+ // declare a function that replace based on header matching
+ fscanAndReplace := func(s string, header string, replace func(string) string) string {
+ b := strings.Builder{}
+ for strings.Contains(s, header) {
+ idxStart := strings.Index(s, header)
+ endOffset := idxStart + len(header)
+ idxEnd := strings.Index(s[endOffset:], "}")
+ if idxEnd > -1 {
+ placeholder := strings.ToLower(s[idxStart : endOffset+idxEnd+1])
+ replacement := replace(placeholder)
+ if replacement == "" {
+ replacement = r.emptyValue
+ }
+ b.WriteString(s[:idxStart])
+ b.WriteString(replacement)
+ s = s[endOffset+idxEnd+1:]
+ } else {
+ break
}
- s = s[:idxStart] + replacement + s[endOffset+idxEnd+1:]
- } else {
- break
}
+ b.WriteString(s)
+ return b.String()
}
+ // Header replacements - these are case-insensitive, so we can't just use strings.Replace()
+ s = fscanAndReplace(s, headerReplacer, func(placeholder string) string {
+ return r.replacements[placeholder]
+ })
+
// Regular replacements - these are easier because they're case-sensitive
for placeholder, replacement := range r.replacements {
if replacement == "" {
@@ -96,6 +114,16 @@ func (r replacer) Replace(s string) string {
s = strings.Replace(s, placeholder, replacement, -1)
}
+ // Metadata label replacements
+ s = fscanAndReplace(s, headerLabelReplacer, func(placeholder string) string {
+ // label place holder has the format {/<label>}
+ fm := metadata.ValueFunc(r.ctx, placeholder[len(headerLabelReplacer):len(placeholder)-1])
+ if fm != nil {
+ return fm()
+ }
+ return ""
+ })
+
return s
}
@@ -161,4 +189,7 @@ func addrToRFC3986(addr string) string {
return addr
}
-const headerReplacer = "{>"
+const (
+ headerReplacer = "{>"
+ headerLabelReplacer = "{/"
+)
diff --git a/plugin/pkg/replacer/replacer_test.go b/plugin/pkg/replacer/replacer_test.go
index 2fcaafc92..a813057d8 100644
--- a/plugin/pkg/replacer/replacer_test.go
+++ b/plugin/pkg/replacer/replacer_test.go
@@ -1,9 +1,13 @@
package replacer
import (
+ "context"
"strings"
"testing"
+ "github.com/coredns/coredns/plugin/metadata"
+ "github.com/coredns/coredns/request"
+
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
@@ -17,7 +21,7 @@ func TestNewReplacer(t *testing.T) {
r.SetQuestion("example.org.", dns.TypeHINFO)
r.MsgHdr.AuthenticatedData = true
- replaceValues := New(r, w, "")
+ replaceValues := New(context.TODO(), r, w, "")
switch v := replaceValues.(type) {
case replacer:
@@ -47,7 +51,7 @@ func TestSet(t *testing.T) {
r.SetQuestion("example.org.", dns.TypeHINFO)
r.MsgHdr.AuthenticatedData = true
- repl := New(r, w, "")
+ repl := New(context.TODO(), r, w, "")
repl.Set("name", "coredns.io.")
repl.Set("type", "A")
@@ -63,3 +67,66 @@ func TestSet(t *testing.T) {
t.Error("Expected size replacement failed")
}
}
+
+type testProvider map[string]metadata.Func
+
+func (tp testProvider) Metadata(ctx context.Context, state request.Request) context.Context {
+ for k, v := range tp {
+ metadata.SetValueFunc(ctx, k, v)
+ }
+ return ctx
+}
+
+type testHandler struct{ ctx context.Context }
+
+func (m *testHandler) Name() string { return "test" }
+
+func (m *testHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ m.ctx = ctx
+ return 0, nil
+}
+
+func TestMetadataReplacement(t *testing.T) {
+
+ mdata := testProvider{
+ "test/key2": func() string { return "two" },
+ }
+
+ tests := []struct {
+ expr string
+ result string
+ }{
+ {"{name}", "example.org."},
+ {"{/test/key2}", "two"},
+ {"TYPE={type}, NAME={name}, BUFSIZE={>bufsize}, WHAT={/test/key2} .. and more", "TYPE=HINFO, NAME=example.org., BUFSIZE=512, WHAT=two .. and more"},
+ {"{/test/key2}{/test/key4}", "two-"},
+ {"{test/key2", "{test/key2"}, // if last } is missing, the end of format is considered static text
+ {"{/test-key2}", "-"}, // everything that is not a placeholder for log or a metadata label is invalid
+ }
+
+ next := &testHandler{} // fake handler which stores the resulting context
+ m := metadata.Metadata{
+ Zones: []string{"."},
+ Providers: []metadata.Provider{mdata},
+ Next: next,
+ }
+
+ ctx := context.TODO()
+ m.ServeDNS(ctx, &test.ResponseWriter{}, new(dns.Msg))
+ nctx := next.ctx
+
+ w := dnstest.NewRecorder(&test.ResponseWriter{})
+
+ r := new(dns.Msg)
+ r.SetQuestion("example.org.", dns.TypeHINFO)
+ r.MsgHdr.AuthenticatedData = true
+
+ repl := New(nctx, r, w, "-")
+
+ for i, ts := range tests {
+ r := repl.Replace(ts.expr)
+ if r != ts.result {
+ t.Errorf("Test %d - expr : %s, expected replacement being %s, and got %s", i, ts.expr, ts.result, r)
+ }
+ }
+}
diff --git a/plugin/rewrite/condition.go b/plugin/rewrite/condition.go
index f8d2a9f08..97ca99753 100644
--- a/plugin/rewrite/condition.go
+++ b/plugin/rewrite/condition.go
@@ -1,6 +1,7 @@
package rewrite
import (
+ "context"
"fmt"
"regexp"
"strings"
@@ -22,7 +23,7 @@ const (
NotMatch = "not_match"
)
-func newReplacer(r *dns.Msg) replacer.Replacer { return replacer.New(r, nil, "") }
+func newReplacer(r *dns.Msg) replacer.Replacer { return replacer.New(context.TODO(), r, nil, "") }
// condition is a rewrite condition.
type condition func(string, string) bool