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	"io"
11	"os"
12)
13
14func query(ctx context.Context, filename, query string, bufSize int) (res []string, err error) {
15	file, err := os.OpenFile(filename, os.O_RDWR, 0)
16	if err != nil {
17		return
18	}
19	defer file.Close()
20
21	_, err = file.Seek(0, io.SeekStart)
22	if err != nil {
23		return
24	}
25	_, err = file.WriteString(query)
26	if err != nil {
27		return
28	}
29	_, err = file.Seek(0, io.SeekStart)
30	if err != nil {
31		return
32	}
33	buf := make([]byte, bufSize)
34	for {
35		n, _ := file.Read(buf)
36		if n <= 0 {
37			break
38		}
39		res = append(res, string(buf[:n]))
40	}
41	return
42}
43
44func queryCS(ctx context.Context, net, host, service string) (res []string, err error) {
45	switch net {
46	case "tcp4", "tcp6":
47		net = "tcp"
48	case "udp4", "udp6":
49		net = "udp"
50	}
51	if host == "" {
52		host = "*"
53	}
54	return query(ctx, netdir+"/cs", net+"!"+host+"!"+service, 128)
55}
56
57func queryCS1(ctx context.Context, net string, ip IP, port int) (clone, dest string, err error) {
58	ips := "*"
59	if len(ip) != 0 && !ip.IsUnspecified() {
60		ips = ip.String()
61	}
62	lines, err := queryCS(ctx, net, ips, itoa(port))
63	if err != nil {
64		return
65	}
66	f := getFields(lines[0])
67	if len(f) < 2 {
68		return "", "", errors.New("bad response from ndb/cs")
69	}
70	clone, dest = f[0], f[1]
71	return
72}
73
74func queryDNS(ctx context.Context, addr string, typ string) (res []string, err error) {
75	return query(ctx, netdir+"/dns", addr+" "+typ, 1024)
76}
77
78// toLower returns a lower-case version of in. Restricting us to
79// ASCII is sufficient to handle the IP protocol names and allow
80// us to not depend on the strings and unicode packages.
81func toLower(in string) string {
82	for _, c := range in {
83		if 'A' <= c && c <= 'Z' {
84			// Has upper case; need to fix.
85			out := []byte(in)
86			for i := 0; i < len(in); i++ {
87				c := in[i]
88				if 'A' <= c && c <= 'Z' {
89					c += 'a' - 'A'
90				}
91				out[i] = c
92			}
93			return string(out)
94		}
95	}
96	return in
97}
98
99// lookupProtocol looks up IP protocol name and returns
100// the corresponding protocol number.
101func lookupProtocol(ctx context.Context, name string) (proto int, err error) {
102	lines, err := query(ctx, netdir+"/cs", "!protocol="+toLower(name), 128)
103	if err != nil {
104		return 0, err
105	}
106	if len(lines) == 0 {
107		return 0, UnknownNetworkError(name)
108	}
109	f := getFields(lines[0])
110	if len(f) < 2 {
111		return 0, UnknownNetworkError(name)
112	}
113	s := f[1]
114	if n, _, ok := dtoi(s[byteIndex(s, '=')+1:]); ok {
115		return n, nil
116	}
117	return 0, UnknownNetworkError(name)
118}
119
120func (*Resolver) lookupHost(ctx context.Context, host string) (addrs []string, err error) {
121	// Use netdir/cs instead of netdir/dns because cs knows about
122	// host names in local network (e.g. from /lib/ndb/local)
123	lines, err := queryCS(ctx, "net", host, "1")
124	if err != nil {
125		if stringsHasSuffix(err.Error(), "dns failure") {
126			err = errNoSuchHost
127		}
128		return
129	}
130loop:
131	for _, line := range lines {
132		f := getFields(line)
133		if len(f) < 2 {
134			continue
135		}
136		addr := f[1]
137		if i := byteIndex(addr, '!'); i >= 0 {
138			addr = addr[:i] // remove port
139		}
140		if ParseIP(addr) == nil {
141			continue
142		}
143		// only return unique addresses
144		for _, a := range addrs {
145			if a == addr {
146				continue loop
147			}
148		}
149		addrs = append(addrs, addr)
150	}
151	return
152}
153
154func (r *Resolver) lookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) {
155	lits, err := r.lookupHost(ctx, host)
156	if err != nil {
157		return
158	}
159	for _, lit := range lits {
160		host, zone := splitHostZone(lit)
161		if ip := ParseIP(host); ip != nil {
162			addr := IPAddr{IP: ip, Zone: zone}
163			addrs = append(addrs, addr)
164		}
165	}
166	return
167}
168
169func (*Resolver) lookupPort(ctx context.Context, network, service string) (port int, err error) {
170	switch network {
171	case "tcp4", "tcp6":
172		network = "tcp"
173	case "udp4", "udp6":
174		network = "udp"
175	}
176	lines, err := queryCS(ctx, network, "127.0.0.1", toLower(service))
177	if err != nil {
178		return
179	}
180	unknownPortError := &AddrError{Err: "unknown port", Addr: network + "/" + service}
181	if len(lines) == 0 {
182		return 0, unknownPortError
183	}
184	f := getFields(lines[0])
185	if len(f) < 2 {
186		return 0, unknownPortError
187	}
188	s := f[1]
189	if i := byteIndex(s, '!'); i >= 0 {
190		s = s[i+1:] // remove address
191	}
192	if n, _, ok := dtoi(s); ok {
193		return n, nil
194	}
195	return 0, unknownPortError
196}
197
198func (*Resolver) lookupCNAME(ctx context.Context, name string) (cname string, err error) {
199	lines, err := queryDNS(ctx, name, "cname")
200	if err != nil {
201		if stringsHasSuffix(err.Error(), "dns failure") || stringsHasSuffix(err.Error(), "resource does not exist; negrcode 0") {
202			cname = name + "."
203			err = nil
204		}
205		return
206	}
207	if len(lines) > 0 {
208		if f := getFields(lines[0]); len(f) >= 3 {
209			return f[2] + ".", nil
210		}
211	}
212	return "", errors.New("bad response from ndb/dns")
213}
214
215func (*Resolver) lookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) {
216	var target string
217	if service == "" && proto == "" {
218		target = name
219	} else {
220		target = "_" + service + "._" + proto + "." + name
221	}
222	lines, err := queryDNS(ctx, target, "srv")
223	if err != nil {
224		return
225	}
226	for _, line := range lines {
227		f := getFields(line)
228		if len(f) < 6 {
229			continue
230		}
231		port, _, portOk := dtoi(f[4])
232		priority, _, priorityOk := dtoi(f[3])
233		weight, _, weightOk := dtoi(f[2])
234		if !(portOk && priorityOk && weightOk) {
235			continue
236		}
237		addrs = append(addrs, &SRV{absDomainName([]byte(f[5])), uint16(port), uint16(priority), uint16(weight)})
238		cname = absDomainName([]byte(f[0]))
239	}
240	byPriorityWeight(addrs).sort()
241	return
242}
243
244func (*Resolver) lookupMX(ctx context.Context, name string) (mx []*MX, err error) {
245	lines, err := queryDNS(ctx, name, "mx")
246	if err != nil {
247		return
248	}
249	for _, line := range lines {
250		f := getFields(line)
251		if len(f) < 4 {
252			continue
253		}
254		if pref, _, ok := dtoi(f[2]); ok {
255			mx = append(mx, &MX{absDomainName([]byte(f[3])), uint16(pref)})
256		}
257	}
258	byPref(mx).sort()
259	return
260}
261
262func (*Resolver) lookupNS(ctx context.Context, name string) (ns []*NS, err error) {
263	lines, err := queryDNS(ctx, name, "ns")
264	if err != nil {
265		return
266	}
267	for _, line := range lines {
268		f := getFields(line)
269		if len(f) < 3 {
270			continue
271		}
272		ns = append(ns, &NS{absDomainName([]byte(f[2]))})
273	}
274	return
275}
276
277func (*Resolver) lookupTXT(ctx context.Context, name string) (txt []string, err error) {
278	lines, err := queryDNS(ctx, name, "txt")
279	if err != nil {
280		return
281	}
282	for _, line := range lines {
283		if i := byteIndex(line, '\t'); i >= 0 {
284			txt = append(txt, absDomainName([]byte(line[i+1:])))
285		}
286	}
287	return
288}
289
290func (*Resolver) lookupAddr(ctx context.Context, addr string) (name []string, err error) {
291	arpa, err := reverseaddr(addr)
292	if err != nil {
293		return
294	}
295	lines, err := queryDNS(ctx, arpa, "ptr")
296	if err != nil {
297		return
298	}
299	for _, line := range lines {
300		f := getFields(line)
301		if len(f) < 3 {
302			continue
303		}
304		name = append(name, absDomainName([]byte(f[2])))
305	}
306	return
307}
308