diff --git a/core/directives.go b/core/directives.go index c6b7a4b4a..b679fb7e5 100644 --- a/core/directives.go +++ b/core/directives.go @@ -51,17 +51,16 @@ var directiveOrder = []directive{ {"shutdown", setup.Shutdown}, // Directives that inject handlers (middleware) + {"log", setup.Log}, {"prometheus", setup.Prometheus}, - {"chaos", setup.Chaos}, {"rewrite", setup.Rewrite}, {"loadbalance", setup.Loadbalance}, - {"log", setup.Log}, - {"errors", setup.Errors}, - {"file", setup.File}, + {"chaos", setup.Chaos}, {"secondary", setup.Secondary}, {"etcd", setup.Etcd}, {"proxy", setup.Proxy}, + {"errors", setup.Errors}, } // RegisterDirective adds the given directive to caddy's list of directives. diff --git a/middleware/chaos/README.md b/middleware/chaos/README.md index 45d022fce..1720e918a 100644 --- a/middleware/chaos/README.md +++ b/middleware/chaos/README.md @@ -1,6 +1,7 @@ # chaos -`chaos` +The `chaos` middleware allows CoreDNS to response to TXT queries in CH class. +Useful for retrieving version or author information from the server. ## Syntax @@ -11,16 +12,12 @@ chaos [version] [authors...] * `version` the version to return, defaults to CoreDNS. * `authors` what authors to return. No default. +Note this middleware can only be specified for a zone once. This is because it hijacks +the zones `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and +`id.server`, which means it can only be routed to one middleware. + ## Examples ~~~ -etcd { - path /skydns - endpoint endpoint... - stubzones -} +chaos CoreDNS-001 "Miek Gieben" miek@miek.nl ~~~ - -* `path` /skydns -* `endpoint` endpoints... -* `stubzones` diff --git a/middleware/chaos/chaos.go b/middleware/chaos/chaos.go index f668d9a4d..f1725495f 100644 --- a/middleware/chaos/chaos.go +++ b/middleware/chaos/chaos.go @@ -22,7 +22,10 @@ func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( if state.QClass() != dns.ClassCHAOS || state.QType() != dns.TypeTXT { return c.Next.ServeDNS(ctx, w, r) } + m := new(dns.Msg) + m.SetReply(r) + hdr := dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} switch state.Name() { default: diff --git a/middleware/etcd/handler.go b/middleware/etcd/handler.go index c7d9aca61..55a5d0fa8 100644 --- a/middleware/etcd/handler.go +++ b/middleware/etcd/handler.go @@ -1,6 +1,7 @@ package etcd import ( + "fmt" "strings" "github.com/miekg/coredns/middleware" @@ -11,6 +12,9 @@ import ( func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} + if state.QClass() != dns.ClassINET { + return dns.RcodeServerFailure, fmt.Errorf("etcd: can only deal with ClassINET") + } // We need to check stubzones first, because we may get a request for a zone we // are not auth. for *but* do have a stubzone forward for. If we do the stubzone diff --git a/middleware/file/file.go b/middleware/file/file.go index caf0c5fbc..a647f9d12 100644 --- a/middleware/file/file.go +++ b/middleware/file/file.go @@ -1,6 +1,7 @@ package file import ( + "fmt" "io" "log" @@ -24,6 +25,10 @@ type ( func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} + if state.QClass() != dns.ClassINET { + return dns.RcodeServerFailure, fmt.Errorf("file: can only deal with ClassINET") + } + qname := state.Name() zone := middleware.Zones(f.Zones.Names).Matches(qname) if zone == "" { diff --git a/middleware/log/log.go b/middleware/log/log.go index 998c2b602..ba5dba40c 100644 --- a/middleware/log/log.go +++ b/middleware/log/log.go @@ -23,6 +23,7 @@ func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) if middleware.Name(state.Name()).Matches(rule.NameScope) { responseRecorder := middleware.NewResponseRecorder(w) rcode, err := l.Next.ServeDNS(ctx, responseRecorder, r) + if rcode > 0 { // There was an error up the chain, but no response has been written yet. // The error must be handled here so the log entry will record the response size. @@ -58,7 +59,7 @@ const ( // DefaultLogFilename is the default log filename. DefaultLogFilename = "query.log" // CommonLogFormat is the common log format. - CommonLogFormat = `{remote} ` + CommonLogEmptyValue + ` [{when}] "{type} {name} {proto} {>do} {>bufsize}" {rcode} {size} {duration}` + CommonLogFormat = `{remote} ` + CommonLogEmptyValue + ` [{when}] "{type} {class} {name} {proto} {>do} {>bufsize}" {rcode} {size} {duration}` // CommonLogEmptyValue is the common empty log value. CommonLogEmptyValue = "-" // CombinedLogFormat is the combined log format. diff --git a/middleware/middleware.go b/middleware/middleware.go index 1ce5f62d6..44e806dde 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -20,6 +20,7 @@ type ( // If ServeDNS writes to the response body, it should return a status // code of 0. This signals to other handlers above it that the response // body is already written, and that they should not write to it also. + // TODO(miek): explain return codes better. // // If ServeDNS encounters an error, it should return the error value // so it can be logged by designated error-handling middleware. diff --git a/middleware/rewrite/README.md b/middleware/rewrite/README.md index 6b5fb8161..8e5a460f5 100644 --- a/middleware/rewrite/README.md +++ b/middleware/rewrite/README.md @@ -16,6 +16,9 @@ rewrite from to If from *and* to look like a DNS type (`A`, `MX`, etc.) the type of the message will be rewriten, i.e. to rewrite ANY queries to HINFO, use `rewrite ANY HINFO`. +If from *and* to look like a DNS class (`IN`, `CH`, or `HS`) the class of the message will be +rewritten. I.e. to rewrite CH queries to IN use `rewrite CH IN`. + If it does not look like a type a name is assumed and the qname in the message is rewritten, this needs to be a full match of the name `rewrite miek.nl example.org`. diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index 7ec8debdb..591598dda 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -58,24 +58,29 @@ type Rule interface { // the type of the request is rewritten, otherwise the name is. // Note: TSIG signed requests will be invalid. type SimpleRule struct { - From, To string - fromType, toType uint16 + From, To string + fromType, toType uint16 + fromClass, toClass uint16 } // NewSimpleRule creates a new Simple Rule func NewSimpleRule(from, to string) SimpleRule { tpf := dns.StringToType[from] tpt := dns.StringToType[to] - // It's only a type if uppercase is used. + clf := dns.StringToClass[from] + clt := dns.StringToClass[to] + // It's only a type/class if uppercase is used. if from != strings.ToUpper(from) { tpf = 0 + clf = 0 from = middleware.Name(from).Normalize() } if to != strings.ToUpper(to) { tpt = 0 + clt = 0 to = middleware.Name(to).Normalize() } - return SimpleRule{From: from, To: to, fromType: tpf, toType: tpt} + return SimpleRule{From: from, To: to, fromType: tpf, toType: tpt, fromClass: clf, toClass: clt} } // Rewrite rewrites the the current request. @@ -89,6 +94,15 @@ func (s SimpleRule) Rewrite(r *dns.Msg) Result { return RewriteIgnored } + // class rewrite + if s.fromClass > 0 && s.toClass > 0 { + if r.Question[0].Qclass == s.fromClass { + r.Question[0].Qclass = s.toClass + return RewriteDone + } + return RewriteIgnored + } + // name rewite if s.From == r.Question[0].Name { r.Question[0].Name = s.To diff --git a/server/server.go b/server/server.go index 1c588b69f..c186c82d2 100644 --- a/server/server.go +++ b/server/server.go @@ -17,6 +17,7 @@ import ( "golang.org/x/net/context" + "github.com/miekg/coredns/middleware/chaos" "github.com/miekg/dns" ) @@ -97,7 +98,6 @@ func New(addr string, configs []Config, gracefulTimeout time.Duration) (*Server, // Set up each zone for _, conf := range configs { - // TODO(miek): something better here? if _, exists := s.zones[conf.Host]; exists { return nil, fmt.Errorf("cannot serve %s - host already defined for address %s", conf.Address(), s.Addr) } @@ -111,6 +111,19 @@ func New(addr string, configs []Config, gracefulTimeout time.Duration) (*Server, } s.zones[conf.Host] = z + + // A bit of a hack. Loop through the middlewares of this zone and check if + // they have enabled the chaos middleware. If so add the special chaos zones. + Middleware: + for _, mid := range z.config.Middleware { + fn := mid(nil) + if _, ok := fn.(chaos.Chaos); ok { + for _, ch := range []string{"authors.bind.", "version.bind.", "version.server.", "hostname.bind.", "id.server."} { + s.zones[ch] = z + } + break Middleware + } + } } return s, nil