blob: 445b1294e352dd683b91d25d9469c271c7845bec [file] [log] [blame]
// 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"
"internal/bytealg"
"internal/itoa"
"io"
"os"
)
func query(ctx context.Context, filename, query string, bufSize int) (addrs []string, err error) {
queryAddrs := func() (addrs []string, err error) {
file, err := os.OpenFile(filename, os.O_RDWR, 0)
if err != nil {
return nil, err
}
defer file.Close()
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
_, err = file.WriteString(query)
if err != nil {
return nil, err
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
buf := make([]byte, bufSize)
for {
n, _ := file.Read(buf)
if n <= 0 {
break
}
addrs = append(addrs, string(buf[:n]))
}
return addrs, nil
}
type ret struct {
addrs []string
err error
}
ch := make(chan ret, 1)
go func() {
addrs, err := queryAddrs()
ch <- ret{addrs: addrs, err: err}
}()
select {
case r := <-ch:
return r.addrs, r.err
case <-ctx.Done():
return nil, &DNSError{
Name: query,
Err: ctx.Err().Error(),
IsTimeout: ctx.Err() == context.DeadlineExceeded,
}
}
}
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.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[bytealg.IndexByteString(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 {
dnsError := &DNSError{Err: err.Error(), Name: host}
if stringsHasSuffix(err.Error(), "dns failure") {
dnsError.Err = errNoSuchHost.Error()
dnsError.IsNotFound = true
}
return nil, dnsError
}
loop:
for _, line := range lines {
f := getFields(line)
if len(f) < 2 {
continue
}
addr := f[1]
if i := bytealg.IndexByteString(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
}
// preferGoOverPlan9 reports whether the resolver should use the
// "PreferGo" implementation rather than asking plan9 services
// for the answers.
func (r *Resolver) preferGoOverPlan9() bool {
conf := systemConf()
order := conf.hostLookupOrder(r, "") // name is unused
// TODO(bradfitz): for now we only permit use of the PreferGo
// implementation when there's a non-nil Resolver with a
// non-nil Dialer. This is a sign that they the code is trying
// to use their DNS-speaking net.Conn (such as an in-memory
// DNS cache) and they don't want to actually hit the network.
// Once we add support for looking the default DNS servers
// from plan9, though, then we can relax this.
return order != hostLookupCgo && r != nil && r.Dial != nil
}
func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []IPAddr, err error) {
if r.preferGoOverPlan9() {
return r.goLookupIP(ctx, network, host)
}
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 := bytealg.IndexByteString(s, '!'); i >= 0 {
s = s[i+1:] // remove address
}
if n, _, ok := dtoi(s); ok {
return n, nil
}
return 0, unknownPortError
}
func (r *Resolver) lookupCNAME(ctx context.Context, name string) (cname string, err error) {
if r.preferGoOverPlan9() {
return r.goLookupCNAME(ctx, name)
}
lines, err := queryDNS(ctx, name, "cname")
if err != nil {
if stringsHasSuffix(err.Error(), "dns failure") || stringsHasSuffix(err.Error(), "resource does not exist; negrcode 0") {
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 (r *Resolver) lookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) {
if r.preferGoOverPlan9() {
return r.goLookupSRV(ctx, service, proto, name)
}
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(f[5]), uint16(port), uint16(priority), uint16(weight)})
cname = absDomainName(f[0])
}
byPriorityWeight(addrs).sort()
return
}
func (r *Resolver) lookupMX(ctx context.Context, name string) (mx []*MX, err error) {
if r.preferGoOverPlan9() {
return r.goLookupMX(ctx, name)
}
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(f[3]), uint16(pref)})
}
}
byPref(mx).sort()
return
}
func (r *Resolver) lookupNS(ctx context.Context, name string) (ns []*NS, err error) {
if r.preferGoOverPlan9() {
return r.goLookupNS(ctx, name)
}
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(f[2])})
}
return
}
func (r *Resolver) lookupTXT(ctx context.Context, name string) (txt []string, err error) {
if r.preferGoOverPlan9() {
return r.goLookupTXT(ctx, name)
}
lines, err := queryDNS(ctx, name, "txt")
if err != nil {
return
}
for _, line := range lines {
if i := bytealg.IndexByteString(line, '\t'); i >= 0 {
txt = append(txt, line[i+1:])
}
}
return
}
func (r *Resolver) lookupAddr(ctx context.Context, addr string) (name []string, err error) {
if r.preferGoOverPlan9() {
return r.goLookupPTR(ctx, addr)
}
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(f[2]))
}
return
}
// concurrentThreadsLimit returns the number of threads we permit to
// run concurrently doing DNS lookups.
func concurrentThreadsLimit() int {
return 500
}