coredns源码分析

  • Post author:
  • Post category:其他


CoreDNS是使用go语言编写的快速灵活的DNS服务,采用链式插件模式,每个插件实现独立的功能,底层协议可以是tcp/udp,也可以是TLS,gRPC等。默认监听所有ip地址,可使用bind插件指定监听指定地址。

配置文件

格式如下

[SCHEME://]ZONE [[SCHEME://]ZONE]...[:PORT] {
    [PLUGIN]...
}

SCHEME是可选的,默认值为dns://,也可以指定为tls://,grpc://或者https://。

ZONE是可选的,指定了此dnsserver可以服务的域名前缀,如果不指定,则默认为root,表示可以接收所有的dns请求。

PORT是选项的,指定了监听端口号,默认为53,如果这里指定了端口号,则不能通过参数-dns.port覆盖。

一块上面格式的配置表示一个dnsserver,称为serverblock,可以配置多个serverblock表示多个dnsserver。

下面通过一个例子说明,如下配置文件指定了4个serverblock,即4个dnsserver,第一个监听端口5300,后面三个监听同一个端口53,每个dnsserver指定了特定的插件。

coredns.io:5300 {
    file /etc/coredns/zones/coredns.io.db
}

example.io:53 {
    log    
    errors
    file /etc/coredns/zones/example.io.db
}

example.net:53 {
    file /etc/coredns/zones/example.net.db
}

.:53 {
    kubernetes
    errors
    log
    health
}

下图为配置的简略图

image.png

a. 从图中可看到插件执行顺序不是配置文件中的顺序,这是因为插件执行顺序是在源码目录中的plugin.cfg指定的,一旦编译后,顺序就固定了。

b. .根serverblock虽然指定了health,但是图中却没有,这是因为health插件不参与dns请求的处理。能处理dns请求的插件必须提供如下两个接口函数。

Handler interface {
    ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
    Name() string
}


dns请求处理流程


收到dns请求后,首先根据域名匹配zone找到对应的dnsserver(最长匹配优先),如果没有匹配到,则使用默认的root dnsserver。

找到dnsserver后,就要按照插件顺序执行其中配置的插件,当然并不是配置的插件都会被执行,如果某个插件成功找到记录,则返回成功,否则根据插件是否配置了fallthrough等来决定是否执行下一个插件。

源码分析


plugin.cfg


源码目录下的plugin.cfg指定了插件执行顺序,如果想添加插件,可按格式添加到指定位置。

metadata:metadata
geoip:geoip
cancel:cancel
tls:tls
reload:reload
nsid:nsid
bufsize:bufsize
root:root
bind:bind
debug:debug
trace:trace
ready:ready
health:health
pprof:pprof
prometheus:metrics
errors:errors
log:log
dnstap:dnstap
local:local
dns64:dns64
acl:acl
any:any
chaos:chaos
loadbalance:loadbalance
cache:cache
rewrite:rewrite
header:header
dnssec:dnssec
autopath:autopath
minimal:minimal
template:template
transfer:transfer
hosts:hosts
route53:route53
azure:azure
clouddns:clouddns
k8s_external:k8s_external
kubernetes:kubernetes
file:file
auto:auto
secondary:secondary
etcd:etcd
loop:loop
forward:forward
grpc:grpc
erratic:erratic
whoami:whoami
on:github.com/coredns/caddy/onevent
sign:sign

源码目录下的Makefile根据plugin.cfg生成了两个go文件:zplugin.go和zdirectives.go。

core/plugin/zplugin.go core/dnsserver/zdirectives.go: plugin.cfg
    go generate coredns.go
    go get

