coredns/plugin/kubernetes/external.go
Zhizhen He 5de473da1c
fix: remove unnecessary conversion (#6258)
Signed-off-by: Zhizhen He <hezhizhen.yi@gmail.com>
2023-08-14 15:14:09 +02:00

237 lines
8 KiB
Go

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"
)
// 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"
)
// External implements the ExternalFunc call from the external plugin.
// 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) {
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
}
base, _ := dnsutil.TrimZone(state.Name(), state.Zone)
segs := dns.SplitDomainName(base)
last := len(segs) - 1
if last < 0 {
return nil, dns.RcodeServerFailure
}
// 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
namespace := segs[last]
if !k.namespaceExposed(namespace) {
return nil, dns.RcodeNameError
}
last--
if last < 0 {
return nil, dns.RcodeSuccess
}
service := segs[last]
last--
if last == 0 {
endpoint = stripUnderscore(segs[last])
last--
} else if last == 1 {
protocol = stripUnderscore(segs[last])
port = stripUnderscore(segs[last-1])
last -= 2
}
if last != -1 {
// too long
return nil, dns.RcodeNameError
}
var (
endpointsList []*object.Endpoints
serviceList []*object.Service
)
idx := object.ServiceKey(service, namespace)
serviceList = k.APIConn.SvcIndex(idx)
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
}
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 {
continue
}
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
}
rcode = dns.RcodeSuccess
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
} else {
for _, ip := range svc.ExternalIPs {
for _, p := range svc.Ports {
if !(matchPortAndProtocol(port, p.Name, protocol, string(p.Protocol))) {
continue
}
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)
}
}
}
}
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
}
return services, rcode
}
// ExternalAddress returns the external service address(es) for the CoreDNS service.
func (k *Kubernetes) ExternalAddress(state request.Request, headless bool) []dns.RR {
// 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.
return k.nsAddrs(true, headless, state.Zone)
}
// 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) {
zonePath := msg.Path(zone, coredns)
headlessServices = make(map[string][]msg.Service)
for _, svc := range k.APIConn.ServiceList() {
// 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)
s.Key = strings.Join(append([]string{zonePath, svc.Namespace, svc.Name}, strings.ToLower("_"+p.Protocol), strings.ToLower("_"+p.Name)), "/")
headlessServices[strings.Join([]string{s.Key, PortProtocol}, "/")] = append(headlessServices[strings.Join([]string{s.Key, PortProtocol}, "/")], s)
}
}
}
}
continue
} else {
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)
}
}
}
}
return services, headlessServices
}
// ExternalSerial returns the serial of the external zone
func (k *Kubernetes) ExternalSerial(string) uint32 {
return uint32(k.APIConn.Modified(true))
}
// 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
}