| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  | 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.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | Federation is only useful in conjunction with the kubernetes plugin, without it is a noop.
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | */
 | 
					
						
							|  |  |  | package federation
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							| 
									
										
										
										
											2018-04-22 08:34:35 +01:00
										 |  |  | 	"context"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	"github.com/coredns/coredns/plugin"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/etcd/msg"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/dnsutil"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/nonwriter"
 | 
					
						
							| 
									
										
										
										
											2018-10-15 12:43:03 -04:00
										 |  |  | 	"github.com/coredns/coredns/plugin/pkg/upstream"
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	"github.com/coredns/coredns/request"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/miekg/dns"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Federation contains the name to zone mapping used for federation in kubernetes.
 | 
					
						
							|  |  |  | type Federation struct {
 | 
					
						
							| 
									
										
										
										
											2018-10-15 12:43:03 -04:00
										 |  |  | 	f        map[string]string
 | 
					
						
							|  |  |  | 	zones    []string
 | 
					
						
							|  |  |  | 	Upstream *upstream.Upstream
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	Next        plugin.Handler
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	Federations Func
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | // Func needs to be implemented by any plugin that implements
 | 
					
						
							|  |  |  | // federation. Right now this is only the kubernetes plugin.
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 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)}
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | // ServeDNS implements the plugin.Handle interface.
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | func (f *Federation) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
 | 
					
						
							|  |  |  | 	if f.Federations == nil {
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 		return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r)
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-26 14:37:30 +00:00
										 |  |  | 	state := request.Request{W: w, Req: r}
 | 
					
						
							| 
									
										
										
										
											2018-10-15 12:43:03 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	zone := plugin.Zones(f.zones).Matches(state.Name())
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	if zone == "" {
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 		return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r)
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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 == "" {
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 		return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r)
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	qname := r.Question[0].Name
 | 
					
						
							|  |  |  | 	r.Question[0].Name = without
 | 
					
						
							|  |  |  | 	state.Clear()
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	// Start the next plugin, but with a nowriter, capture the result, if NXDOMAIN
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	// perform federation, otherwise just write the result.
 | 
					
						
							| 
									
										
										
										
											2017-08-19 17:28:42 +01:00
										 |  |  | 	nw := nonwriter.New(w)
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	ret, err := plugin.NextOrFailure(f.Name(), f.Next, ctx, nw, r)
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | 	if !plugin.ClientWrite(ret) {
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 		// something went wrong
 | 
					
						
							| 
									
										
										
										
											2017-09-07 13:47:58 +01:00
										 |  |  | 		r.Question[0].Name = qname
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 		return ret, err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if m := nw.Msg; m.Rcode != dns.RcodeNameError {
 | 
					
						
							| 
									
										
										
										
											2017-09-07 13:47:58 +01:00
										 |  |  | 		// If positive answer we need to substitute the original qname in the answer.
 | 
					
						
							|  |  |  | 		m.Question[0].Name = qname
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 		for _, a := range m.Answer {
 | 
					
						
							|  |  |  | 			a.Header().Name = qname
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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 {
 | 
					
						
							| 
									
										
										
										
											2017-09-07 13:47:58 +01:00
										 |  |  | 		r.Question[0].Name = qname
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 		return dns.RcodeServerFailure, err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r.Question[0].Name = qname
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m := new(dns.Msg)
 | 
					
						
							|  |  |  | 	m.SetReply(r)
 | 
					
						
							| 
									
										
										
										
											2018-12-30 17:05:08 +01:00
										 |  |  | 	m.Authoritative = true
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	m.Answer = []dns.RR{service.NewCNAME(state.QName(), service.Host)}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-15 12:43:03 -04:00
										 |  |  | 	if f.Upstream != nil {
 | 
					
						
							| 
									
										
										
										
											2019-03-26 14:37:30 +00:00
										 |  |  | 		aRecord, err := f.Upstream.Lookup(ctx, state, service.Host, state.QType())
 | 
					
						
							| 
									
										
										
										
											2018-10-15 12:43:03 -04:00
										 |  |  | 		if err == nil && aRecord != nil && len(aRecord.Answer) > 0 {
 | 
					
						
							|  |  |  | 			m.Answer = append(m.Answer, aRecord.Answer...)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 	w.WriteMsg(m)
 | 
					
						
							|  |  |  | 	return dns.RcodeSuccess, nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-14 09:36:06 +01:00
										 |  |  | // Name implements the plugin.Handle interface.
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 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
 | 
					
						
							| 
									
										
										
										
											2017-08-26 17:32:16 -07:00
										 |  |  | // empty strings if there wasn't a hit.
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 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 {
 | 
					
						
							| 
									
										
										
										
											2018-09-22 15:12:02 +01:00
										 |  |  | 		without := dnsutil.Join(labels[:ll-2]...) + labels[ll-1] + "." + zone
 | 
					
						
							| 
									
										
										
										
											2017-08-18 14:45:20 +01:00
										 |  |  | 		return without, fed
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return "", ""
 | 
					
						
							|  |  |  | }
 |