core/plugin/zplugin.go会导入所有的插件,执行所有插件的init函数。
import (
    // Include all plugins.
    _ "github.com/coredns/caddy/onevent"
    _ "github.com/coredns/coredns/plugin/acl"
    _ "github.com/coredns/coredns/plugin/any"
    _ "github.com/coredns/coredns/plugin/auto"
    _ "github.com/coredns/coredns/plugin/autopath"
    _ "github.com/coredns/coredns/plugin/azure"
    _ "github.com/coredns/coredns/plugin/bind"
    _ "github.com/coredns/coredns/plugin/bufsize"
    _ "github.com/coredns/coredns/plugin/cache"
    _ "github.com/coredns/coredns/plugin/cancel"
    _ "github.com/coredns/coredns/plugin/chaos"
    _ "github.com/coredns/coredns/plugin/clouddns"
    _ "github.com/coredns/coredns/plugin/debug"
    _ "github.com/coredns/coredns/plugin/dns64"
    _ "github.com/coredns/coredns/plugin/dnssec"
    _ "github.com/coredns/coredns/plugin/dnstap"
    _ "github.com/coredns/coredns/plugin/erratic"
    _ "github.com/coredns/coredns/plugin/errors"
    _ "github.com/coredns/coredns/plugin/etcd"
    _ "github.com/coredns/coredns/plugin/file"
    _ "github.com/coredns/coredns/plugin/forward"
    _ "github.com/coredns/coredns/plugin/geoip"
    _ "github.com/coredns/coredns/plugin/grpc"
    _ "github.com/coredns/coredns/plugin/header"
    _ "github.com/coredns/coredns/plugin/health"
    _ "github.com/coredns/coredns/plugin/hosts"
    _ "github.com/coredns/coredns/plugin/k8s_external"
    _ "github.com/coredns/coredns/plugin/kubernetes"
    _ "github.com/coredns/coredns/plugin/loadbalance"
    _ "github.com/coredns/coredns/plugin/local"
    _ "github.com/coredns/coredns/plugin/log"
    _ "github.com/coredns/coredns/plugin/loop"
    _ "github.com/coredns/coredns/plugin/metadata"
    _ "github.com/coredns/coredns/plugin/metrics"
    _ "github.com/coredns/coredns/plugin/minimal"
    _ "github.com/coredns/coredns/plugin/nsid"
    _ "github.com/coredns/coredns/plugin/pprof"
    _ "github.com/coredns/coredns/plugin/ready"
    _ "github.com/coredns/coredns/plugin/reload"
    _ "github.com/coredns/coredns/plugin/rewrite"
    _ "github.com/coredns/coredns/plugin/root"
    _ "github.com/coredns/coredns/plugin/route53"
    _ "github.com/coredns/coredns/plugin/secondary"
    _ "github.com/coredns/coredns/plugin/sign"
    _ "github.com/coredns/coredns/plugin/template"
    _ "github.com/coredns/coredns/plugin/tls"
    _ "github.com/coredns/coredns/plugin/trace"
    _ "github.com/coredns/coredns/plugin/transfer"
    _ "github.com/coredns/coredns/plugin/whoami"
)

core/dnsserver/zdirectives.go将所有插件名字放在一个数组中。

var Directives = []string{
    "metadata",
    "geoip",
    "cancel",
    "tls",
    "reload",
    "nsid",
    "bufsize",
    "root",
    "bind",
    "debug",
    "trace",
    "ready",
    "health",
    "pprof",
    "prometheus",
    "errors",
    "log",
    "dnstap",
    "local",
    "dns64",
    "acl",
    "any",
    "chaos",
    "loadbalance",
    "cache",
    "rewrite",
    "header",
    "dnssec",
    "autopath",
    "minimal",
    "template",
    "transfer",
    "hosts",
    "route53",
    "azure",
    "clouddns",
    "k8s_external",
    "kubernetes",
    "file",
    "auto",
    "secondary",
    "etcd",
    "loop",
    "forward",
    "grpc",
    "erratic",
    "whoami",
    "on",
    "sign",
}


codedns 主函数

//coredns/coredns.go
import (
    _ "github.com/coredns/coredns/core/plugin" // Plug in CoreDNS.
    "github.com/coredns/coredns/coremain"
)

main
    coremain.Run()

codedns.go 首先导入了包”github.com/coredns/coredns/core/plugin”,此包内只有一个文件zplugin.go,此文件为自动生成的,主要导入了所有的插件,执行每个插件的init函数。

