| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | package kubernetes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-04-22 08:34:35 +01:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 	"math" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 	"github.com/coredns/coredns/plugin" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/etcd/msg" | 
					
						
							| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | 	"github.com/coredns/coredns/request" | 
					
						
							| 
									
										
										
										
											2018-04-19 07:41:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 	"github.com/miekg/dns" | 
					
						
							|  |  |  | 	api "k8s.io/api/core/v1" | 
					
						
							| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | const transferLength = 2000 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | // Serial implements the Transferer interface. | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified()) } | 
					
						
							| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // MinTTL implements the Transferer interface. | 
					
						
							| 
									
										
										
										
											2019-01-10 02:34:22 -05:00
										 |  |  | func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl } | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Transfer implements the Transferer interface. | 
					
						
							|  |  |  | func (k *Kubernetes) Transfer(ctx context.Context, state request.Request) (int, error) { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-13 18:25:30 -05:00
										 |  |  | 	if !k.transferAllowed(state) { | 
					
						
							|  |  |  | 		return dns.RcodeRefused, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 	// Get all services. | 
					
						
							|  |  |  | 	rrs := make(chan dns.RR) | 
					
						
							|  |  |  | 	go k.transfer(rrs, state.Zone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	records := []dns.RR{} | 
					
						
							|  |  |  | 	for r := range rrs { | 
					
						
							|  |  |  | 		records = append(records, r) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(records) == 0 { | 
					
						
							|  |  |  | 		return dns.RcodeServerFailure, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ch := make(chan *dns.Envelope) | 
					
						
							|  |  |  | 	tr := new(dns.Transfer) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-26 14:37:30 +00:00
										 |  |  | 	soa, err := plugin.SOA(ctx, k, state.Zone, state, plugin.Options{}) | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return dns.RcodeServerFailure, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	records = append(soa, records...) | 
					
						
							|  |  |  | 	records = append(records, soa...) | 
					
						
							|  |  |  | 	go func(ch chan *dns.Envelope) { | 
					
						
							|  |  |  | 		j, l := 0, 0 | 
					
						
							| 
									
										
										
										
											2018-04-19 07:41:56 +01:00
										 |  |  | 		log.Infof("Outgoing transfer of %d records of zone %s to %s started", len(records), state.Zone, state.IP()) | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 		for i, r := range records { | 
					
						
							|  |  |  | 			l += dns.Len(r) | 
					
						
							|  |  |  | 			if l > transferLength { | 
					
						
							|  |  |  | 				ch <- &dns.Envelope{RR: records[j:i]} | 
					
						
							|  |  |  | 				l = 0 | 
					
						
							|  |  |  | 				j = i | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if j < len(records) { | 
					
						
							|  |  |  | 			ch <- &dns.Envelope{RR: records[j:]} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		close(ch) | 
					
						
							|  |  |  | 	}(ch) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tr.Out(state.W, state.Req, ch) | 
					
						
							|  |  |  | 	// Defer closing to the client | 
					
						
							|  |  |  | 	state.W.Hijack() | 
					
						
							|  |  |  | 	return dns.RcodeSuccess, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-13 18:25:30 -05:00
										 |  |  | // transferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs. | 
					
						
							|  |  |  | // Note: This is copied from zone.transferAllowed, but should eventually be factored into a common transfer pkg. | 
					
						
							|  |  |  | func (k *Kubernetes) transferAllowed(state request.Request) bool { | 
					
						
							|  |  |  | 	for _, t := range k.TransferTo { | 
					
						
							|  |  |  | 		if t == "*" { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// If remote IP matches we accept. | 
					
						
							|  |  |  | 		remote := state.IP() | 
					
						
							|  |  |  | 		to, _, err := net.SplitHostPort(t) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if to == remote { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | func (k *Kubernetes) transfer(c chan dns.RR, zone string) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer close(c) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	zonePath := msg.Path(zone, "coredns") | 
					
						
							|  |  |  | 	serviceList := k.APIConn.ServiceList() | 
					
						
							|  |  |  | 	for _, svc := range serviceList { | 
					
						
							| 
									
										
										
										
											2018-08-27 10:38:49 -04:00
										 |  |  | 		if !k.namespaceExposed(svc.Namespace) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 		svcBase := []string{zonePath, Svc, svc.Namespace, svc.Name} | 
					
						
							| 
									
										
										
										
											2018-10-09 21:56:09 +01:00
										 |  |  | 		switch svc.Type { | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 		case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer: | 
					
						
							| 
									
										
										
										
											2018-10-09 21:56:09 +01:00
										 |  |  | 			clusterIP := net.ParseIP(svc.ClusterIP) | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 			if clusterIP != nil { | 
					
						
							| 
									
										
										
										
											2019-09-08 03:28:30 -04:00
										 |  |  | 				s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl} | 
					
						
							|  |  |  | 				s.Key = strings.Join(svcBase, "/") | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-08 03:28:30 -04:00
										 |  |  | 				// Change host from IP to Name for SRV records | 
					
						
							|  |  |  | 				host := emitAddressRecord(c, s) | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-08 03:28:30 -04:00
										 |  |  | 				for _, p := range svc.Ports { | 
					
						
							|  |  |  | 					s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl} | 
					
						
							|  |  |  | 					s.Key = strings.Join(svcBase, "/") | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// Need to generate this to handle use cases for peer-finder | 
					
						
							|  |  |  | 					// ref: https://github.com/coredns/coredns/pull/823 | 
					
						
							|  |  |  | 					c <- s.NewSRV(msg.Domain(s.Key), 100) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// As per spec unnamed ports do not have a srv record | 
					
						
							|  |  |  | 					// https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records | 
					
						
							|  |  |  | 					if p.Name == "" { | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					c <- s.NewSRV(msg.Domain(s.Key), 100) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				//  Skip endpoint discovery if clusterIP is defined | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-11 21:59:50 +01:00
										 |  |  | 			endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace) | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-11 21:59:50 +01:00
										 |  |  | 			for _, ep := range endpointsList { | 
					
						
							|  |  |  | 				if ep.Name != svc.Name || ep.Namespace != svc.Namespace { | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-11 21:59:50 +01:00
										 |  |  | 				for _, eps := range ep.Subsets { | 
					
						
							|  |  |  | 					srvWeight := calcSRVWeight(len(eps.Addresses)) | 
					
						
							|  |  |  | 					for _, addr := range eps.Addresses { | 
					
						
							|  |  |  | 						s := msg.Service{Host: addr.IP, TTL: k.ttl} | 
					
						
							|  |  |  | 						s.Key = strings.Join(svcBase, "/") | 
					
						
							|  |  |  | 						// We don't need to change the msg.Service host from IP to Name yet | 
					
						
							|  |  |  | 						// so disregard the return value here | 
					
						
							|  |  |  | 						emitAddressRecord(c, s) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						s.Key = strings.Join(append(svcBase, endpointHostname(addr, k.endpointNameMode)), "/") | 
					
						
							|  |  |  | 						// Change host from IP to Name for SRV records | 
					
						
							|  |  |  | 						host := emitAddressRecord(c, s) | 
					
						
							|  |  |  | 						s.Host = host | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						for _, p := range eps.Ports { | 
					
						
							|  |  |  | 							// As per spec unnamed ports do not have a srv record | 
					
						
							|  |  |  | 							// https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records | 
					
						
							|  |  |  | 							if p.Name == "" { | 
					
						
							|  |  |  | 								continue | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							s.Port = int(p.Port) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/") | 
					
						
							|  |  |  | 							c <- s.NewSRV(msg.Domain(s.Key), srvWeight) | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		case api.ServiceTypeExternalName: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-09 21:56:09 +01:00
										 |  |  | 			s := msg.Service{Key: strings.Join(svcBase, "/"), Host: svc.ExternalName, TTL: k.ttl} | 
					
						
							| 
									
										
										
										
											2018-02-08 10:11:04 -06:00
										 |  |  | 			if t, _ := s.HostType(); t == dns.TypeCNAME { | 
					
						
							|  |  |  | 				c <- s.NewCNAME(msg.Domain(s.Key), s.Host) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // emitAddressRecord generates a new A or AAAA record based on the msg.Service and writes it to | 
					
						
							|  |  |  | // a channel. | 
					
						
							|  |  |  | // emitAddressRecord returns the host name from the generated record. | 
					
						
							|  |  |  | func emitAddressRecord(c chan dns.RR, message msg.Service) string { | 
					
						
							|  |  |  | 	ip := net.ParseIP(message.Host) | 
					
						
							|  |  |  | 	var host string | 
					
						
							|  |  |  | 	dnsType, _ := message.HostType() | 
					
						
							|  |  |  | 	switch dnsType { | 
					
						
							|  |  |  | 	case dns.TypeA: | 
					
						
							|  |  |  | 		arec := message.NewA(msg.Domain(message.Key), ip) | 
					
						
							|  |  |  | 		host = arec.Hdr.Name | 
					
						
							|  |  |  | 		c <- arec | 
					
						
							|  |  |  | 	case dns.TypeAAAA: | 
					
						
							|  |  |  | 		arec := message.NewAAAA(msg.Domain(message.Key), ip) | 
					
						
							|  |  |  | 		host = arec.Hdr.Name | 
					
						
							|  |  |  | 		c <- arec | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return host | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // calcSrvWeight borrows the logic implemented in plugin.SRV for dynamically | 
					
						
							|  |  |  | // calculating the srv weight and priority | 
					
						
							|  |  |  | func calcSRVWeight(numservices int) uint16 { | 
					
						
							|  |  |  | 	var services []msg.Service | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < numservices; i++ { | 
					
						
							|  |  |  | 		services = append(services, msg.Service{}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w := make(map[int]int) | 
					
						
							|  |  |  | 	for _, serv := range services { | 
					
						
							|  |  |  | 		weight := 100 | 
					
						
							|  |  |  | 		if serv.Weight != 0 { | 
					
						
							|  |  |  | 			weight = serv.Weight | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if _, ok := w[serv.Priority]; !ok { | 
					
						
							|  |  |  | 			w[serv.Priority] = weight | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		w[serv.Priority] += weight | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return uint16(math.Floor((100.0 / float64(w[0])) * 100)) | 
					
						
							| 
									
										
										
										
											2017-11-01 10:11:34 +00:00
										 |  |  | } |