• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19// Package dns implements a dns resolver to be installed as the default resolver
20// in grpc.
21package dns
22
23import (
24	"encoding/json"
25	"errors"
26	"fmt"
27	"net"
28	"os"
29	"strconv"
30	"strings"
31	"sync"
32	"time"
33
34	"golang.org/x/net/context"
35	"google.golang.org/grpc/grpclog"
36	"google.golang.org/grpc/internal/grpcrand"
37	"google.golang.org/grpc/resolver"
38)
39
40func init() {
41	resolver.Register(NewBuilder())
42}
43
44const (
45	defaultPort = "443"
46	defaultFreq = time.Minute * 30
47	golang      = "GO"
48	// In DNS, service config is encoded in a TXT record via the mechanism
49	// described in RFC-1464 using the attribute name grpc_config.
50	txtAttribute = "grpc_config="
51)
52
53var (
54	errMissingAddr = errors.New("dns resolver: missing address")
55
56	// Addresses ending with a colon that is supposed to be the separator
57	// between host and port is not allowed.  E.g. "::" is a valid address as
58	// it is an IPv6 address (host only) and "[::]:" is invalid as it ends with
59	// a colon as the host and port separator
60	errEndsWithColon = errors.New("dns resolver: missing port after port-separator colon")
61)
62
63// NewBuilder creates a dnsBuilder which is used to factory DNS resolvers.
64func NewBuilder() resolver.Builder {
65	return &dnsBuilder{freq: defaultFreq}
66}
67
68type dnsBuilder struct {
69	// frequency of polling the DNS server.
70	freq time.Duration
71}
72
73// Build creates and starts a DNS resolver that watches the name resolution of the target.
74func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) {
75	if target.Authority != "" {
76		return nil, fmt.Errorf("Default DNS resolver does not support custom DNS server")
77	}
78	host, port, err := parseTarget(target.Endpoint)
79	if err != nil {
80		return nil, err
81	}
82
83	// IP address.
84	if net.ParseIP(host) != nil {
85		host, _ = formatIP(host)
86		addr := []resolver.Address{{Addr: host + ":" + port}}
87		i := &ipResolver{
88			cc: cc,
89			ip: addr,
90			rn: make(chan struct{}, 1),
91			q:  make(chan struct{}),
92		}
93		cc.NewAddress(addr)
94		go i.watcher()
95		return i, nil
96	}
97
98	// DNS address (non-IP).
99	ctx, cancel := context.WithCancel(context.Background())
100	d := &dnsResolver{
101		freq:                 b.freq,
102		host:                 host,
103		port:                 port,
104		ctx:                  ctx,
105		cancel:               cancel,
106		cc:                   cc,
107		t:                    time.NewTimer(0),
108		rn:                   make(chan struct{}, 1),
109		disableServiceConfig: opts.DisableServiceConfig,
110	}
111
112	d.wg.Add(1)
113	go d.watcher()
114	return d, nil
115}
116
117// Scheme returns the naming scheme of this resolver builder, which is "dns".
118func (b *dnsBuilder) Scheme() string {
119	return "dns"
120}
121
122// ipResolver watches for the name resolution update for an IP address.
123type ipResolver struct {
124	cc resolver.ClientConn
125	ip []resolver.Address
126	// rn channel is used by ResolveNow() to force an immediate resolution of the target.
127	rn chan struct{}
128	q  chan struct{}
129}
130
131// ResolveNow resend the address it stores, no resolution is needed.
132func (i *ipResolver) ResolveNow(opt resolver.ResolveNowOption) {
133	select {
134	case i.rn <- struct{}{}:
135	default:
136	}
137}
138
139// Close closes the ipResolver.
140func (i *ipResolver) Close() {
141	close(i.q)
142}
143
144func (i *ipResolver) watcher() {
145	for {
146		select {
147		case <-i.rn:
148			i.cc.NewAddress(i.ip)
149		case <-i.q:
150			return
151		}
152	}
153}
154
155// dnsResolver watches for the name resolution update for a non-IP target.
156type dnsResolver struct {
157	freq   time.Duration
158	host   string
159	port   string
160	ctx    context.Context
161	cancel context.CancelFunc
162	cc     resolver.ClientConn
163	// rn channel is used by ResolveNow() to force an immediate resolution of the target.
164	rn chan struct{}
165	t  *time.Timer
166	// wg is used to enforce Close() to return after the watcher() goroutine has finished.
167	// Otherwise, data race will be possible. [Race Example] in dns_resolver_test we
168	// replace the real lookup functions with mocked ones to facilitate testing.
169	// If Close() doesn't wait for watcher() goroutine finishes, race detector sometimes
170	// will warns lookup (READ the lookup function pointers) inside watcher() goroutine
171	// has data race with replaceNetFunc (WRITE the lookup function pointers).
172	wg                   sync.WaitGroup
173	disableServiceConfig bool
174}
175
176// ResolveNow invoke an immediate resolution of the target that this dnsResolver watches.
177func (d *dnsResolver) ResolveNow(opt resolver.ResolveNowOption) {
178	select {
179	case d.rn <- struct{}{}:
180	default:
181	}
182}
183
184// Close closes the dnsResolver.
185func (d *dnsResolver) Close() {
186	d.cancel()
187	d.wg.Wait()
188	d.t.Stop()
189}
190
191func (d *dnsResolver) watcher() {
192	defer d.wg.Done()
193	for {
194		select {
195		case <-d.ctx.Done():
196			return
197		case <-d.t.C:
198		case <-d.rn:
199		}
200		result, sc := d.lookup()
201		// Next lookup should happen after an interval defined by d.freq.
202		d.t.Reset(d.freq)
203		d.cc.NewServiceConfig(sc)
204		d.cc.NewAddress(result)
205	}
206}
207
208func (d *dnsResolver) lookupSRV() []resolver.Address {
209	var newAddrs []resolver.Address
210	_, srvs, err := lookupSRV(d.ctx, "grpclb", "tcp", d.host)
211	if err != nil {
212		grpclog.Infof("grpc: failed dns SRV record lookup due to %v.\n", err)
213		return nil
214	}
215	for _, s := range srvs {
216		lbAddrs, err := lookupHost(d.ctx, s.Target)
217		if err != nil {
218			grpclog.Infof("grpc: failed load balancer address dns lookup due to %v.\n", err)
219			continue
220		}
221		for _, a := range lbAddrs {
222			a, ok := formatIP(a)
223			if !ok {
224				grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
225				continue
226			}
227			addr := a + ":" + strconv.Itoa(int(s.Port))
228			newAddrs = append(newAddrs, resolver.Address{Addr: addr, Type: resolver.GRPCLB, ServerName: s.Target})
229		}
230	}
231	return newAddrs
232}
233
234func (d *dnsResolver) lookupTXT() string {
235	ss, err := lookupTXT(d.ctx, d.host)
236	if err != nil {
237		grpclog.Infof("grpc: failed dns TXT record lookup due to %v.\n", err)
238		return ""
239	}
240	var res string
241	for _, s := range ss {
242		res += s
243	}
244
245	// TXT record must have "grpc_config=" attribute in order to be used as service config.
246	if !strings.HasPrefix(res, txtAttribute) {
247		grpclog.Warningf("grpc: TXT record %v missing %v attribute", res, txtAttribute)
248		return ""
249	}
250	return strings.TrimPrefix(res, txtAttribute)
251}
252
253func (d *dnsResolver) lookupHost() []resolver.Address {
254	var newAddrs []resolver.Address
255	addrs, err := lookupHost(d.ctx, d.host)
256	if err != nil {
257		grpclog.Warningf("grpc: failed dns A record lookup due to %v.\n", err)
258		return nil
259	}
260	for _, a := range addrs {
261		a, ok := formatIP(a)
262		if !ok {
263			grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
264			continue
265		}
266		addr := a + ":" + d.port
267		newAddrs = append(newAddrs, resolver.Address{Addr: addr})
268	}
269	return newAddrs
270}
271
272func (d *dnsResolver) lookup() ([]resolver.Address, string) {
273	newAddrs := d.lookupSRV()
274	// Support fallback to non-balancer address.
275	newAddrs = append(newAddrs, d.lookupHost()...)
276	if d.disableServiceConfig {
277		return newAddrs, ""
278	}
279	sc := d.lookupTXT()
280	return newAddrs, canaryingSC(sc)
281}
282
283// formatIP returns ok = false if addr is not a valid textual representation of an IP address.
284// If addr is an IPv4 address, return the addr and ok = true.
285// If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true.
286func formatIP(addr string) (addrIP string, ok bool) {
287	ip := net.ParseIP(addr)
288	if ip == nil {
289		return "", false
290	}
291	if ip.To4() != nil {
292		return addr, true
293	}
294	return "[" + addr + "]", true
295}
296
297// parseTarget takes the user input target string, returns formatted host and port info.
298// If target doesn't specify a port, set the port to be the defaultPort.
299// If target is in IPv6 format and host-name is enclosed in sqarue brackets, brackets
300// are strippd when setting the host.
301// examples:
302// target: "www.google.com" returns host: "www.google.com", port: "443"
303// target: "ipv4-host:80" returns host: "ipv4-host", port: "80"
304// target: "[ipv6-host]" returns host: "ipv6-host", port: "443"
305// target: ":80" returns host: "localhost", port: "80"
306func parseTarget(target string) (host, port string, err error) {
307	if target == "" {
308		return "", "", errMissingAddr
309	}
310	if ip := net.ParseIP(target); ip != nil {
311		// target is an IPv4 or IPv6(without brackets) address
312		return target, defaultPort, nil
313	}
314	if host, port, err = net.SplitHostPort(target); err == nil {
315		if port == "" {
316			// If the port field is empty (target ends with colon), e.g. "[::1]:", this is an error.
317			return "", "", errEndsWithColon
318		}
319		// target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port
320		if host == "" {
321			// Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed.
322			host = "localhost"
323		}
324		return host, port, nil
325	}
326	if host, port, err = net.SplitHostPort(target + ":" + defaultPort); err == nil {
327		// target doesn't have port
328		return host, port, nil
329	}
330	return "", "", fmt.Errorf("invalid target address %v, error info: %v", target, err)
331}
332
333type rawChoice struct {
334	ClientLanguage *[]string        `json:"clientLanguage,omitempty"`
335	Percentage     *int             `json:"percentage,omitempty"`
336	ClientHostName *[]string        `json:"clientHostName,omitempty"`
337	ServiceConfig  *json.RawMessage `json:"serviceConfig,omitempty"`
338}
339
340func containsString(a *[]string, b string) bool {
341	if a == nil {
342		return true
343	}
344	for _, c := range *a {
345		if c == b {
346			return true
347		}
348	}
349	return false
350}
351
352func chosenByPercentage(a *int) bool {
353	if a == nil {
354		return true
355	}
356	return grpcrand.Intn(100)+1 <= *a
357}
358
359func canaryingSC(js string) string {
360	if js == "" {
361		return ""
362	}
363	var rcs []rawChoice
364	err := json.Unmarshal([]byte(js), &rcs)
365	if err != nil {
366		grpclog.Warningf("grpc: failed to parse service config json string due to %v.\n", err)
367		return ""
368	}
369	cliHostname, err := os.Hostname()
370	if err != nil {
371		grpclog.Warningf("grpc: failed to get client hostname due to %v.\n", err)
372		return ""
373	}
374	var sc string
375	for _, c := range rcs {
376		if !containsString(c.ClientLanguage, golang) ||
377			!chosenByPercentage(c.Percentage) ||
378			!containsString(c.ClientHostName, cliHostname) ||
379			c.ServiceConfig == nil {
380			continue
381		}
382		sc = string(*c.ServiceConfig)
383		break
384	}
385	return sc
386}
387