diff options
author | 2018-11-13 14:20:49 -0500 | |
---|---|---|
committer | 2018-11-13 11:20:49 -0800 | |
commit | 94c9aae323bc544fa96693d24a24a4059d6f9b78 (patch) | |
tree | 705e05b4c99f5160b12e11dd0f1eb38825de4691 | |
parent | 35c5474660ab091b96c26e9b152033139d3f6117 (diff) | |
download | coredns-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.md | 5 | ||||
-rw-r--r-- | plugin/log/log.go | 2 | ||||
-rw-r--r-- | plugin/pkg/replacer/replacer.go | 61 | ||||
-rw-r--r-- | plugin/pkg/replacer/replacer_test.go | 71 | ||||
-rw-r--r-- | plugin/rewrite/condition.go | 3 |
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 |