aboutsummaryrefslogtreecommitdiff
path: root/middleware/federation/federation.go
diff options
context:
space:
mode:
Diffstat (limited to 'middleware/federation/federation.go')
-rw-r--r--middleware/federation/federation.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/middleware/federation/federation.go b/middleware/federation/federation.go
new file mode 100644
index 000000000..caf29e630
--- /dev/null
+++ b/middleware/federation/federation.go
@@ -0,0 +1,140 @@
+/*
+Package federation implements kubernetes federation. It checks if the qname matches
+a possible federation. If this is the case and the captured answer is an NXDOMAIN,
+federation is performed. If this is not the case the original answer is returned.
+
+The federation label is always the 2nd to last once the zone is chopped of. For
+instance "nginx.mynamespace.myfederation.svc.example.com" has "myfederation" as
+the federation label. For federation to work we do a normal k8s lookup
+*without* that label, if that comes back with NXDOMAIN or NODATA(??) we create
+a federation record and return that.
+
+Federation is only useful in conjunction with the kubernetes middleware, without it is a noop.
+*/
+package federation
+
+import (
+ "strings"
+
+ "github.com/coredns/coredns/middleware"
+ "github.com/coredns/coredns/middleware/etcd/msg"
+ "github.com/coredns/coredns/middleware/pkg/dnsutil"
+ "github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
+ "golang.org/x/net/context"
+)
+
+// Federation contains the name to zone mapping used for federation in kubernetes.
+type Federation struct {
+ f map[string]string
+ zones []string
+
+ Next middleware.Handler
+ Federations Func
+}
+
+// Func needs to be implemented by any middleware that implements
+// federation. Right now this is only the kubernetes middleware.
+type Func func(state request.Request, fname, fzone string) (msg.Service, error)
+
+// New returns a new federation.
+func New() *Federation {
+ return &Federation{f: make(map[string]string)}
+}
+
+// ServeDNS implements the middleware.Handle interface.
+func (f *Federation) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ if f.Federations == nil {
+ return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r)
+ }
+
+ state := request.Request{W: w, Req: r}
+ zone := middleware.Zones(f.zones).Matches(state.Name())
+ if zone == "" {
+ return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r)
+ }
+
+ state.Zone = zone
+
+ // Remove the federation label from the qname to see if something exists.
+ without, label := f.isNameFederation(state.Name(), state.Zone)
+ if without == "" {
+ return middleware.NextOrFailure(f.Name(), f.Next, ctx, w, r)
+ }
+
+ qname := r.Question[0].Name
+ r.Question[0].Name = without
+ state.Clear()
+
+ // Start the next middleware, but with a nowriter, capture the result, if NXDOMAIN
+ // perform federation, otherwise just write the result.
+ nw := NewNonWriter(w)
+ ret, err := middleware.NextOrFailure(f.Name(), f.Next, ctx, nw, r)
+
+ if !middleware.ClientWrite(ret) {
+ // something went wrong
+ return ret, err
+ }
+
+ if m := nw.Msg; m.Rcode != dns.RcodeNameError {
+ // If positive answer we need to substitute the orinal qname in question and answer.
+ r.Question[0].Name = qname
+ for _, a := range m.Answer {
+ a.Header().Name = qname
+ }
+
+ state.SizeAndDo(m)
+ m, _ = state.Scrub(m)
+ w.WriteMsg(m)
+
+ return dns.RcodeSuccess, nil
+ }
+
+ // Still here, we've seen NXDOMAIN and need to perform federation.
+ service, err := f.Federations(state, label, f.f[label]) // state references Req which has updated qname
+ if err != nil {
+ return dns.RcodeServerFailure, err
+ }
+
+ r.Question[0].Name = qname
+
+ m := new(dns.Msg)
+ m.SetReply(r)
+ m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
+
+ m.Answer = []dns.RR{service.NewCNAME(state.QName(), service.Host)}
+
+ state.SizeAndDo(m)
+ m, _ = state.Scrub(m)
+ w.WriteMsg(m)
+
+ return dns.RcodeSuccess, nil
+}
+
+// Name implements the middleware.Handle interface.
+func (f *Federation) Name() string { return "federation" }
+
+// IsNameFederation checks the qname to see if it is a potential federation. The federation
+// label is always the 2nd to last once the zone is chopped of. For instance
+// "nginx.mynamespace.myfederation.svc.example.com" has "myfederation" as the federation label.
+// IsNameFederation returns a new qname with the federation label and the label itself or two
+// emtpy strings if there wasn't a hit.
+func (f *Federation) isNameFederation(name, zone string) (string, string) {
+ base, _ := dnsutil.TrimZone(name, zone)
+
+ // TODO(miek): dns.PrevLabel is better for memory, or dns.Split.
+ labels := dns.SplitDomainName(base)
+ ll := len(labels)
+ if ll < 2 {
+ return "", ""
+ }
+
+ fed := labels[ll-2]
+
+ if _, ok := f.f[fed]; ok {
+ without := strings.Join(labels[:ll-2], ".") + "." + labels[ll-1] + "." + zone
+ return without, fed
+ }
+ return "", ""
+}