1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package net
6
7import (
8	"context"
9	"errors"
10	"internal/bytealg"
11	"internal/itoa"
12	"internal/stringslite"
13	"io"
14	"os"
15)
16
17// cgoAvailable set to true to indicate that the cgo resolver
18// is available on Plan 9. Note that on Plan 9 the cgo resolver
19// does not actually use cgo.
20const cgoAvailable = true
21
22func query(ctx context.Context, filename, query string, bufSize int) (addrs []string, err error) {
23	queryAddrs := func() (addrs []string, err error) {
24		file, err := os.OpenFile(filename, os.O_RDWR, 0)
25		if err != nil {
26			return nil, err
27		}
28		defer file.Close()
29
30		_, err = file.Seek(0, io.SeekStart)
31		if err != nil {
32			return nil, err
33		}
34		_, err = file.WriteString(query)
35		if err != nil {
36			return nil, err
37		}
38		_, err = file.Seek(0, io.SeekStart)
39		if err != nil {
40			return nil, err
41		}
42		buf := make([]byte, bufSize)
43		for {
44			n, _ := file.Read(buf)
45			if n <= 0 {
46				break
47			}
48			addrs = append(addrs, string(buf[:n]))
49		}
50		return addrs, nil
51	}
52
53	type ret struct {
54		addrs []string
55		err   error
56	}
57
58	ch := make(chan ret, 1)
59	go func() {
60		addrs, err := queryAddrs()
61		ch <- ret{addrs: addrs, err: err}
62	}()
63
64	select {
65	case r := <-ch:
66		return r.addrs, r.err
67	case <-ctx.Done():
68		return nil, &DNSError{
69			Name:      query,
70			Err:       ctx.Err().Error(),
71			IsTimeout: ctx.Err() == context.DeadlineExceeded,
72		}
73	}
74}
75
76func queryCS(ctx context.Context, net, host, service string) (res []string, err error) {
77	switch net {
78	case "tcp4", "tcp6":
79		net = "tcp"
80	case "udp4", "udp6":
81		net = "udp"
82	}
83	if host == "" {
84		host = "*"
85	}
86	return query(ctx, netdir+"/cs", net+"!"+host+"!"+service, 128)
87}
88
89func queryCS1(ctx context.Context, net string, ip IP, port int) (clone, dest string, err error) {
90	ips := "*"
91	if len(ip) != 0 && !ip.IsUnspecified() {
92		ips = ip.String()
93	}
94	lines, err := queryCS(ctx, net, ips, itoa.Itoa(port))
95	if err != nil {
96		return
97	}
98	f := getFields(lines[0])
99	if len(f) < 2 {
100		return "", "", errors.New("bad response from ndb/cs")
101	}
102	clone, dest = f[0], f[1]
103	return
104}
105
106func queryDNS(ctx context.Context, addr string, typ string) (res []string, err error) {
107	return query(ctx, netdir+"/dns", addr+" "+typ, 1024)
108}
109
110func handlePlan9DNSError(err error, name string) error {
111	if stringslite.HasSuffix(err.Error(), "dns: name does not exist") ||
112		stringslite.HasSuffix(err.Error(), "dns: resource does not exist; negrcode 0") ||
113		stringslite.HasSuffix(err.Error(), "dns: resource does not exist; negrcode") ||
114		stringslite.HasSuffix(err.Error(), "dns failure") {
115		err = errNoSuchHost
116	}
117	return newDNSError(err, name, "")
118}
119
120// toLower returns a lower-case version of in. Restricting us to
121// ASCII is sufficient to handle the IP protocol names and allow
122// us to not depend on the strings and unicode packages.
123func toLower(in string) string {
124	for _, c := range in {
125		if 'A' <= c && c <= 'Z' {
126			// Has upper case; need to fix.
127			out := []byte(in)
128			for i := 0; i < len(in); i++ {
129				c := in[i]
130				if 'A' <= c && c <= 'Z' {
131					c += 'a' - 'A'
132				}
133				out[i] = c
134			}
135			return string(out)
136		}
137	}
138	return in
139}
140
141// lookupProtocol looks up IP protocol name and returns
142// the corresponding protocol number.
143func lookupProtocol(ctx context.Context, name string) (proto int, err error) {
144	lines, err := query(ctx, netdir+"/cs", "!protocol="+toLower(name), 128)
145	if err != nil {
146		return 0, err
147	}
148	if len(lines) == 0 {
149		return 0, UnknownNetworkError(name)
150	}
151	f := getFields(lines[0])
152	if len(f) < 2 {
153		return 0, UnknownNetworkError(name)
154	}
155	s := f[1]
156	if n, _, ok := dtoi(s[bytealg.IndexByteString(s, '=')+1:]); ok {
157		return n, nil
158	}
159	return 0, UnknownNetworkError(name)
160}
161
162func (*Resolver) lookupHost(ctx context.Context, host string) (addrs []string, err error) {
163	// Use netdir/cs instead of netdir/dns because cs knows about
164	// host names in local network (e.g. from /lib/ndb/local)
165	lines, err := queryCS(ctx, "net", host, "1")
166	if err != nil {
167		return nil, handlePlan9DNSError(err, host)
168	}
169loop:
170	for _, line := range lines {
171		f := getFields(line)
172		if len(f) < 2 {
173			continue
174		}
175		addr := f[1]
176		if i := bytealg.IndexByteString(addr, '!'); i >= 0 {
177			addr = addr[:i] // remove port
178		}
179		if ParseIP(addr) == nil {
180			continue
181		}
182		// only return unique addresses
183		for _, a := range addrs {
184			if a == addr {
185				continue loop
186			}
187		}
188		addrs = append(addrs, addr)
189	}
190	return
191}
192
193func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []IPAddr, err error) {
194	if order, conf := systemConf().hostLookupOrder(r, host); order != hostLookupCgo {
195		return r.goLookupIP(ctx, network, host, order, conf)
196	}
197
198	lits, err := r.lookupHost(ctx, host)
199	if err != nil {
200		return
201	}
202	for _, lit := range lits {
203		host, zone := splitHostZone(lit)
204		if ip := ParseIP(host); ip != nil {
205			addr := IPAddr{IP: ip, Zone: zone}
206			addrs = append(addrs, addr)
207		}
208	}
209	return
210}
211
212func (r *Resolver) lookupPort(ctx context.Context, network, service string) (port int, err error) {
213	switch network {
214	case "ip": // no hints
215		if p, err := r.lookupPortWithNetwork(ctx, "tcp", "ip", service); err == nil {
216			return p, nil
217		}
218		return r.lookupPortWithNetwork(ctx, "udp", "ip", service)
219	case "tcp", "tcp4", "tcp6":
220		return r.lookupPortWithNetwork(ctx, "tcp", "tcp", service)
221	case "udp", "udp4", "udp6":
222		return r.lookupPortWithNetwork(ctx, "udp", "udp", service)
223	default:
224		return 0, &DNSError{Err: "unknown network", Name: network + "/" + service}
225	}
226}
227
228func (*Resolver) lookupPortWithNetwork(ctx context.Context, network, errNetwork, service string) (port int, err error) {
229	lines, err := queryCS(ctx, network, "127.0.0.1", toLower(service))
230	if err != nil {
231		if stringslite.HasSuffix(err.Error(), "can't translate service") {
232			return 0, &DNSError{Err: "unknown port", Name: errNetwork + "/" + service, IsNotFound: true}
233		}
234		return
235	}
236	if len(lines) == 0 {
237		return 0, &DNSError{Err: "unknown port", Name: errNetwork + "/" + service, IsNotFound: true}
238	}
239	f := getFields(lines[0])
240	if len(f) < 2 {
241		return 0, &DNSError{Err: "unknown port", Name: errNetwork + "/" + service, IsNotFound: true}
242	}
243	s := f[1]
244	if i := bytealg.IndexByteString(s, '!'); i >= 0 {
245		s = s[i+1:] // remove address
246	}
247	if n, _, ok := dtoi(s); ok {
248		return n, nil
249	}
250	return 0, &DNSError{Err: "unknown port", Name: errNetwork + "/" + service, IsNotFound: true}
251}
252
253func (r *Resolver) lookupCNAME(ctx context.Context, name string) (cname string, err error) {
254	if order, conf := systemConf().hostLookupOrder(r, name); order != hostLookupCgo {
255		return r.goLookupCNAME(ctx, name, order, conf)
256	}
257
258	lines, err := queryDNS(ctx, name, "cname")
259	if err != nil {
260		if stringslite.HasSuffix(err.Error(), "dns failure") ||
261			stringslite.HasSuffix(err.Error(), "resource does not exist; negrcode 0") ||
262			stringslite.HasSuffix(err.Error(), "resource does not exist; negrcode") {
263			return absDomainName(name), nil
264		}
265		return "", handlePlan9DNSError(err, cname)
266	}
267	if len(lines) > 0 {
268		if f := getFields(lines[0]); len(f) >= 3 {
269			return f[2] + ".", nil
270		}
271	}
272	return "", errors.New("bad response from ndb/dns")
273}
274
275func (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) {
276	if systemConf().mustUseGoResolver(r) {
277		return r.goLookupSRV(ctx, service, proto, name)
278	}
279	var target string
280	if service == "" && proto == "" {
281		target = name
282	} else {
283		target = "_" + service + "._" + proto + "." + name
284	}
285	lines, err := queryDNS(ctx, target, "srv")
286	if err != nil {
287		return "", nil, handlePlan9DNSError(err, name)
288	}
289	for _, line := range lines {
290		f := getFields(line)
291		if len(f) < 6 {
292			continue
293		}
294		port, _, portOk := dtoi(f[4])
295		priority, _, priorityOk := dtoi(f[3])
296		weight, _, weightOk := dtoi(f[2])
297		if !(portOk && priorityOk && weightOk) {
298			continue
299		}
300		addrs = append(addrs, &SRV{absDomainName(f[5]), uint16(port), uint16(priority), uint16(weight)})
301		cname = absDomainName(f[0])
302	}
303	byPriorityWeight(addrs).sort()
304	return
305}
306
307func (r *Resolver) lookupMX(ctx context.Context, name string) (mx []*MX, err error) {
308	if systemConf().mustUseGoResolver(r) {
309		return r.goLookupMX(ctx, name)
310	}
311	lines, err := queryDNS(ctx, name, "mx")
312	if err != nil {
313		return nil, handlePlan9DNSError(err, name)
314	}
315	for _, line := range lines {
316		f := getFields(line)
317		if len(f) < 4 {
318			continue
319		}
320		if pref, _, ok := dtoi(f[2]); ok {
321			mx = append(mx, &MX{absDomainName(f[3]), uint16(pref)})
322		}
323	}
324	byPref(mx).sort()
325	return
326}
327
328func (r *Resolver) lookupNS(ctx context.Context, name string) (ns []*NS, err error) {
329	if systemConf().mustUseGoResolver(r) {
330		return r.goLookupNS(ctx, name)
331	}
332	lines, err := queryDNS(ctx, name, "ns")
333	if err != nil {
334		return nil, handlePlan9DNSError(err, name)
335	}
336	for _, line := range lines {
337		f := getFields(line)
338		if len(f) < 3 {
339			continue
340		}
341		ns = append(ns, &NS{absDomainName(f[2])})
342	}
343	return
344}
345
346func (r *Resolver) lookupTXT(ctx context.Context, name string) (txt []string, err error) {
347	if systemConf().mustUseGoResolver(r) {
348		return r.goLookupTXT(ctx, name)
349	}
350	lines, err := queryDNS(ctx, name, "txt")
351	if err != nil {
352		return nil, handlePlan9DNSError(err, name)
353	}
354	for _, line := range lines {
355		if i := bytealg.IndexByteString(line, '\t'); i >= 0 {
356			txt = append(txt, line[i+1:])
357		}
358	}
359	return
360}
361
362func (r *Resolver) lookupAddr(ctx context.Context, addr string) (name []string, err error) {
363	if order, conf := systemConf().addrLookupOrder(r, addr); order != hostLookupCgo {
364		return r.goLookupPTR(ctx, addr, order, conf)
365	}
366	arpa, err := reverseaddr(addr)
367	if err != nil {
368		return
369	}
370	lines, err := queryDNS(ctx, arpa, "ptr")
371	if err != nil {
372		return nil, handlePlan9DNSError(err, addr)
373	}
374	for _, line := range lines {
375		f := getFields(line)
376		if len(f) < 3 {
377			continue
378		}
379		name = append(name, absDomainName(f[2]))
380	}
381	return
382}
383
384// concurrentThreadsLimit returns the number of threads we permit to
385// run concurrently doing DNS lookups.
386func concurrentThreadsLimit() int {
387	return 500
388}
389