| // Copyright 2011 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | package net | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"errors" | 
 | 	"io" | 
 | 	"os" | 
 | ) | 
 |  | 
 | func query(ctx context.Context, filename, query string, bufSize int) (res []string, err error) { | 
 | 	file, err := os.OpenFile(filename, os.O_RDWR, 0) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	defer file.Close() | 
 |  | 
 | 	_, err = file.Seek(0, io.SeekStart) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	_, err = file.WriteString(query) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	_, err = file.Seek(0, io.SeekStart) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	buf := make([]byte, bufSize) | 
 | 	for { | 
 | 		n, _ := file.Read(buf) | 
 | 		if n <= 0 { | 
 | 			break | 
 | 		} | 
 | 		res = append(res, string(buf[:n])) | 
 | 	} | 
 | 	return | 
 | } | 
 |  | 
 | func queryCS(ctx context.Context, net, host, service string) (res []string, err error) { | 
 | 	switch net { | 
 | 	case "tcp4", "tcp6": | 
 | 		net = "tcp" | 
 | 	case "udp4", "udp6": | 
 | 		net = "udp" | 
 | 	} | 
 | 	if host == "" { | 
 | 		host = "*" | 
 | 	} | 
 | 	return query(ctx, netdir+"/cs", net+"!"+host+"!"+service, 128) | 
 | } | 
 |  | 
 | func queryCS1(ctx context.Context, net string, ip IP, port int) (clone, dest string, err error) { | 
 | 	ips := "*" | 
 | 	if len(ip) != 0 && !ip.IsUnspecified() { | 
 | 		ips = ip.String() | 
 | 	} | 
 | 	lines, err := queryCS(ctx, net, ips, itoa(port)) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	f := getFields(lines[0]) | 
 | 	if len(f) < 2 { | 
 | 		return "", "", errors.New("bad response from ndb/cs") | 
 | 	} | 
 | 	clone, dest = f[0], f[1] | 
 | 	return | 
 | } | 
 |  | 
 | func queryDNS(ctx context.Context, addr string, typ string) (res []string, err error) { | 
 | 	return query(ctx, netdir+"/dns", addr+" "+typ, 1024) | 
 | } | 
 |  | 
 | // toLower returns a lower-case version of in. Restricting us to | 
 | // ASCII is sufficient to handle the IP protocol names and allow | 
 | // us to not depend on the strings and unicode packages. | 
 | func toLower(in string) string { | 
 | 	for _, c := range in { | 
 | 		if 'A' <= c && c <= 'Z' { | 
 | 			// Has upper case; need to fix. | 
 | 			out := []byte(in) | 
 | 			for i := 0; i < len(in); i++ { | 
 | 				c := in[i] | 
 | 				if 'A' <= c && c <= 'Z' { | 
 | 					c += 'a' - 'A' | 
 | 				} | 
 | 				out[i] = c | 
 | 			} | 
 | 			return string(out) | 
 | 		} | 
 | 	} | 
 | 	return in | 
 | } | 
 |  | 
 | // lookupProtocol looks up IP protocol name and returns | 
 | // the corresponding protocol number. | 
 | func lookupProtocol(ctx context.Context, name string) (proto int, err error) { | 
 | 	lines, err := query(ctx, netdir+"/cs", "!protocol="+toLower(name), 128) | 
 | 	if err != nil { | 
 | 		return 0, err | 
 | 	} | 
 | 	if len(lines) == 0 { | 
 | 		return 0, UnknownNetworkError(name) | 
 | 	} | 
 | 	f := getFields(lines[0]) | 
 | 	if len(f) < 2 { | 
 | 		return 0, UnknownNetworkError(name) | 
 | 	} | 
 | 	s := f[1] | 
 | 	if n, _, ok := dtoi(s[byteIndex(s, '=')+1:]); ok { | 
 | 		return n, nil | 
 | 	} | 
 | 	return 0, UnknownNetworkError(name) | 
 | } | 
 |  | 
 | func (*Resolver) lookupHost(ctx context.Context, host string) (addrs []string, err error) { | 
 | 	// Use netdir/cs instead of netdir/dns because cs knows about | 
 | 	// host names in local network (e.g. from /lib/ndb/local) | 
 | 	lines, err := queryCS(ctx, "net", host, "1") | 
 | 	if err != nil { | 
 | 		if stringsHasSuffix(err.Error(), "dns failure") { | 
 | 			err = errNoSuchHost | 
 | 		} | 
 | 		return | 
 | 	} | 
 | loop: | 
 | 	for _, line := range lines { | 
 | 		f := getFields(line) | 
 | 		if len(f) < 2 { | 
 | 			continue | 
 | 		} | 
 | 		addr := f[1] | 
 | 		if i := byteIndex(addr, '!'); i >= 0 { | 
 | 			addr = addr[:i] // remove port | 
 | 		} | 
 | 		if ParseIP(addr) == nil { | 
 | 			continue | 
 | 		} | 
 | 		// only return unique addresses | 
 | 		for _, a := range addrs { | 
 | 			if a == addr { | 
 | 				continue loop | 
 | 			} | 
 | 		} | 
 | 		addrs = append(addrs, addr) | 
 | 	} | 
 | 	return | 
 | } | 
 |  | 
 | func (r *Resolver) lookupIP(ctx context.Context, host string) (addrs []IPAddr, err error) { | 
 | 	lits, err := r.lookupHost(ctx, host) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	for _, lit := range lits { | 
 | 		host, zone := splitHostZone(lit) | 
 | 		if ip := ParseIP(host); ip != nil { | 
 | 			addr := IPAddr{IP: ip, Zone: zone} | 
 | 			addrs = append(addrs, addr) | 
 | 		} | 
 | 	} | 
 | 	return | 
 | } | 
 |  | 
 | func (*Resolver) lookupPort(ctx context.Context, network, service string) (port int, err error) { | 
 | 	switch network { | 
 | 	case "tcp4", "tcp6": | 
 | 		network = "tcp" | 
 | 	case "udp4", "udp6": | 
 | 		network = "udp" | 
 | 	} | 
 | 	lines, err := queryCS(ctx, network, "127.0.0.1", toLower(service)) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	unknownPortError := &AddrError{Err: "unknown port", Addr: network + "/" + service} | 
 | 	if len(lines) == 0 { | 
 | 		return 0, unknownPortError | 
 | 	} | 
 | 	f := getFields(lines[0]) | 
 | 	if len(f) < 2 { | 
 | 		return 0, unknownPortError | 
 | 	} | 
 | 	s := f[1] | 
 | 	if i := byteIndex(s, '!'); i >= 0 { | 
 | 		s = s[i+1:] // remove address | 
 | 	} | 
 | 	if n, _, ok := dtoi(s); ok { | 
 | 		return n, nil | 
 | 	} | 
 | 	return 0, unknownPortError | 
 | } | 
 |  | 
 | func (*Resolver) lookupCNAME(ctx context.Context, name string) (cname string, err error) { | 
 | 	lines, err := queryDNS(ctx, name, "cname") | 
 | 	if err != nil { | 
 | 		if stringsHasSuffix(err.Error(), "dns failure") { | 
 | 			cname = name + "." | 
 | 			err = nil | 
 | 		} | 
 | 		return | 
 | 	} | 
 | 	if len(lines) > 0 { | 
 | 		if f := getFields(lines[0]); len(f) >= 3 { | 
 | 			return f[2] + ".", nil | 
 | 		} | 
 | 	} | 
 | 	return "", errors.New("bad response from ndb/dns") | 
 | } | 
 |  | 
 | func (*Resolver) lookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) { | 
 | 	var target string | 
 | 	if service == "" && proto == "" { | 
 | 		target = name | 
 | 	} else { | 
 | 		target = "_" + service + "._" + proto + "." + name | 
 | 	} | 
 | 	lines, err := queryDNS(ctx, target, "srv") | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	for _, line := range lines { | 
 | 		f := getFields(line) | 
 | 		if len(f) < 6 { | 
 | 			continue | 
 | 		} | 
 | 		port, _, portOk := dtoi(f[4]) | 
 | 		priority, _, priorityOk := dtoi(f[3]) | 
 | 		weight, _, weightOk := dtoi(f[2]) | 
 | 		if !(portOk && priorityOk && weightOk) { | 
 | 			continue | 
 | 		} | 
 | 		addrs = append(addrs, &SRV{absDomainName([]byte(f[5])), uint16(port), uint16(priority), uint16(weight)}) | 
 | 		cname = absDomainName([]byte(f[0])) | 
 | 	} | 
 | 	byPriorityWeight(addrs).sort() | 
 | 	return | 
 | } | 
 |  | 
 | func (*Resolver) lookupMX(ctx context.Context, name string) (mx []*MX, err error) { | 
 | 	lines, err := queryDNS(ctx, name, "mx") | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	for _, line := range lines { | 
 | 		f := getFields(line) | 
 | 		if len(f) < 4 { | 
 | 			continue | 
 | 		} | 
 | 		if pref, _, ok := dtoi(f[2]); ok { | 
 | 			mx = append(mx, &MX{absDomainName([]byte(f[3])), uint16(pref)}) | 
 | 		} | 
 | 	} | 
 | 	byPref(mx).sort() | 
 | 	return | 
 | } | 
 |  | 
 | func (*Resolver) lookupNS(ctx context.Context, name string) (ns []*NS, err error) { | 
 | 	lines, err := queryDNS(ctx, name, "ns") | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	for _, line := range lines { | 
 | 		f := getFields(line) | 
 | 		if len(f) < 3 { | 
 | 			continue | 
 | 		} | 
 | 		ns = append(ns, &NS{absDomainName([]byte(f[2]))}) | 
 | 	} | 
 | 	return | 
 | } | 
 |  | 
 | func (*Resolver) lookupTXT(ctx context.Context, name string) (txt []string, err error) { | 
 | 	lines, err := queryDNS(ctx, name, "txt") | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	for _, line := range lines { | 
 | 		if i := byteIndex(line, '\t'); i >= 0 { | 
 | 			txt = append(txt, absDomainName([]byte(line[i+1:]))) | 
 | 		} | 
 | 	} | 
 | 	return | 
 | } | 
 |  | 
 | func (*Resolver) lookupAddr(ctx context.Context, addr string) (name []string, err error) { | 
 | 	arpa, err := reverseaddr(addr) | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	lines, err := queryDNS(ctx, arpa, "ptr") | 
 | 	if err != nil { | 
 | 		return | 
 | 	} | 
 | 	for _, line := range lines { | 
 | 		f := getFields(line) | 
 | 		if len(f) < 3 { | 
 | 			continue | 
 | 		} | 
 | 		name = append(name, absDomainName([]byte(f[2]))) | 
 | 	} | 
 | 	return | 
 | } |