接着执行 run.go Run

//coredns/coremain/run.go
import (
    ...
    "github.com/coredns/coredns/core/dnsserver"
)

func Run()
    //解析参数
    flag.Parse()

    //如果指定了参数 version,则打印版本信息后退出
    if version {
        showVersion()
        os.Exit(0)
    }
    
    //如果指定了参数 plugins,则只打印插件信息后退出
    if plugins {
        fmt.Println(caddy.DescribePlugins())
        os.Exit(0)
    }

    //解析配置文件
    corefile, err := caddy.LoadCaddyfile(serverType)
        cdyfile, err := loadCaddyfileInput(serverType)
            for _, l := range caddyfileLoaders {
                //执行 confLoader
                cdyfile, err := l.loader.Load(serverType)
            }

    instance, err := caddy.Start(corefile)
    
    // Twiddle your thumbs
    instance.Wait()

此文件又引入了包”github.com/coredns/coredns/core/dnsserver”,其init函数在 dnsserver/register.go 文件中,如下所示,主要是注册了serverType

const serverType = "dns"
// DefaultPort is the default port.
const DefaultPort = transport.Port
Port = "53"

func init() {
    flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port")
    flag.StringVar(&Port, "p", DefaultPort, "Default port")

    caddy.RegisterServerType(serverType, caddy.ServerType{
        Directives: func() []string { return Directives },
        DefaultInput: func() caddy.Input {
            return caddy.CaddyfileInput{
                Filepath:       "Corefile",
                Contents:       []byte(".:" + Port + " {\nwhoami\nlog\n}\n"),
                ServerTypeName: serverType,
            }
        },
        NewContext: newContext,
    })
}

剩下的就是解析参数,解析配置文件后,执行caddy.Start。

这里就是根据配置文件中指定的serverblock,执行插件的setup进行初始化,创建对应的server,开始监听dns请求

//caddy/caddy.go
func Start(cdyfile Input) (*Instance, error)
    inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}
    err := startWithListenerFds(cdyfile, inst, nil)

