| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | package kubernetes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/etcd/msg" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/kubernetes/object" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/dnsutil" | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/request" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/miekg/dns" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | // Those constants are used to distinguish between records in ExternalServices headless | 
					
						
							|  |  |  | // return values. | 
					
						
							|  |  |  | // They are always appendedn to key in a map which is | 
					
						
							|  |  |  | // either base service key eg. /com/example/namespace/service/endpoint or | 
					
						
							|  |  |  | // /com/example/namespace/service/_http/_tcp/port.protocol | 
					
						
							|  |  |  | // this will allow us to distinguish services in implementation of Transfer protocol | 
					
						
							|  |  |  | // see plugin/k8s_external/transfer.go | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	Endpoint     = "endpoint" | 
					
						
							|  |  |  | 	PortProtocol = "port.protocol" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | // External implements the ExternalFunc call from the external plugin. | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | // It returns any services matching in the services' ExternalIPs and if enabled, headless endpoints.. | 
					
						
							|  |  |  | func (k *Kubernetes) External(state request.Request, headless bool) ([]msg.Service, int) { | 
					
						
							| 
									
										
										
										
											2022-07-06 13:55:15 -04:00
										 |  |  | 	if state.QType() == dns.TypePTR { | 
					
						
							|  |  |  | 		ip := dnsutil.ExtractAddressFromReverse(state.Name()) | 
					
						
							|  |  |  | 		if ip != "" { | 
					
						
							|  |  |  | 			svcs, err := k.ExternalReverse(ip) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, dns.RcodeNameError | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return svcs, dns.RcodeSuccess | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// for invalid reverse names, fall through to determine proper nxdomain/nodata response | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 	base, _ := dnsutil.TrimZone(state.Name(), state.Zone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	segs := dns.SplitDomainName(base) | 
					
						
							|  |  |  | 	last := len(segs) - 1 | 
					
						
							|  |  |  | 	if last < 0 { | 
					
						
							|  |  |  | 		return nil, dns.RcodeServerFailure | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 	// We are dealing with a fairly normal domain name here, but we still need to have the service, | 
					
						
							|  |  |  | 	// namespace and if present, endpoint: | 
					
						
							|  |  |  | 	// service.namespace.<base> or | 
					
						
							|  |  |  | 	// endpoint.service.namespace.<base> | 
					
						
							|  |  |  | 	var port, protocol, endpoint string | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 	namespace := segs[last] | 
					
						
							| 
									
										
										
										
											2019-03-22 08:32:40 -06:00
										 |  |  | 	if !k.namespaceExposed(namespace) { | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 		return nil, dns.RcodeNameError | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	last-- | 
					
						
							|  |  |  | 	if last < 0 { | 
					
						
							|  |  |  | 		return nil, dns.RcodeSuccess | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	service := segs[last] | 
					
						
							|  |  |  | 	last-- | 
					
						
							| 
									
										
										
										
											2025-04-04 20:27:39 +02:00
										 |  |  | 	switch last { | 
					
						
							|  |  |  | 	case 0: | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 		endpoint = stripUnderscore(segs[last]) | 
					
						
							|  |  |  | 		last-- | 
					
						
							| 
									
										
										
										
											2025-04-04 20:27:39 +02:00
										 |  |  | 	case 1: | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 		protocol = stripUnderscore(segs[last]) | 
					
						
							|  |  |  | 		port = stripUnderscore(segs[last-1]) | 
					
						
							|  |  |  | 		last -= 2 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if last != -1 { | 
					
						
							|  |  |  | 		// too long | 
					
						
							|  |  |  | 		return nil, dns.RcodeNameError | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 	var ( | 
					
						
							|  |  |  | 		endpointsList []*object.Endpoints | 
					
						
							|  |  |  | 		serviceList   []*object.Service | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 	idx := object.ServiceKey(service, namespace) | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 	serviceList = k.APIConn.SvcIndex(idx) | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	services := []msg.Service{} | 
					
						
							|  |  |  | 	zonePath := msg.Path(state.Zone, coredns) | 
					
						
							|  |  |  | 	rcode := dns.RcodeNameError | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, svc := range serviceList { | 
					
						
							|  |  |  | 		if namespace != svc.Namespace { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if service != svc.Name { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 		if headless && len(svc.ExternalIPs) == 0 && (svc.Headless() || endpoint != "") { | 
					
						
							|  |  |  | 			if endpointsList == nil { | 
					
						
							|  |  |  | 				endpointsList = k.APIConn.EpIndex(idx) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Endpoint query or headless service | 
					
						
							|  |  |  | 			for _, ep := range endpointsList { | 
					
						
							|  |  |  | 				if object.EndpointsKey(svc.Name, svc.Namespace) != ep.Index { | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 				for _, eps := range ep.Subsets { | 
					
						
							|  |  |  | 					for _, addr := range eps.Addresses { | 
					
						
							|  |  |  | 						if endpoint != "" && !match(endpoint, endpointHostname(addr, k.endpointNameMode)) { | 
					
						
							|  |  |  | 							continue | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						for _, p := range eps.Ports { | 
					
						
							|  |  |  | 							if !(matchPortAndProtocol(port, p.Name, protocol, p.Protocol)) { | 
					
						
							|  |  |  | 								continue | 
					
						
							|  |  |  | 							} | 
					
						
							| 
									
										
										
										
											2022-10-20 16:30:12 -04:00
										 |  |  | 							rcode = dns.RcodeSuccess | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 							s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl} | 
					
						
							|  |  |  | 							s.Key = strings.Join([]string{zonePath, svc.Namespace, svc.Name, endpointHostname(addr, k.endpointNameMode)}, "/") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							services = append(services, s) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2025-06-05 09:10:58 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		for _, ip := range svc.ExternalIPs { | 
					
						
							|  |  |  | 			for _, p := range svc.Ports { | 
					
						
							|  |  |  | 				if !(matchPortAndProtocol(port, p.Name, protocol, string(p.Protocol))) { | 
					
						
							|  |  |  | 					continue | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2025-06-05 09:10:58 +02:00
										 |  |  | 				rcode = dns.RcodeSuccess | 
					
						
							|  |  |  | 				s := msg.Service{Host: ip, Port: int(p.Port), TTL: k.ttl} | 
					
						
							|  |  |  | 				s.Key = strings.Join([]string{zonePath, svc.Namespace, svc.Name}, "/") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				services = append(services, s) | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-07-06 13:55:15 -04:00
										 |  |  | 	if state.QType() == dns.TypePTR { | 
					
						
							|  |  |  | 		// if this was a PTR request, return empty service list, but retain rcode for proper nxdomain/nodata response | 
					
						
							|  |  |  | 		return nil, rcode | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | 	return services, rcode | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ExternalAddress returns the external service address(es) for the CoreDNS service. | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | func (k *Kubernetes) ExternalAddress(state request.Request, headless bool) []dns.RR { | 
					
						
							| 
									
										
										
										
											2019-08-23 12:54:06 -04:00
										 |  |  | 	// If CoreDNS is running inside the Kubernetes cluster: k.nsAddrs() will return the external IPs of the services | 
					
						
							|  |  |  | 	// targeting the CoreDNS Pod. | 
					
						
							|  |  |  | 	// If CoreDNS is running outside of the Kubernetes cluster: k.nsAddrs() will return the first non-loopback IP | 
					
						
							|  |  |  | 	// address seen on the local system it is running on. This could be the wrong answer if coredns is using the *bind* | 
					
						
							|  |  |  | 	// plugin to bind to a different IP address. | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 	return k.nsAddrs(true, headless, state.Zone) | 
					
						
							| 
									
										
										
										
											2018-12-14 09:41:51 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | // ExternalServices returns all services with external IPs and if enabled headless services | 
					
						
							|  |  |  | func (k *Kubernetes) ExternalServices(zone string, headless bool) (services []msg.Service, headlessServices map[string][]msg.Service) { | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | 	zonePath := msg.Path(zone, coredns) | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 	headlessServices = make(map[string][]msg.Service) | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | 	for _, svc := range k.APIConn.ServiceList() { | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 		// Endpoints and headless services | 
					
						
							|  |  |  | 		if headless && len(svc.ExternalIPs) == 0 && svc.Headless() { | 
					
						
							|  |  |  | 			idx := object.ServiceKey(svc.Name, svc.Namespace) | 
					
						
							|  |  |  | 			endpointsList := k.APIConn.EpIndex(idx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for _, ep := range endpointsList { | 
					
						
							|  |  |  | 				for _, eps := range ep.Subsets { | 
					
						
							|  |  |  | 					for _, addr := range eps.Addresses { | 
					
						
							|  |  |  | 						// we need to have some answers grouped together | 
					
						
							|  |  |  | 						// 1. for endpoint requests eg. endpoint-0.service.example.com - will always have one endpoint | 
					
						
							|  |  |  | 						// 2. for service requests eg. service.example.com - can have multiple endpoints | 
					
						
							|  |  |  | 						// 3. for port.protocol requests eg. _http._tcp.service.example.com - can have multiple endpoints | 
					
						
							|  |  |  | 						for _, p := range eps.Ports { | 
					
						
							|  |  |  | 							s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl} | 
					
						
							|  |  |  | 							baseSvc := strings.Join([]string{zonePath, svc.Namespace, svc.Name}, "/") | 
					
						
							|  |  |  | 							s.Key = strings.Join([]string{baseSvc, endpointHostname(addr, k.endpointNameMode)}, "/") | 
					
						
							|  |  |  | 							headlessServices[strings.Join([]string{baseSvc, Endpoint}, "/")] = append(headlessServices[strings.Join([]string{baseSvc, Endpoint}, "/")], s) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// 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.Host = msg.Domain(s.Key) | 
					
						
							| 
									
										
										
										
											2023-08-14 21:14:09 +08:00
										 |  |  | 							s.Key = strings.Join(append([]string{zonePath, svc.Namespace, svc.Name}, strings.ToLower("_"+p.Protocol), strings.ToLower("_"+p.Name)), "/") | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 							headlessServices[strings.Join([]string{s.Key, PortProtocol}, "/")] = append(headlessServices[strings.Join([]string{s.Key, PortProtocol}, "/")], s) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2025-06-05 09:10:58 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		for _, ip := range svc.ExternalIPs { | 
					
						
							|  |  |  | 			for _, p := range svc.Ports { | 
					
						
							|  |  |  | 				s := msg.Service{Host: ip, Port: int(p.Port), TTL: k.ttl} | 
					
						
							|  |  |  | 				s.Key = strings.Join([]string{zonePath, svc.Namespace, svc.Name}, "/") | 
					
						
							|  |  |  | 				services = append(services, s) | 
					
						
							|  |  |  | 				s.Key = strings.Join(append([]string{zonePath, svc.Namespace, svc.Name}, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+p.Name)), "/") | 
					
						
							|  |  |  | 				s.TargetStrip = 2 | 
					
						
							|  |  |  | 				services = append(services, s) | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-30 20:59:27 +02:00
										 |  |  | 	return services, headlessServices | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-09 18:08:23 +02:00
										 |  |  | // ExternalSerial returns the serial of the external zone | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | func (k *Kubernetes) ExternalSerial(string) uint32 { | 
					
						
							| 
									
										
										
										
											2025-05-19 07:58:16 +02:00
										 |  |  | 	return uint32(k.APIConn.Modified(ModifiedExternal)) | 
					
						
							| 
									
										
										
										
											2022-03-07 12:16:24 -05:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-07-06 13:55:15 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ExternalReverse does a reverse lookup for the external IPs | 
					
						
							|  |  |  | func (k *Kubernetes) ExternalReverse(ip string) ([]msg.Service, error) { | 
					
						
							|  |  |  | 	records := k.serviceRecordForExternalIP(ip) | 
					
						
							|  |  |  | 	if len(records) == 0 { | 
					
						
							|  |  |  | 		return records, errNoItems | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return records, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (k *Kubernetes) serviceRecordForExternalIP(ip string) []msg.Service { | 
					
						
							|  |  |  | 	var svcs []msg.Service | 
					
						
							|  |  |  | 	for _, service := range k.APIConn.SvcExtIndexReverse(ip) { | 
					
						
							|  |  |  | 		if len(k.Namespaces) > 0 && !k.namespaceExposed(service.Namespace) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		domain := strings.Join([]string{service.Name, service.Namespace}, ".") | 
					
						
							|  |  |  | 		svcs = append(svcs, msg.Service{Host: domain, TTL: k.ttl}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return svcs | 
					
						
							|  |  |  | } |