2018-01-08 11:52:25 +01:00
|
|
|
package template
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2018-04-22 08:34:35 +01:00
|
|
|
"context"
|
2018-01-08 11:52:25 +01:00
|
|
|
"regexp"
|
|
|
|
|
"strconv"
|
|
|
|
|
gotmpl "text/template"
|
|
|
|
|
|
|
|
|
|
"github.com/coredns/coredns/plugin"
|
2019-07-03 16:10:56 +01:00
|
|
|
"github.com/coredns/coredns/plugin/metadata"
|
2018-04-27 19:37:12 +01:00
|
|
|
"github.com/coredns/coredns/plugin/metrics"
|
2018-01-09 22:30:58 +01:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
2018-01-08 11:52:25 +01:00
|
|
|
"github.com/coredns/coredns/request"
|
|
|
|
|
|
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Handler is a plugin handler that takes a query and templates a response.
|
|
|
|
|
type Handler struct {
|
2018-01-09 22:30:58 +01:00
|
|
|
Zones []string
|
|
|
|
|
|
2018-01-08 11:52:25 +01:00
|
|
|
Next plugin.Handler
|
|
|
|
|
Templates []template
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type template struct {
|
2018-01-09 22:30:58 +01:00
|
|
|
zones []string
|
2018-01-08 11:52:25 +01:00
|
|
|
rcode int
|
|
|
|
|
regex []*regexp.Regexp
|
|
|
|
|
answer []*gotmpl.Template
|
|
|
|
|
additional []*gotmpl.Template
|
|
|
|
|
authority []*gotmpl.Template
|
2018-01-09 22:30:58 +01:00
|
|
|
qclass uint16
|
|
|
|
|
qtype uint16
|
2022-10-03 17:04:56 +02:00
|
|
|
ederror *ederror
|
2018-01-09 21:48:32 +00:00
|
|
|
fall fall.F
|
2022-01-26 15:49:44 -05:00
|
|
|
upstream Upstreamer
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 17:04:56 +02:00
|
|
|
type ederror struct {
|
|
|
|
|
code uint16
|
|
|
|
|
reason string
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-26 15:49:44 -05:00
|
|
|
// Upstreamer looks up targets of CNAME templates
|
|
|
|
|
type Upstreamer interface {
|
|
|
|
|
Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error)
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type templateData struct {
|
2018-01-09 22:30:58 +01:00
|
|
|
Zone string
|
2018-01-08 11:52:25 +01:00
|
|
|
Name string
|
|
|
|
|
Regex string
|
|
|
|
|
Match []string
|
|
|
|
|
Group map[string]string
|
|
|
|
|
Class string
|
|
|
|
|
Type string
|
|
|
|
|
Message *dns.Msg
|
|
|
|
|
Question *dns.Question
|
2020-08-10 10:38:18 +02:00
|
|
|
Remote string
|
2019-07-03 16:10:56 +01:00
|
|
|
md map[string]metadata.Func
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (data *templateData) Meta(metaName string) string {
|
|
|
|
|
if data.md == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f, ok := data.md[metaName]; ok {
|
|
|
|
|
return f()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ""
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ServeDNS implements the plugin.Handler interface.
|
|
|
|
|
func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
2019-03-26 14:37:30 +00:00
|
|
|
state := request.Request{W: w, Req: r}
|
2018-01-08 11:52:25 +01:00
|
|
|
|
2018-01-09 22:30:58 +01:00
|
|
|
zone := plugin.Zones(h.Zones).Matches(state.Name())
|
|
|
|
|
if zone == "" {
|
|
|
|
|
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 11:52:25 +01:00
|
|
|
for _, template := range h.Templates {
|
2019-10-01 07:41:29 +01:00
|
|
|
data, match, fthrough := template.match(ctx, state)
|
2018-01-08 11:52:25 +01:00
|
|
|
if !match {
|
2018-01-09 22:30:58 +01:00
|
|
|
if !fthrough {
|
2022-05-13 23:23:28 -04:00
|
|
|
return dns.RcodeServerFailure, nil
|
2018-01-09 22:30:58 +01:00
|
|
|
}
|
2018-01-08 11:52:25 +01:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-15 15:36:49 +02:00
|
|
|
templateMatchesCount.WithLabelValues(metrics.WithServer(ctx), data.Zone, metrics.WithView(ctx), data.Class, data.Type).Inc()
|
2018-01-08 11:52:25 +01:00
|
|
|
|
|
|
|
|
if template.rcode == dns.RcodeServerFailure {
|
|
|
|
|
return template.rcode, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg := new(dns.Msg)
|
|
|
|
|
msg.SetReply(r)
|
2018-12-30 17:05:08 +01:00
|
|
|
msg.Authoritative = true
|
2018-01-08 11:52:25 +01:00
|
|
|
msg.Rcode = template.rcode
|
|
|
|
|
|
|
|
|
|
for _, answer := range template.answer {
|
2022-09-15 15:36:49 +02:00
|
|
|
rr, err := executeRRTemplate(metrics.WithServer(ctx), metrics.WithView(ctx), "answer", answer, data)
|
2018-01-08 11:52:25 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return dns.RcodeServerFailure, err
|
|
|
|
|
}
|
|
|
|
|
msg.Answer = append(msg.Answer, rr)
|
2018-06-21 14:38:29 -04:00
|
|
|
if template.upstream != nil && (state.QType() == dns.TypeA || state.QType() == dns.TypeAAAA) && rr.Header().Rrtype == dns.TypeCNAME {
|
2022-01-26 15:49:44 -05:00
|
|
|
if up, err := template.upstream.Lookup(ctx, state, rr.(*dns.CNAME).Target, state.QType()); err == nil && up != nil {
|
|
|
|
|
msg.Truncated = up.Truncated
|
|
|
|
|
msg.Answer = append(msg.Answer, up.Answer...)
|
|
|
|
|
}
|
2018-02-16 03:45:25 -05:00
|
|
|
}
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
|
|
|
|
for _, additional := range template.additional {
|
2022-09-15 15:36:49 +02:00
|
|
|
rr, err := executeRRTemplate(metrics.WithServer(ctx), metrics.WithView(ctx), "additional", additional, data)
|
2018-01-08 11:52:25 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return dns.RcodeServerFailure, err
|
|
|
|
|
}
|
|
|
|
|
msg.Extra = append(msg.Extra, rr)
|
|
|
|
|
}
|
|
|
|
|
for _, authority := range template.authority {
|
2022-09-15 15:36:49 +02:00
|
|
|
rr, err := executeRRTemplate(metrics.WithServer(ctx), metrics.WithView(ctx), "authority", authority, data)
|
2018-01-08 11:52:25 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return dns.RcodeServerFailure, err
|
|
|
|
|
}
|
|
|
|
|
msg.Ns = append(msg.Ns, rr)
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 17:04:56 +02:00
|
|
|
if template.ederror != nil {
|
|
|
|
|
msg = msg.SetEdns0(4096, true)
|
|
|
|
|
ede := dns.EDNS0_EDE{InfoCode: template.ederror.code, ExtraText: template.ederror.reason}
|
|
|
|
|
msg.IsEdns0().Option = append(msg.IsEdns0().Option, &ede)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 11:52:25 +01:00
|
|
|
w.WriteMsg(msg)
|
|
|
|
|
return template.rcode, nil
|
|
|
|
|
}
|
2018-01-09 22:30:58 +01:00
|
|
|
|
2020-02-15 13:11:19 -08:00
|
|
|
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Name implements the plugin.Handler interface.
|
2018-01-08 13:13:25 +00:00
|
|
|
func (h Handler) Name() string { return "template" }
|
2018-01-08 11:52:25 +01:00
|
|
|
|
2022-09-15 15:36:49 +02:00
|
|
|
func executeRRTemplate(server, view, section string, template *gotmpl.Template, data *templateData) (dns.RR, error) {
|
2018-01-08 11:52:25 +01:00
|
|
|
buffer := &bytes.Buffer{}
|
|
|
|
|
err := template.Execute(buffer, data)
|
|
|
|
|
if err != nil {
|
2022-09-15 15:36:49 +02:00
|
|
|
templateFailureCount.WithLabelValues(server, data.Zone, view, data.Class, data.Type, section, template.Tree.Root.String()).Inc()
|
2018-01-08 11:52:25 +01:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
rr, err := dns.NewRR(buffer.String())
|
|
|
|
|
if err != nil {
|
2022-09-15 15:36:49 +02:00
|
|
|
templateRRFailureCount.WithLabelValues(server, data.Zone, view, data.Class, data.Type, section, template.Tree.Root.String()).Inc()
|
2018-01-08 11:52:25 +01:00
|
|
|
return rr, err
|
|
|
|
|
}
|
|
|
|
|
return rr, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-15 12:25:58 -07:00
|
|
|
func newTemplate(name, text string) (*gotmpl.Template, error) {
|
2022-09-15 12:48:38 -07:00
|
|
|
funcMap := gotmpl.FuncMap{
|
|
|
|
|
"parseInt": strconv.ParseUint,
|
|
|
|
|
}
|
|
|
|
|
return gotmpl.New(name).Funcs(funcMap).Parse(text)
|
2022-09-15 12:25:58 -07:00
|
|
|
}
|
|
|
|
|
|
2019-10-01 07:41:29 +01:00
|
|
|
func (t template) match(ctx context.Context, state request.Request) (*templateData, bool, bool) {
|
2018-01-08 11:52:25 +01:00
|
|
|
q := state.Req.Question[0]
|
2020-08-10 10:38:18 +02:00
|
|
|
data := &templateData{md: metadata.ValueFuncs(ctx), Remote: state.IP()}
|
2018-01-08 11:52:25 +01:00
|
|
|
|
2019-10-01 07:41:29 +01:00
|
|
|
zone := plugin.Zones(t.zones).Matches(state.Name())
|
2018-01-09 22:30:58 +01:00
|
|
|
if zone == "" {
|
|
|
|
|
return data, false, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if t.qclass != dns.ClassANY && q.Qclass != dns.ClassANY && q.Qclass != t.qclass {
|
|
|
|
|
return data, false, true
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
|
|
|
|
if t.qtype != dns.TypeANY && q.Qtype != dns.TypeANY && q.Qtype != t.qtype {
|
2018-01-09 22:30:58 +01:00
|
|
|
return data, false, true
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
2018-01-09 22:30:58 +01:00
|
|
|
|
2018-01-08 11:52:25 +01:00
|
|
|
for _, regex := range t.regex {
|
|
|
|
|
if !regex.MatchString(state.Name()) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-09 22:30:58 +01:00
|
|
|
data.Zone = zone
|
2018-01-08 11:52:25 +01:00
|
|
|
data.Regex = regex.String()
|
|
|
|
|
data.Name = state.Name()
|
|
|
|
|
data.Question = &q
|
|
|
|
|
data.Message = state.Req
|
2018-01-09 22:30:58 +01:00
|
|
|
if q.Qclass != dns.ClassANY {
|
|
|
|
|
data.Class = dns.ClassToString[q.Qclass]
|
|
|
|
|
} else {
|
|
|
|
|
data.Class = dns.ClassToString[t.qclass]
|
|
|
|
|
}
|
|
|
|
|
if q.Qtype != dns.TypeANY {
|
|
|
|
|
data.Type = dns.TypeToString[q.Qtype]
|
|
|
|
|
} else {
|
|
|
|
|
data.Type = dns.TypeToString[t.qtype]
|
|
|
|
|
}
|
2018-01-08 11:52:25 +01:00
|
|
|
|
|
|
|
|
matches := regex.FindStringSubmatch(state.Name())
|
|
|
|
|
data.Match = make([]string, len(matches))
|
|
|
|
|
data.Group = make(map[string]string)
|
|
|
|
|
groupNames := regex.SubexpNames()
|
|
|
|
|
for i, m := range matches {
|
|
|
|
|
data.Match[i] = m
|
|
|
|
|
data.Group[strconv.Itoa(i)] = m
|
|
|
|
|
}
|
|
|
|
|
for i, m := range matches {
|
|
|
|
|
if len(groupNames[i]) > 0 {
|
|
|
|
|
data.Group[groupNames[i]] = m
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-09 22:30:58 +01:00
|
|
|
return data, true, false
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|
2018-01-09 22:30:58 +01:00
|
|
|
|
2018-01-09 21:48:32 +00:00
|
|
|
return data, false, t.fall.Through(state.Name())
|
2018-01-08 11:52:25 +01:00
|
|
|
}
|