func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error {
    ValidateAndExecuteDirectives(cdyfile, inst, false)
        //stypeName 为 dns
        stypeName := cdyfile.ServerType()
        //stype 通过 RegisterServerType 注册,在 //coredns/core/dnsserver/register.go init时注册
        stype, err := getServerType(stypeName)
            stype, ok := serverTypes[serverType]
            if ok {
                return stype, nil
            }
            ...

        inst.caddyfileInput = cdyfile

        //func loadServerBlocks(serverType, filename string, input io.Reader) ([]caddyfile.ServerBlock, error)
        sblocks, err := loadServerBlocks(stypeName, cdyfile.Path(), bytes.NewReader(cdyfile.Body()))
            validDirectives := ValidDirectives(serverType)
            serverBlocks, err := caddyfile.Parse(filename, input, validDirectives)
                p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives}
                    // NewDispenser returns a Dispenser, ready to use for parsing the given input.
                    func NewDispenser(filename string, input io.Reader) Dispenser {
                        tokens, _ := allTokens(input) // ignoring error because nothing to do with it
                        return Dispenser{
                            filename: filename,
                            tokens:   tokens,
                            cursor:   -1,
                        }
                    }
                return p.parseAll()

        //coredns/core/dnsserver/register.go:newContext
        inst.context = stype.NewContext(inst)

        //coredns/core/dnsserver/register.go:InspectServerBlocks
        sblocks, err = inst.context.InspectServerBlocks(cdyfile.Path(), sblocks)

        return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
            //遍历执行插件注册的 setup 函数
            for _, dir := range directives {
                for i, sb := range sblocks {
                    //获取插件的初始化函数 setup
                    setup, err := DirectiveAction(inst.serverType, dir)
                        if stypePlugins, ok := plugins[serverType]; ok {
                            if plugin, ok := stypePlugins[dir]; ok {
                                return plugin.Action, nil
                            }
                        }
                    //执行插件注册的 setup 函数
                    setup(controller)
                        //将 onStart 添加到数组 c.instance.OnStartup
                        c.OnStartup(onStart)
                        //每个插件的setup函数都会调用如下函数,注册插件handler
                        //AddPlugin -> c.Plugin = append(c.Plugin, m)
                        dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
                            l.Next = next
                            return l
                        })
                }
            }

    slist, err := inst.context.MakeServers()
        errValid := h.validateZonesAndListeningAddresses()
        for _, c := range h.configs {
            c.Plugin = c.firstConfigInBlock.Plugin
            c.ListenHosts = c.firstConfigInBlock.ListenHosts
            c.Debug = c.firstConfigInBlock.Debug
            c.TLSConfig = c.firstConfigInBlock.TLSConfig
        }

        //将监听相同地址的config放在同一个group。一个config表示一个 dnsserver
        // we must map (group) each config to a bind address
        groups, err := groupConfigsByListenAddr(h.configs)
            groups := make(map[string][]*Config)
            for _, conf := range configs {
                for _, h := range conf.ListenHosts {
                    addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))
                    if err != nil {
                        return nil, err
                    }
                    addrstr := conf.Transport + "://" + addr.String()
                    groups[addrstr] = append(groups[addrstr], conf)
                }
            }

            return groups, nil

        //为同一个组的config创建一个server,多个插件共享一个底层server,上层通过
        //zone区分请求是到哪个dnsserver的
        // then we create a server for each group
        var servers []caddy.Server
        for addr, group := range groups {
            // switch on addr
            switch tr, _ := parse.Transport(addr); tr {
            case transport.DNS:
                s, err := NewServer(addr, group)
                    s := &Server{
                        Addr:         addr,
                        zones:        make(map[string]*Config),
                        graceTimeout: 5 * time.Second,
                    }

                    //site的类型是 Config,每个site表示一个dnsserver
                    for _, site := range group {
                        // set the config per zone
                        s.zones[site.Zone] = site

                        //遍历每个dnsserver配置的插件
                        //site.Plugin 为每个插件初始化setup时调用 dnsserver.GetConfig(c).AddPlugin 生成,
                        //顺序是按照数组 Directives 从前向后
                        // compile custom plugin for everything
                        var stack plugin.Handler
                        //从后向前逆序遍历 site.Plugin
                        for i := len(site.Plugin) - 1; i >= 0; i-- {
                            stack = site.Plugin[i](stack)

                            // register the *handler* also
                            site.registerHandler(stack)
                                c.registry[h.Name()] = h
                                
                            if s.trace == nil && stack.Name() == "trace" {
                                // we have to stash away the plugin, not the
                                // Tracer object, because the Tracer won't be initialized yet
                                if t, ok := stack.(trace.Trace); ok {
                                    s.trace = t
                                }
                            }
                            // Unblock CH class queries when any of these plugins are loaded.
                            if _, ok := EnableChaos[stack.Name()]; ok {
                                s.classChaos = true
                            }
                        }
                        //pluginChain 为第一个插件的 handler,收到dns请求后,先执行第一个插件的 handler
                        //在 ServeDNS(core/dnsserver/server.go) 函数中执行 pluginChain
                        site.pluginChain = stack
                    }

                    return s, nil
                servers = append(servers, s)
            ...
            }
        }
        
        return servers, nil
        
    for _, startupFunc := range inst.OnStartup {
        //比如 kubernetes 的 onStart 函数
        err = startupFunc()
    }

    startServers(slist, inst, restartFds)
        for _, s := range serverList {
            if ln == nil {
                ln, err = s.Listen()
                    //core/dnsserver/server.go:Listen
                    l, err := reuseport.Listen("tcp", s.Addr[len(transport.DNS+"://"):])
                    return l, nil
            }

            if pc == nil {
                pc, err = s.ListenPacket()
                    //core/dnsserver/server.go:ListenPacket
                    p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.DNS+"://"):])
                    return p, nil
            }
            inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
        }
        
        for _, s := range inst.servers {
            func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
                go func() {
                    defer func() {
                        inst.wg.Done()
                        stopWg.Done()
                    }()
                    errChan <- s.Serve(ln)
                }()

                go func() {
                    defer func() {
                        inst.wg.Done()
                        stopWg.Done()
                    }()
                    errChan <- s.ServePacket(pc)
                }()
            }(s.server, s.listener, s.packet, inst)
        }
}

