1// Copyright 2009 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
5//go:build !windows
6
7// Read system DNS config from /etc/resolv.conf
8
9package net
10
11import (
12	"internal/bytealg"
13	"internal/stringslite"
14	"net/netip"
15	"time"
16)
17
18// See resolv.conf(5) on a Linux machine.
19func dnsReadConfig(filename string) *dnsConfig {
20	conf := &dnsConfig{
21		ndots:    1,
22		timeout:  5 * time.Second,
23		attempts: 2,
24	}
25	file, err := open(filename)
26	if err != nil {
27		conf.servers = defaultNS
28		conf.search = dnsDefaultSearch()
29		conf.err = err
30		return conf
31	}
32	defer file.close()
33	if fi, err := file.file.Stat(); err == nil {
34		conf.mtime = fi.ModTime()
35	} else {
36		conf.servers = defaultNS
37		conf.search = dnsDefaultSearch()
38		conf.err = err
39		return conf
40	}
41	for line, ok := file.readLine(); ok; line, ok = file.readLine() {
42		if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
43			// comment.
44			continue
45		}
46		f := getFields(line)
47		if len(f) < 1 {
48			continue
49		}
50		switch f[0] {
51		case "nameserver": // add one name server
52			if len(f) > 1 && len(conf.servers) < 3 { // small, but the standard limit
53				// One more check: make sure server name is
54				// just an IP address. Otherwise we need DNS
55				// to look it up.
56				if _, err := netip.ParseAddr(f[1]); err == nil {
57					conf.servers = append(conf.servers, JoinHostPort(f[1], "53"))
58				}
59			}
60
61		case "domain": // set search path to just this domain
62			if len(f) > 1 {
63				conf.search = []string{ensureRooted(f[1])}
64			}
65
66		case "search": // set search path to given servers
67			conf.search = make([]string, 0, len(f)-1)
68			for i := 1; i < len(f); i++ {
69				name := ensureRooted(f[i])
70				if name == "." {
71					continue
72				}
73				conf.search = append(conf.search, name)
74			}
75
76		case "options": // magic options
77			for _, s := range f[1:] {
78				switch {
79				case stringslite.HasPrefix(s, "ndots:"):
80					n, _, _ := dtoi(s[6:])
81					if n < 0 {
82						n = 0
83					} else if n > 15 {
84						n = 15
85					}
86					conf.ndots = n
87				case stringslite.HasPrefix(s, "timeout:"):
88					n, _, _ := dtoi(s[8:])
89					if n < 1 {
90						n = 1
91					}
92					conf.timeout = time.Duration(n) * time.Second
93				case stringslite.HasPrefix(s, "attempts:"):
94					n, _, _ := dtoi(s[9:])
95					if n < 1 {
96						n = 1
97					}
98					conf.attempts = n
99				case s == "rotate":
100					conf.rotate = true
101				case s == "single-request" || s == "single-request-reopen":
102					// Linux option:
103					// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
104					// "By default, glibc performs IPv4 and IPv6 lookups in parallel [...]
105					//  This option disables the behavior and makes glibc
106					//  perform the IPv6 and IPv4 requests sequentially."
107					conf.singleRequest = true
108				case s == "use-vc" || s == "usevc" || s == "tcp":
109					// Linux (use-vc), FreeBSD (usevc) and OpenBSD (tcp) option:
110					// http://man7.org/linux/man-pages/man5/resolv.conf.5.html
111					// "Sets RES_USEVC in _res.options.
112					//  This option forces the use of TCP for DNS resolutions."
113					// https://www.freebsd.org/cgi/man.cgi?query=resolv.conf&sektion=5&manpath=freebsd-release-ports
114					// https://man.openbsd.org/resolv.conf.5
115					conf.useTCP = true
116				case s == "trust-ad":
117					conf.trustAD = true
118				case s == "edns0":
119					// We use EDNS by default.
120					// Ignore this option.
121				case s == "no-reload":
122					conf.noReload = true
123				default:
124					conf.unknownOpt = true
125				}
126			}
127
128		case "lookup":
129			// OpenBSD option:
130			// https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5
131			// "the legal space-separated values are: bind, file, yp"
132			conf.lookup = f[1:]
133
134		default:
135			conf.unknownOpt = true
136		}
137	}
138	if len(conf.servers) == 0 {
139		conf.servers = defaultNS
140	}
141	if len(conf.search) == 0 {
142		conf.search = dnsDefaultSearch()
143	}
144	return conf
145}
146
147func dnsDefaultSearch() []string {
148	hn, err := getHostname()
149	if err != nil {
150		// best effort
151		return nil
152	}
153	if i := bytealg.IndexByteString(hn, '.'); i >= 0 && i < len(hn)-1 {
154		return []string{ensureRooted(hn[i+1:])}
155	}
156	return nil
157}
158
159func ensureRooted(s string) string {
160	if len(s) > 0 && s[len(s)-1] == '.' {
161		return s
162	}
163	return s + "."
164}
165