diff options
Diffstat (limited to 'src/net/lookup_plan9.go')
-rw-r--r-- | src/net/lookup_plan9.go | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/src/net/lookup_plan9.go b/src/net/lookup_plan9.go new file mode 100644 index 0000000..1995742 --- /dev/null +++ b/src/net/lookup_plan9.go @@ -0,0 +1,384 @@ +// 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 { + _, _, res := r.preferGoOverPlan9WithOrderAndConf() + return res +} + +func (r *Resolver) preferGoOverPlan9WithOrderAndConf() (hostLookupOrder, *dnsConfig, bool) { + order, conf := systemConf().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, conf, 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 order, conf, preferGo := r.preferGoOverPlan9WithOrderAndConf(); preferGo { + return r.goLookupCNAME(ctx, name, order, conf) + } + + 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 _, conf, preferGo := r.preferGoOverPlan9WithOrderAndConf(); preferGo { + return r.goLookupPTR(ctx, addr, conf) + } + 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 +} |