tcp协议调用Serve,udp协议调用ServePacket

//core/dnsserver/server.go
// Serve starts the server with an existing listener. It blocks until the server stops.
// This implements caddy.TCPServer interface.
func (s *Server) Serve(l net.Listener) error {
    s.m.Lock()
    s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
        ctx := context.WithValue(context.Background(), Key{}, s)
        ctx = context.WithValue(ctx, LoopKey{}, 0)
        s.ServeDNS(ctx, w, r)
    })}
    s.m.Unlock()

    return s.server[tcp].ActivateAndServe()
}

// ServePacket starts the server with an existing packetconn. It blocks until the server stops.
// This implements caddy.UDPServer interface.
func (s *Server) ServePacket(p net.PacketConn) error {
    s.m.Lock()
    s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
        ctx := context.WithValue(context.Background(), Key{}, s)
        ctx = context.WithValue(ctx, LoopKey{}, 0)
        s.ServeDNS(ctx, w, r)
    })}
    s.m.Unlock()

    return s.server[udp].ActivateAndServe()
}

收到DNS请求后,调用ServeDNS,根据域名匹配dnsserver,如果没有匹配不到则使用根dnsserver,然后执行dnsserver中配置的插件

// ServeDNS is the entry point for every request to the address that
// is bound to. It acts as a multiplexer for the requests zonename as
// defined in the request so that the correct zone
// (configuration and plugin stack) will handle the request.
func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) {
    // The default dns.Mux checks the question section size, but we have our
    // own mux here. Check if we have a question section. If not drop them here.
    if r == nil || len(r.Question) == 0 {
        errorAndMetricsFunc(s.Addr, w, r, dns.RcodeServerFailure)
        return
    }

    // Wrap the response writer in a ScrubWriter so we automatically make the reply fit in the client's buffer.
    w = request.NewScrubWriter(r, w)

    q := strings.ToLower(r.Question[0].Name)
    var (
        off       int
        end       bool
        dshandler *Config
    )

    //根据dns请求的域名作为zone(最长匹配优先),遍历 s.zones 进行匹配(每个zone表示一个dnsserver),
    //如果匹配到了,则设置 dshandler = h
    for {
        if h, ok := s.zones[q[off:]]; ok {
            if h.pluginChain == nil { // zone defined, but has not got any plugins
                errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)
                return
            }
            if r.Question[0].Qtype != dns.TypeDS {
                rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
                if !plugin.ClientWrite(rcode) {
                    errorFunc(s.Addr, w, r, rcode)
                }
                return
            }
            // The type is DS, keep the handler, but keep on searching as maybe we are serving
            // the parent as well and the DS should be routed to it - this will probably *misroute* DS
            // queries to a possibly grand parent, but there is no way for us to know at this point
            // if there is an actual delegation from grandparent -> parent -> zone.
            // In all fairness: direct DS queries should not be needed.
            dshandler = h
        }
        off, end = dns.NextLabel(q, off)
        if end {
            break
        }
    }

    //匹配到zone,执行dnsserver的插件的 ServeDNS。
    //如果插件的 ServeDNS 直接返回了(比如k8s插件查找成功时),则只执行一个插件,
    //如果插件的 ServeDNS 调用plugin.NextOrFailure,则开始执行下一个插件 ServeDNS 了,
    //依次类推,直到有插件返回成功或者失败。
    if r.Question[0].Qtype == dns.TypeDS && dshandler != nil && dshandler.pluginChain != nil {
        // DS request, and we found a zone, use the handler for the query.
        rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r)
        if !plugin.ClientWrite(rcode) {
            errorFunc(s.Addr, w, r, rcode)
        }
        return
    }

    //如果dnsserver没有匹配的zone,则最后尝试执行根zone,即配置文件中指定的"."
    // Wildcard match, if we have found nothing try the root zone as a last resort.
    if h, ok := s.zones["."]; ok && h.pluginChain != nil {
        rcode, _ := h.pluginChain.ServeDNS(ctx, w, r)
        if !plugin.ClientWrite(rcode) {
            errorFunc(s.Addr, w, r, rcode)
        }
        return
    }

    // Still here? Error out with REFUSED.
    errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused)
}

