diff --git a/core/directives.go b/core/directives.go index 5d9fa1a9a..27d379a0d 100644 --- a/core/directives.go +++ b/core/directives.go @@ -53,13 +53,13 @@ var directiveOrder = []directive{ // Directives that inject handlers (middleware) {"prometheus", setup.Prometheus}, {"rewrite", setup.Rewrite}, + {"file", setup.File}, + {"loadbalance", setup.Loadbalance}, {"log", setup.Log}, {"errors", setup.Errors}, {"etcd", setup.Etcd}, {"file", setup.File}, - {"reflect", setup.Reflect}, - {"proxy", setup.Proxy}, } diff --git a/core/setup/loadbalance.go b/core/setup/loadbalance.go new file mode 100644 index 000000000..93b919e5f --- /dev/null +++ b/core/setup/loadbalance.go @@ -0,0 +1,19 @@ +package setup + +import ( + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/middleware/loadbalance" +) + +// Root sets up the root file path of the server. +func Loadbalance(c *Controller) (middleware.Middleware, error) { + for c.Next() { + // and choosing the correct balancer + // TODO(miek): block and option parsing + } + return func(next middleware.Handler) middleware.Handler { + return loadbalance.RoundRobin{Next: next} + }, nil + + return nil, nil +} diff --git a/core/setup/reflect.go b/core/setup/reflect.go deleted file mode 100644 index 9ae1d5181..000000000 --- a/core/setup/reflect.go +++ /dev/null @@ -1,28 +0,0 @@ -package setup - -import ( - "github.com/miekg/coredns/middleware" - "github.com/miekg/coredns/middleware/reflect" -) - -// Reflect sets up the reflect middleware. -func Reflect(c *Controller) (middleware.Middleware, error) { - if err := reflectParse(c); err != nil { - return nil, err - } - return func(next middleware.Handler) middleware.Handler { - return reflect.Reflect{Next: next} - }, nil - -} - -func reflectParse(c *Controller) error { - for c.Next() { - if c.Val() == "reflect" { - if c.NextArg() { - return c.ArgErr() - } - } - } - return nil -} diff --git a/middleware/loadbalance/handler.go b/middleware/loadbalance/handler.go new file mode 100644 index 000000000..bb8619543 --- /dev/null +++ b/middleware/loadbalance/handler.go @@ -0,0 +1,19 @@ +// Package loadbalance is middleware for rewriting responses to do "load balancing" +package loadbalance + +import ( + "github.com/miekg/coredns/middleware" + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +// RoundRobin is middleware to rewrite responses for "load balancing". +type RoundRobin struct { + Next middleware.Handler +} + +// ServeHTTP implements the middleware.Handler interface. +func (rr RoundRobin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + wrr := NewRoundRobinResponseWriter(w) + return rr.Next.ServeDNS(ctx, wrr, r) +} diff --git a/middleware/loadbalance/loadbalance.go b/middleware/loadbalance/loadbalance.go new file mode 100644 index 000000000..5d96beaba --- /dev/null +++ b/middleware/loadbalance/loadbalance.go @@ -0,0 +1,68 @@ +package loadbalance + +import "github.com/miekg/dns" + +type RoundRobinResponseWriter struct { + dns.ResponseWriter +} + +func NewRoundRobinResponseWriter(w dns.ResponseWriter) *RoundRobinResponseWriter { + return &RoundRobinResponseWriter{w} +} + +func (r *RoundRobinResponseWriter) WriteMsg(res *dns.Msg) error { + if res.Rcode != dns.RcodeSuccess { + return r.ResponseWriter.WriteMsg(res) + } + if len(res.Answer) == 1 { + return r.ResponseWriter.WriteMsg(res) + } + + // put CNAMEs first, randomize a/aaaa's and put packet back together. + // TODO(miek): check family and give v6 more prio? + cname := []dns.RR{} + address := []dns.RR{} + rest := []dns.RR{} + for _, r := range res.Answer { + switch r.Header().Rrtype { + case dns.TypeCNAME: + cname = append(cname, r) + case dns.TypeA, dns.TypeAAAA: + address = append(address, r) + default: + rest = append(rest, r) + } + } + + switch l := len(address); l { + case 0, 1: + return r.ResponseWriter.WriteMsg(res) + case 2: + if dns.Id()%2 == 0 { + address[0], address[1] = address[1], address[0] + } + default: + for j := 0; j < l*(int(dns.Id())%4+1); j++ { + q := int(dns.Id()) % l + p := int(dns.Id()) % l + if q == p { + p = (p + 1) % l + } + address[q], address[p] = address[p], address[q] + } + } + res.Answer = append(cname, rest...) + res.Answer = append(res.Answer, address...) + return r.ResponseWriter.WriteMsg(res) +} + +func (r *RoundRobinResponseWriter) Write(buf []byte) (int, error) { + // pack and unpack? Not likely + n, err := r.ResponseWriter.Write(buf) + return n, err +} + +func (r *RoundRobinResponseWriter) Hijack() { + r.ResponseWriter.Hijack() + return +} diff --git a/middleware/loadbalance/loadbalance.md b/middleware/loadbalance/loadbalance.md new file mode 100644 index 000000000..0e931fb53 --- /dev/null +++ b/middleware/loadbalance/loadbalance.md @@ -0,0 +1,19 @@ +# loadbalance + +`loadbalance` acts as a round-robin DNS loadbalancer by randomizing A and AAAA records in the +message. See [Wikipedia](https://en.wikipedia.org/wiki/Round-robin_DNS) about the pros and cons +on this setup. + +## Syntax + +~~~ +loadbalance [policy] +~~~ + +* policy is how to balance, the default is "round_robin" + +## Examples + +~~~ +loadbalance round_robin +~~~ diff --git a/middleware/name.go b/middleware/name.go index f866a3865..ab9772b37 100644 --- a/middleware/name.go +++ b/middleware/name.go @@ -1,6 +1,10 @@ package middleware -import "strings" +import ( + "strings" + + "github.com/miekg/dns" +) // Name represents a domain name. type Name string @@ -13,3 +17,8 @@ type Name string func (n Name) Matches(other string) bool { return strings.HasSuffix(string(n), other) } + +// Normalize lowercases and makes n fully qualified. +func (n Name) Normalize() string { + return strings.ToLower(dns.Fqdn(string(n))) +} diff --git a/middleware/reflect/reflect.go b/middleware/reflect/reflect.go deleted file mode 100644 index 6e49c4199..000000000 --- a/middleware/reflect/reflect.go +++ /dev/null @@ -1,86 +0,0 @@ -// Reflect provides middleware that reflects back some client properties. -// This is the default middleware when Caddy is run without configuration. -// -// The left-most label must be `who`. -// When queried for type A (resp. AAAA), it sends back the IPv4 (resp. v6) address. -// In the additional section the port number and transport are shown. -// Basic use pattern: -// -// dig @localhost -p 1053 who.miek.nl A -// -// ;; ANSWER SECTION: -// who.miek.nl. 0 IN A 127.0.0.1 -// -// ;; ADDITIONAL SECTION: -// who.miek.nl. 0 IN TXT "Port: 56195 (udp)" -package reflect - -import ( - "errors" - "net" - "strings" - - "golang.org/x/net/context" - - "github.com/miekg/coredns/middleware" - "github.com/miekg/dns" -) - -type Reflect struct { - Next middleware.Handler -} - -func (rl Reflect) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { - state := middleware.State{Req: r, W: w} - - class := r.Question[0].Qclass - qname := r.Question[0].Name - i, ok := dns.NextLabel(qname, 0) - - if strings.ToLower(qname[:i]) != who || ok { - err := state.ErrorMessage(dns.RcodeFormatError) - w.WriteMsg(err) - return dns.RcodeFormatError, errors.New(dns.RcodeToString[dns.RcodeFormatError]) - } - - answer := new(dns.Msg) - answer.SetReply(r) - answer.Compress = true - answer.Authoritative = true - - ip := state.IP() - proto := state.Proto() - port, _ := state.Port() - family := state.Family() - var rr dns.RR - - switch family { - case 1: - rr = new(dns.A) - rr.(*dns.A).Hdr = dns.RR_Header{Name: qname, Rrtype: dns.TypeA, Class: class, Ttl: 0} - rr.(*dns.A).A = net.ParseIP(ip).To4() - case 2: - rr = new(dns.AAAA) - rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: qname, Rrtype: dns.TypeAAAA, Class: class, Ttl: 0} - rr.(*dns.AAAA).AAAA = net.ParseIP(ip) - } - - t := new(dns.TXT) - t.Hdr = dns.RR_Header{Name: qname, Rrtype: dns.TypeTXT, Class: class, Ttl: 0} - t.Txt = []string{"Port: " + port + " (" + proto + ")"} - - switch state.Type() { - case "TXT": - answer.Answer = append(answer.Answer, t) - answer.Extra = append(answer.Extra, rr) - default: - fallthrough - case "AAAA", "A": - answer.Answer = append(answer.Answer, rr) - answer.Extra = append(answer.Extra, t) - } - w.WriteMsg(answer) - return 0, nil -} - -const who = "who." diff --git a/middleware/reflect/reflect_test.go b/middleware/reflect/reflect_test.go deleted file mode 100644 index 477a3a573..000000000 --- a/middleware/reflect/reflect_test.go +++ /dev/null @@ -1 +0,0 @@ -package reflect diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index 42043506e..7ec8debdb 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -69,27 +69,31 @@ func NewSimpleRule(from, to string) SimpleRule { // It's only a type if uppercase is used. if from != strings.ToUpper(from) { tpf = 0 + from = middleware.Name(from).Normalize() } if to != strings.ToUpper(to) { tpt = 0 + to = middleware.Name(to).Normalize() } - - // lowercase and fully qualify the others here? TODO(miek) return SimpleRule{From: from, To: to, fromType: tpf, toType: tpt} } // Rewrite rewrites the the current request. func (s SimpleRule) Rewrite(r *dns.Msg) Result { + // type rewrite if s.fromType > 0 && s.toType > 0 { if r.Question[0].Qtype == s.fromType { r.Question[0].Qtype = s.toType return RewriteDone } - + return RewriteIgnored } - // if the question name matches the full name, or subset rewrite that - // s.Question[0].Name + // name rewite + if s.From == r.Question[0].Name { + r.Question[0].Name = s.To + return RewriteDone + } return RewriteIgnored } diff --git a/middleware/rewrite/rewrite.md b/middleware/rewrite/rewrite.md index 198e35825..6b5fb8161 100644 --- a/middleware/rewrite/rewrite.md +++ b/middleware/rewrite/rewrite.md @@ -16,9 +16,13 @@ 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 it does not look like a type a name is assumed and the qname in the message is rewritten. +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`. Advanced users may open a block and make a complex rewrite rule: +TODO(miek): this has not yet been implemented. + +> Everything below this line has not been implemented, yet. ~~~ rewrite [basename] {