coredns/plugin/kubernetes/xfr.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

195 lines
5.4 KiB
Go

package kubernetes
import (
"context"
"math"
"net"
"sort"
"strings"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/transfer"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
api "k8s.io/api/core/v1"
)
// Transfer implements the transfer.Transfer interface.
func (k *Kubernetes) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) {
match := plugin.Zones(k.Zones).Matches(zone)
if match == "" {
return nil, transfer.ErrNotAuthoritative
}
// state is not used here, hence the empty request.Request{]
soa, err := plugin.SOA(context.TODO(), k, zone, request.Request{}, plugin.Options{})
if err != nil {
return nil, transfer.ErrNotAuthoritative
}
ch := make(chan []dns.RR)
zonePath := msg.Path(zone, "coredns")
serviceList := k.APIConn.ServiceList()
go func() {
// ixfr fallback
if serial != 0 && soa[0].(*dns.SOA).Serial == serial {
ch <- soa
close(ch)
return
}
ch <- soa
nsAddrs := k.nsAddrs(false, false, zone)
nsHosts := make(map[string]struct{})
for _, nsAddr := range nsAddrs {
nsHost := nsAddr.Header().Name
if _, ok := nsHosts[nsHost]; !ok {
nsHosts[nsHost] = struct{}{}
ch <- []dns.RR{&dns.NS{Hdr: dns.RR_Header{Name: zone, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: k.ttl}, Ns: nsHost}}
}
ch <- nsAddrs
}
sort.Slice(serviceList, func(i, j int) bool {
return serviceList[i].Name < serviceList[j].Name
})
for _, svc := range serviceList {
if !k.namespaceExposed(svc.Namespace) {
continue
}
svcBase := []string{zonePath, Svc, svc.Namespace, svc.Name}
switch svc.Type {
case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
clusterIP := net.ParseIP(svc.ClusterIPs[0])
if clusterIP != nil {
var host string
for _, ip := range svc.ClusterIPs {
s := msg.Service{Host: ip, TTL: k.ttl}
s.Key = strings.Join(svcBase, "/")
// Change host from IP to Name for SRV records
host = emitAddressRecord(ch, s)
}
for _, p := range svc.Ports {
s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
s.Key = strings.Join(svcBase, "/")
// Need to generate this to handle use cases for peer-finder
// ref: https://github.com/coredns/coredns/pull/823
ch <- []dns.RR{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("_"+p.Name)), "/")
ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), 100)}
}
// Skip endpoint discovery if clusterIP is defined
continue
}
endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
for _, ep := range endpointsList {
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(ch, s)
s.Key = strings.Join(append(svcBase, endpointHostname(addr, k.endpointNameMode)), "/")
// Change host from IP to Name for SRV records
host := emitAddressRecord(ch, 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("_"+p.Protocol), strings.ToLower("_"+p.Name)), "/")
ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), srvWeight)}
}
}
}
}
case api.ServiceTypeExternalName:
s := msg.Service{Key: strings.Join(svcBase, "/"), Host: svc.ExternalName, TTL: k.ttl}
if t, _ := s.HostType(); t == dns.TypeCNAME {
ch <- []dns.RR{s.NewCNAME(msg.Domain(s.Key), s.Host)}
}
}
}
ch <- soa
close(ch)
}()
return ch, nil
}
// 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, s msg.Service) string {
ip := net.ParseIP(s.Host)
dnsType, _ := s.HostType()
switch dnsType {
case dns.TypeA:
r := s.NewA(msg.Domain(s.Key), ip)
c <- []dns.RR{r}
return r.Hdr.Name
case dns.TypeAAAA:
r := s.NewAAAA(msg.Domain(s.Key), ip)
c <- []dns.RR{r}
return r.Hdr.Name
}
return ""
}
// 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
}
weight := uint16(math.Floor((100.0 / float64(w[0])) * 100))
// weight should be at least 1
if weight == 0 {
weight = 1
}
return weight
}