以k8s插件为例

//k8s插件的 ServeDNS 函数
// ServeDNS implements the plugin.Handler interface.
func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
    state := request.Request{W: w, Req: r}

    qname := state.QName()
    zone := plugin.Zones(k.Zones).Matches(qname)
    if zone == "" {
        return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r)
    }
    zone = qname[len(qname)-len(zone):] // maintain case of original query
    state.Zone = zone

    var (
        records   []dns.RR
        extra     []dns.RR
        truncated bool
        err       error
    )

    switch state.QType() {
    case dns.TypeA:
        records, truncated, err = plugin.A(ctx, &k, zone, state, nil, plugin.Options{})
    case dns.TypeAAAA:
        records, truncated, err = plugin.AAAA(ctx, &k, zone, state, nil, plugin.Options{})
    case dns.TypeTXT:
        records, truncated, err = plugin.TXT(ctx, &k, zone, state, nil, plugin.Options{})
    case dns.TypeCNAME:
        records, err = plugin.CNAME(ctx, &k, zone, state, plugin.Options{})
    case dns.TypePTR:
        records, err = plugin.PTR(ctx, &k, zone, state, plugin.Options{})
    case dns.TypeMX:
        records, extra, err = plugin.MX(ctx, &k, zone, state, plugin.Options{})
    case dns.TypeSRV:
        records, extra, err = plugin.SRV(ctx, &k, zone, state, plugin.Options{})
    case dns.TypeSOA:
        if qname == zone {
            records, err = plugin.SOA(ctx, &k, zone, state, plugin.Options{})
        }
    case dns.TypeAXFR, dns.TypeIXFR:
        return dns.RcodeRefused, nil
    case dns.TypeNS:
        if state.Name() == zone {
            records, extra, err = plugin.NS(ctx, &k, zone, state, plugin.Options{})
            break
        }
        fallthrough
    default:
        // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
        fake := state.NewWithQuestion(state.QName(), dns.TypeA)
        fake.Zone = state.Zone
        _, _, err = plugin.A(ctx, &k, zone, fake, nil, plugin.Options{})
    }

    //没有查找到 dns 记录时,如果配置了fallthrough,则执行下一个插件,
    //否则返回错误信息
    if k.IsNameError(err) {
        if k.Fall.Through(state.Name()) {
            return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r)
        }
        if !k.APIConn.HasSynced() {
            // If we haven't synchronized with the kubernetes cluster, return server failure
            return plugin.BackendError(ctx, &k, zone, dns.RcodeServerFailure, state, nil /* err */, plugin.Options{})
        }
        return plugin.BackendError(ctx, &k, zone, dns.RcodeNameError, state, nil /* err */, plugin.Options{})
    }
    if err != nil {
        return dns.RcodeServerFailure, err
    }

    if len(records) == 0 {
        return plugin.BackendError(ctx, &k, zone, dns.RcodeSuccess, state, nil, plugin.Options{})
    }

    //查到dns记录,返回dns响应
    m := new(dns.Msg)
    m.SetReply(r)
    m.Truncated = truncated
    m.Authoritative = true
    m.Answer = append(m.Answer, records...)
    m.Extra = append(m.Extra, extra...)
    w.WriteMsg(m)
    return dns.RcodeSuccess, nil
}

