host-forward-etcd-plugin
# 1.hosts
# 1.1.注册
hosts plugin也会实现Handler接口定义方法,注册setup()回调,用于服务启动阶段执行plugin初始化及chain组装。func init() { plugin.Register("hosts", setup) } // 注册回调 func setup(c *caddy.Controller) error { // 解析hosts plugin配置(hosts映射加载到inline) h, err := hostsParse(c) ... // 根据reload参数定期更新hosts parseChan := periodicHostsUpdate(&h) // 启动回调 c.OnStartup(func() error { // 构造hosts解析 h.readHosts() return nil }) // 终止回调 c.OnShutdown(func() error { close(parseChan) return nil }) // hosts plugin初始化 dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { h.Next = next return h }) return nil } // 定时更新hosts func periodicHostsUpdate(h *Hosts) chan bool { ... go func() { // 初始化定时器(5s) ticker := time.NewTicker(h.options.reload) for { select { case <-parseChan: return // 触发更新 case <-ticker.C: h.readHosts() } } }() return parseChan } // determines if the cached data needs to be updated based on the size and modification time of the hostsfile. func (h *Hostsfile) readHosts() { // 读取外部hosts配置 file, err := os.Open(h.path) ... defer file.Close() // 文件状态 stat, err := file.Stat() ... // 文件未修改,无需同步 if h.mtime.Equal(stat.ModTime()) && size == stat.Size() { return } // 解析hosts文件内容 newMap := h.parse(file) h.Lock() // 更新 h.hmap = newMap // Update the data cache. h.mtime = stat.ModTime() h.size = stat.Size() ... h.Unlock() }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
注意
hosts plugin默认加载/etc/hosts配置,与直接配置的ip domain共同执行解析
# 1.2.解析
hosts plugin实现serveDNS()用于解析dns请求,基于加载的hosts文件及配置的hosts block解析域名。// ServeDNS implements the plugin.Handle interface. func (h Hosts) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { ... // 域名 qname := state.Name() ... // 匹配域名的zone zone := plugin.Zones(h.Origins).Matches(qname) // 无法处理,交给其它plugin if zone == "" { // PTR zones don't need to be specified in Origins. if state.QType() != dns.TypePTR { return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) } } switch state.QType() { // 反向解析 case dns.TypePTR: // IP找域名 names := h.LookupStaticAddr(dnsutil.ExtractAddressFromReverse(qname)) // 未找到,交给其它plugin if len(names) == 0 { // If this doesn't match we need to fall through regardless of h.Fallthrough return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) } answers = h.ptr(qname, h.options.ttl, names) // IPV4解析 case dns.TypeA: // 域名找IP ips := h.LookupStaticHostV4(qname) answers = a(qname, h.options.ttl, ips) // IPV6解析 case dns.TypeAAAA: // 域名找IP ips := h.LookupStaticHostV6(qname) answers = aaaa(qname, h.options.ttl, ips) } // 解析为空 if len(answers) == 0 && !h.otherRecordsExist(qname) { // 设置fallthrough,交给其它plugin if h.Fall.Through(qname) { return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r) } return dns.RcodeServerFailure, nil } ... m.Answer = answers w.WriteMsg(m) return dns.RcodeSuccess, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
注意
1.
hosts解析其实就是从inline(直接配置)和hmap(文件加载)缓存查找映射2.
hosts plugin无法解析,根据不同情况交给其它插件处理
# 2.forward
# 2.1.注册
同样,
forward plugin也会实现Handler接口定义方法,注册setup()回调,用于服务启动阶段执行plugin初始化及chain组装。func init() { plugin.Register("forward", setup) } func setup(c *caddy.Controller) error { // 解析forward定义 fs, err := parseForward(c) ... // 遍历每块定义,作为单独plugin for i := range fs { f := fs[i] // forward plugin的上游不能超过15 if f.Len() > max { return plugin.Error("forward", fmt.Errorf("more than %d TOs configured: %d", max, f.Len())) } // 最后一个forward plugin指向其它插件 if i == len(fs)-1 { // last forward: point next to next plugin dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { f.Next = next return f }) // 其它forward plugin指向下一个forward plugin } else { // middle forward: point next to next forward nextForward := fs[i+1] dnsserver.GetConfig(c).AddPlugin(func(plugin.Handler) plugin.Handler { f.Next = nextForward return f }) } // 启动回调 c.OnStartup(func() error { return f.OnStartup() }) // dnstap插件关联,记录转发流量 c.OnStartup(func() error { if taph := dnsserver.GetConfig(c).Handler("dnstap"); taph != nil { if tapPlugin, ok := taph.(dnstap.Dnstap); ok { f.tapPlugin = &tapPlugin } } return nil }) //终止回调 c.OnShutdown(func() error { return f.OnShutdown() }) } return nil } // OnStartup starts a goroutines for all proxies. func (f *Forward) OnStartup() (err error) { for _, p := range f.proxies { // 启动proxy检测 p.start(f.hcInterval) } return nil } // start starts the proxy's healthchecking. func (p *Proxy) start(duration time.Duration) { // 更新probe执行周期(500ms) p.probe.Start(duration) // 启动连接管理器 p.transport.Start() } // Start starts the transport's connection manager. func (t *Transport) Start() { go t.connManager() } // connManagers manages the persistent connection cache for UDP and TCP. func (t *Transport) connManager() { // 10s定时器 ticker := time.NewTicker(defaultExpire) Wait: for { select { // 申请连接(serveDNS触发) case proto := <-t.dial: ... // 根据协议取最近使用的连接 if stack := t.conns[transtype]; len(stack) > 0 { pc := stack[len(stack)-1] // 取出的连接未过期(10s) if time.Since(pc.used) < t.expire { // 出队返给申请方 t.conns[transtype] = stack[:len(stack)-1] t.ret <- pc continue Wait } // 过期,清空连接缓存 t.conns[transtype] = nil // 关闭旧连接 go closeConns(stack) } // 返给申请方nil t.ret <- nil // 归还连接 case pc := <-t.yield: // 重新缓存归还连接 transtype := t.transportTypeFromConn(pc) t.conns[transtype] = append(t.conns[transtype], pc) // 定时清理及关闭过期连接 case <-ticker.C: t.cleanup(false) // 服务终止,清理及关闭所有连接 case <-t.stop: t.cleanup(true) close(t.ret) return } } } // proxy in forward plugin // NewProxy returns a new proxy. func NewProxy(addr, trans string) *Proxy { p := &Proxy{ addr: addr, fails: 0, probe: up.New(), transport: newTransport(addr), } // 健康检查transport connManager退出 p.health = NewHealthChecker(trans, true, ".") // proxy退出前GC,触发 runtime.SetFinalizer(p, (*Proxy).finalizer) return p }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
注意
1.
forward plugin支持定义多个,根据顺序相互串联2.
forward plugin启动回调重要的是transport复用池,支持连接复用、缓存及周期回收
# 2.2.解析
forward plugin实现serveDNS()解析dns请求,基于上游dns proxy代理转发请求进行解析,proxy连接会缓存复用,根据过期(10s)状态定期回收。// ServeDNS implements plugin.Handler. func (f *Forward) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { ... // 请求域名未匹配from if !f.match(state) { // 转到下一个插件 return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, r) } // 最大并发检查 if f.maxConcurrent > 0 { count := atomic.AddInt64(&(f.concurrent), 1) defer atomic.AddInt64(&(f.concurrent), -1) // 超出并发限制,拒绝 if count > f.maxConcurrent { MaxConcurrentRejectCount.Add(1) return dns.RcodeRefused, f.ErrLimitExceeded } } ... // 获取打乱后proxy list := f.List() // 转到上游5s过期 deadline := time.Now().Add(defaultTimeout) ... // 未过期,循环尝试上游代理 for time.Now().Before(deadline) { ... // 获取一个上游 proxy := list[i] ... // 失败超出阈值 if proxy.Down(f.maxfails) { fails++ // 非proxy全部失败,尝试下一个proxy if fails < len(f.proxies) { continue } ... // 全部失败,随机选一个 proxy = r.List(f.proxies)[0] ... } ... // 多次尝试连接 for { // 发起dns请求 ret, err = proxy.Connect(ctx, state, opts) // tcp连接被远端关闭,重试 if err == ErrCachedClosed { // Remote side closed conn, can only happen with TCP. continue } // udp连接失败.强制用tcp重试 if ret != nil && ret.Truncated && !opts.forceTCP && opts.preferUDP { opts.forceTCP = true continue } break } ... // 执行dnstap plugin记录流量 if f.tapPlugin != nil { toDnstap(f, proxy.addr, state, opts, ret, start) } upstreamErr = err if err != nil { // 处理异常,达到限制失败阈值,触发健康检查 if f.maxfails != 0 { proxy.Healthcheck() } // 上游未全部失败,继续解析 if fails < len(f.proxies) { continue } break } // 请求与响应不匹配 if !state.Match(ret) { ... // 响应失败 w.WriteMsg(formerr) return 0, nil } // 响应结果 w.WriteMsg(ret) return 0, nil } ... return dns.RcodeServerFailure, ErrNoHealthy } // Connect selects an upstream, sends the request and waits for a response. func (p *Proxy) Connect(ctx context.Context, state request.Request, opts options) (*dns.Msg, error) { ... // 复用或创建连接 pc, cached, err := p.transport.Dial(proto) ... // udp buffer(512) pc.c.UDPSize = uint16(state.Size()) if pc.c.UDPSize < 512 { pc.c.UDPSize = 512 } // 设置2s转发超时 pc.c.SetWriteDeadline(time.Now().Add(maxTimeout)) ... // 请求转发 pc.c.WriteMsg(state.Req) ... // 设置2s读超时 pc.c.SetReadDeadline(time.Now().Add(readTimeout)) for { // 读响应 ret, err = pc.c.ReadMsg() ... // 只接受匹配请求的数据 if state.Req.Id == ret.Id { break } } ... // 连接放入复用池 p.transport.Yield(pc) ... return ret, nil } // Healthcheck kicks of a round of health checks for this proxy. func (p *Proxy) Healthcheck() { ... // 触发健康检查(已有探测不执行) p.probe.Do(func() error { return p.health.Check(p) }) } // Check is used as the up.Func in the up.Probe. func (h *dnsHc) Check(p *Proxy) error { // 发起dns探测请求 err := h.send(p.addr) if err != nil { ... // 增加失败次数(影响解析策略) atomic.AddUint32(&p.fails, 1) return err } // 重置失败次数 atomic.StoreUint32(&p.fails, 0) return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
注意
1.
forward plugin解析dns请求会根据zone匹配程度决定转到其它forward/plugin处理,或由当前forward处理2.
forward plugin定义的所有proxy会打乱为随机列表,依次遍历转发到上游dns解析3.
forward proxy转发到上游时,会向复用池申请连接或创建连接,使用后缓存到复用池,避免频繁创建连接
# 3.etcd
# 3.1.注册
etcd plugin实现Handler接口定义方法,注册setup()回调,用于服务启动阶段执行plugin初始化及chain组装。本质上,服务提供更多依赖etcd存储数据,插件更像是etcd client工具,用于查询dns记录。func init() { plugin.Register("etcd", setup) } func setup(c *caddy.Controller) error { // 解析etcd block定义 e, err := etcdParse(c) ... // 注册初始化回调 dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { e.Next = next return e }) return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14注意
etcd plugin注册较为简单,本质上是根据block定义初始化etcd client
# 3.2.分发
etcd plugin实现serveDNS()解析dns请求,根据请求类型查询etcd存储,基于查询结果响应请求。// ServeDNS implements the plugin.Handler interface. func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { ... // 域名匹配zone zone := plugin.Zones(e.Zones).Matches(state.Name()) // 未命中,转到其它plugin if zone == "" { return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r) } ... // 根据请求类型解析 switch state.QType() { // IPV4地址解析(dig A my-svc.my-namespace.svc.cluster.local) case dns.TypeA: records, truncated, err = plugin.A(ctx, e, zone, state, nil, opt) // IPV6地址解析(dig AAAA my-svc.my-namespace.svc.cluster.local) case dns.TypeAAAA: records, truncated, err = plugin.AAAA(ctx, e, zone, state, nil, opt) // TXT记录查询(dig TXT my-svc.my-namespace.svc.cluster.local) case dns.TypeTXT: records, truncated, err = plugin.TXT(ctx, e, zone, state, nil, opt) // 别名解析(dig CNAME my-svc.my-namespace.svc.cluster.local) case dns.TypeCNAME: records, err = plugin.CNAME(ctx, e, zone, state, opt) // 反向解析(dig -x 10.96.0.1) case dns.TypePTR: records, err = plugin.PTR(ctx, e, zone, state, opt) ... // 服务发现(dig SRV _http._tcp.my-svc.my-namespace.svc.cluster.local) case dns.TypeSRV: records, extra, err = plugin.SRV(ctx, e, zone, state, opt) // 区域授权记录(dig SOA cluster.local) case dns.TypeSOA: records, err = plugin.SOA(ctx, e, zone, state, opt) // 区域NameServer查询(dig NS cluster.local-->ns.dns.cluster.local-->dns svc入口IP) case dns.TypeNS: if state.Name() == zone { records, extra, err = plugin.NS(ctx, e, zone, state, opt) break } fallthrough // 默认地址解析 default: // Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN _, _, err = plugin.A(ctx, e, zone, state, nil, opt) } // 查询错误 if err != nil && e.IsNameError(err) { // 配置fallthrough,转到next插件 if e.Fall.Through(state.Name()) { return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r) } // Make err nil when returning here, so we don't log spam for NXDOMAIN. return plugin.BackendError(ctx, e, zone, dns.RcodeNameError, state, nil /* err */, opt) } ... // 未查到记录,nodata响应 if len(records) == 0 { return plugin.BackendError(ctx, e, zone, dns.RcodeSuccess, state, err, opt) } ... // 响应解析结果 w.WriteMsg(m) return dns.RcodeSuccess, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
注意
dns请求分发与kubernetes plugin类似,根据qtype区分解析类型调用不同方法
# 3.3.解析
dns解析过程就是根据qtype进行任务分发,执行各类解析方法进行服务查询及结果响应,主要针对集群内的请求,以解析域名至pod/svc/cname。// returns A records from Backend or an error. func A/AAAA/TXT(...) (records []dns.RR, truncated bool, err error) { // 获取匹配的服务列表 services, err := checkForApex(ctx, b, zone, state, opt) ... // 遍历服务 for _, serv := range services { // 根据IP解析类型(CNAME/TXT/A/AAAA) what, ip := serv.HostType() switch what { // CNAME解析 case dns.TypeCNAME: // 检查别名循环(x-->x) if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { continue } // 构造CNAME记录 newRecord := serv.NewCNAME(state.QName(), serv.Host) // 限制递归7层 if len(previousRecords) > 7 { // don't add it, and just continue continue } // 重复检查,避免CNAME重复添加 if dnsutil.DuplicateCNAME(newRecord, previousRecords) { continue } // CNAME指向的域名属于当前zone子域 if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) { state1 := state.NewWithQuestion(serv.Host, state.QType()) state1.Zone = zone // 递归调用A()/AAAA()/TXT()解析 nextRecords, tc, err := A/AAAA/TXT(ctx,b,zone,state1,append(previousRecords,newRecord),opt) ... // 记录解析结果 records = append(records, newRecord) records = append(records, nextRecords...) ... continue } // 目标域名不在当前zone/递归解析失败 target := newRecord.Target // 查询目标域名对应记录 m1, e1 := b.Lookup(ctx, state, target, state.QType()) ... // 记录解析结果 records = append(records, newRecord) records = append(records, m1.Answer...) continue // IPV4解析 case dns.TypeA: if _, ok := dup[serv.Host]; !ok { // 标记srv已处理 dup[serv.Host] = struct{}{} // 追加A记录 records = append(records, serv.NewA(state.QName(), ip)) } // IPV6 case dns.TypeAAAA: if _, ok := dup[serv.Host]; !ok { // 标记srv已处理 dup[serv.Host] = struct{}{} // 追加AAAA记录 records = append(records, serv.NewAAAA(state.QName(), ip)) } // TXT解析 case dns.TypeTXT: if _, ok := dup[serv.Text]; !ok { // 标记srv已处理 dup[serv.Text] = struct{}{} // 构造默认TXT记录 records = append(records, serv.NewTXT(state.QName())) } } } return records, truncated, nil } // CNAME returns CNAME records from the backend or an error. func CNAME(...) (records []dns.RR, err error) { // 获取服务列表 services, err := b.Services(ctx, state, true, opt) ... serv := services[0] ip := net.ParseIP(serv.Host) ... // 解析为指向域名 records = append(records, serv.NewCNAME(state.QName(), serv.Host)) return records, nil } // PTR returns the PTR records from the backend, only services that have a domain name as host are included. func PTR(...) (records []dns.RR, err error) { // 获取反向服务列表 services, err := b.Reverse(ctx, state, true, opt) ... // 遍历服务 for _, serv := range services { ... // 去重 if _, ok := dup[serv.Host]; !ok { // 标记已处理 dup[serv.Host] = struct{}{} // 生成反向解析记录 records = append(records, serv.NewPTR(state.QName(), serv.Host)) } } return records, 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(...) (records, extra []dns.RR, err error) { // 获取服务列表 services, err := b.Services(ctx, state, false, opt) ... // 生成srv记录 for _, serv := range services { ... // 计算srv相对权重(至少为1) weight := uint16(math.Floor(w1)) // serv类型 what, ip := serv.HostType() switch what { // CNAME类型 case dns.TypeCNAME: ... records = append(records, serv.NewSRV(state.QName(), weight)) ... // 当前域不是plugin zone子域 if !dns.IsSubDomain(zone, srv.Target) { // 执行Lookup解析 m1, e1 := b.Lookup(ctx, state, srv.Target, dns.TypeA) ... m1, e1 = b.Lookup(ctx, state, srv.Target, dns.TypeAAAA) ... // 结果追加到extra extra = append(extra, m1) break } // 否则递归调用 addr, _, e1 := A(ctx, b, zone, state1, nil, opt) ... extra = append(extra, addr...) // TODO(miek): AAAA as well here. // 地址类型 case dns.TypeA, dns.TypeAAAA: ... srv := serv.NewSRV(state.QName(), weight) ... // SRV的域名指向结果 // _http._tcp.mysvc.ns.svc.cluster.local --> mysvc.ns.svc.cluster.local records = append(records, srv) ... // 额外的IP记录 // mysvc.ns.svc.cluster.local --> addr extra = append(extra, newAddress(serv, srv.Target, ip, what)) } } return records, extra, nil } // returns NS records from the backend func NS(ctx context.Context, b ServiceBackend, zone string, state request.Request, opt Options) (records, extra []dns.RR, err error) { ... // 更新为默认入口域名 state.Req.Question[0].Name = dnsutil.Join("ns.dns.", zone) // 获取NS对应服务(boundIPs) services, err := b.Services(ctx, state, false, opt) ... // 恢复为请求域名 state.Req.Question[0].Name = old ... for _, serv := range services { what, ip := serv.HostType() switch what { ... // IPV4/IPV6地址 case dns.TypeA, dns.TypeAAAA: // 原始IP替换为域名(ns.dns.zone) serv.Host = msg.Domain(serv.Key) // 构造ns记录 ns := serv.NewNS(state.QName()) // 附加A/AAAA记录 extra = append(extra, newAddress(serv, ns.Ns, ip, what)) ... records = append(records, ns) } } return records, extra, nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
注意
etcd plugin解析dns请求与kubernetes plugin流程类似,不同的是Services()/Reverse()方法实现,本质上仅是数据获取来源不同
# 3.4.正向
不同的
dns查询类型会调用不同的plugin.XXX()方法,本质都是调用e.Services()获取etcd存储KV数据,用于dns解析及处理。// Services implements the ServiceBackend interface. func (e *Etcd) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) { services, err = e.Records(ctx, state, exact) ... return } // Records looks up records in etcd. If exact is true, it will lookup just this // name. This is used when find matches when completing SRV lookups for instance. func (e *Etcd) Records(ctx context.Context, state request.Request, exact bool) ([]msg.Service, error) { ... // 构造查询key(api.test.com-->/skydns/com/test/api) path, star := msg.PathWithWildcard(name, e.PathPrefix) // 查询etcd数据 r, err := e.get(ctx, path, !exact) ... // KV解析为service return e.loopNodes(r.Kvs, segments, star, state.QType()) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19注意
正向解析相对简单,根据请求域名转为
etcd path查询,支持通配*(any)查询多个sub value
# 3.5.反向
PTR反向解析会调用e.Reverse()方法,以解析IP-->domain关系,内部其实还是执行e.Services()方法查询etcd。// Reverse implements the ServiceBackend interface. func (e *Etcd) Reverse(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) { return e.Services(ctx, state, exact, opt) } // Services implements the ServiceBackend interface. func (e *Etcd) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) { // 以地址为域查询etcd KV services, err = e.Records(ctx, state, exact) ... return }1
2
3
4
5
6
7
8
9
10
11
12注意
反向解析会复用
Services()方法,区别在于state.Name()是PTR域名,Records()会将PTR域名转为etcd path查询