// SRV returns SRV records from the Backend.
// If the Target is not a name but an IP address, a name is created on the fly.
func SRV(ctx context.Context, b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, err error) {
    //比如对于 kubernetes 插件来说,b.Services 为 coredns/plugin/kubernetes/kubernetes.go:Services
    services, err := b.Services(ctx, state, false, opt)

    dup := make(map[item]struct{})
    lookup := make(map[string]struct{})

    // Looping twice to get the right weight vs priority. This might break because we may drop duplicate SRV records latter on.
    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
    }
    for _, serv := range services {
        // Don't add the entry if the port is -1 (invalid). The kubernetes plugin uses port -1 when a service/endpoint
        // does not have any declared ports.
        if serv.Port == -1 {
            continue
        }
        w1 := 100.0 / float64(w[serv.Priority])
        if serv.Weight == 0 {
            w1 *= 100
        } else {
            w1 *= float64(serv.Weight)
        }
        weight := uint16(math.Floor(w1))
        // weight should be at least 1
        if weight == 0 {
            weight = 1
        }

        what, ip := serv.HostType()

        switch what {
        case dns.TypeCNAME:
            srv := serv.NewSRV(state.QName(), weight)
            records = append(records, srv)

            if _, ok := lookup[srv.Target]; ok {
                break
            }

            lookup[srv.Target] = struct{}{}

            if !dns.IsSubDomain(zone, srv.Target) {
                m1, e1 := b.Lookup(ctx, state, srv.Target, dns.TypeA)
                if e1 == nil {
                    extra = append(extra, m1.Answer...)
                }

                m1, e1 = b.Lookup(ctx, state, srv.Target, dns.TypeAAAA)
                if e1 == nil {
                    // If we have seen CNAME's we *assume* that they are already added.
                    for _, a := range m1.Answer {
                        if _, ok := a.(*dns.CNAME); !ok {
                            extra = append(extra, a)
                        }
                    }
                }
                break
            }
            // Internal name, we should have some info on them, either v4 or v6
            // Clients expect a complete answer, because we are a recursor in their view.
            state1 := state.NewWithQuestion(srv.Target, dns.TypeA)
            addr, _, e1 := A(ctx, b, zone, state1, nil, opt)
            if e1 == nil {
                extra = append(extra, addr...)
            }
            // TODO(miek): AAAA as well here.

        case dns.TypeA, dns.TypeAAAA:
            addr := serv.Host
            serv.Host = msg.Domain(serv.Key)
            srv := serv.NewSRV(state.QName(), weight)

            if ok := isDuplicate(dup, srv.Target, "", srv.Port); !ok {
                records = append(records, srv)
            }

            if ok := isDuplicate(dup, srv.Target, addr, 0); !ok {
                extra = append(extra, newAddress(serv, srv.Target, ip, what))
            }
        }
    }
    return records, extra, nil
}

func (k *Kubernetes) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (svcs []msg.Service, err error) {
    s, e := k.Records(ctx, state, false)
        r, e := parseRequest(state.Name(), state.Zone)
        services, err := k.findServices(r, state.Zone)
            //根据dns请求的service和namespace获取index
            idx := object.ServiceKey(r.service, r.namespace)
            //根据index从缓存获取 service 信息
            serviceList = k.APIConn.SvcIndex(idx)
            endpointsListFunc = func() []*object.Endpoints { return k.APIConn.EpIndex(idx) }

            zonePath := msg.Path(zone, coredns)
            for _, svc := range serviceList {
            }
        return services, err


参考


//如何写coredns插件


http://dockone.io/article/9620

//coredns源码分析


https://wenku.baidu.com/view/34cabc1e346baf1ffc4ffe4733687e21af45ff7c.html



https://blog.csdn.net/zhonglinzhang/article/details/99679323



https://www.codercto.com/a/89703.html

//NodeLocal DNSCache


https://www.cnblogs.com/sanduzxcvbnm/p/16013560.html



https://blog.csdn.net/xixihahalelehehe/article/details/118894971

也可参考:

coredns源码分析 – 简书 (jianshu.com)



版权声明:本文为fengcai_ke原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。