github.com/comwrg/go/src@v0.0.0-20220319063731-c238d0440370/net/dnsconfig_unix.go (about) 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris 7 8 // Read system DNS config from /etc/resolv.conf 9 10 package net 11 12 import ( 13 "github.com/comwrg/go/src/internal/bytealg" 14 "os" 15 "sync/atomic" 16 "time" 17 ) 18 19 var ( 20 defaultNS = []string{"127.0.0.1:53", "[::1]:53"} 21 getHostname = os.Hostname // variable for testing 22 ) 23 24 type dnsConfig struct { 25 servers []string // server addresses (in host:port form) to use 26 search []string // rooted suffixes to append to local name 27 ndots int // number of dots in name to trigger absolute lookup 28 timeout time.Duration // wait before giving up on a query, including retries 29 attempts int // lost packets before giving up on server 30 rotate bool // round robin among servers 31 unknownOpt bool // anything unknown was encountered 32 lookup []string // OpenBSD top-level database "lookup" order 33 err error // any error that occurs during open of resolv.conf 34 mtime time.Time // time of resolv.conf modification 35 soffset uint32 // used by serverOffset 36 singleRequest bool // use sequential A and AAAA queries instead of parallel queries 37 useTCP bool // force usage of TCP for DNS resolutions 38 } 39 40 // See resolv.conf(5) on a Linux machine. 41 func dnsReadConfig(filename string) *dnsConfig { 42 conf := &dnsConfig{ 43 ndots: 1, 44 timeout: 5 * time.Second, 45 attempts: 2, 46 } 47 file, err := open(filename) 48 if err != nil { 49 conf.servers = defaultNS 50 conf.search = dnsDefaultSearch() 51 conf.err = err 52 return conf 53 } 54 defer file.close() 55 if fi, err := file.file.Stat(); err == nil { 56 conf.mtime = fi.ModTime() 57 } else { 58 conf.servers = defaultNS 59 conf.search = dnsDefaultSearch() 60 conf.err = err 61 return conf 62 } 63 for line, ok := file.readLine(); ok; line, ok = file.readLine() { 64 if len(line) > 0 && (line[0] == ';' || line[0] == '#') { 65 // comment. 66 continue 67 } 68 f := getFields(line) 69 if len(f) < 1 { 70 continue 71 } 72 switch f[0] { 73 case "nameserver": // add one name server 74 if len(f) > 1 && len(conf.servers) < 3 { // small, but the standard limit 75 // One more check: make sure server name is 76 // just an IP address. Otherwise we need DNS 77 // to look it up. 78 if parseIPv4(f[1]) != nil { 79 conf.servers = append(conf.servers, JoinHostPort(f[1], "53")) 80 } else if ip, _ := parseIPv6Zone(f[1]); ip != nil { 81 conf.servers = append(conf.servers, JoinHostPort(f[1], "53")) 82 } 83 } 84 85 case "domain": // set search path to just this domain 86 if len(f) > 1 { 87 conf.search = []string{ensureRooted(f[1])} 88 } 89 90 case "search": // set search path to given servers 91 conf.search = make([]string, len(f)-1) 92 for i := 0; i < len(conf.search); i++ { 93 conf.search[i] = ensureRooted(f[i+1]) 94 } 95 96 case "options": // magic options 97 for _, s := range f[1:] { 98 switch { 99 case hasPrefix(s, "ndots:"): 100 n, _, _ := dtoi(s[6:]) 101 if n < 0 { 102 n = 0 103 } else if n > 15 { 104 n = 15 105 } 106 conf.ndots = n 107 case hasPrefix(s, "timeout:"): 108 n, _, _ := dtoi(s[8:]) 109 if n < 1 { 110 n = 1 111 } 112 conf.timeout = time.Duration(n) * time.Second 113 case hasPrefix(s, "attempts:"): 114 n, _, _ := dtoi(s[9:]) 115 if n < 1 { 116 n = 1 117 } 118 conf.attempts = n 119 case s == "rotate": 120 conf.rotate = true 121 case s == "single-request" || s == "single-request-reopen": 122 // Linux option: 123 // http://man7.org/linux/man-pages/man5/resolv.conf.5.html 124 // "By default, glibc performs IPv4 and IPv6 lookups in parallel [...] 125 // This option disables the behavior and makes glibc 126 // perform the IPv6 and IPv4 requests sequentially." 127 conf.singleRequest = true 128 case s == "use-vc" || s == "usevc" || s == "tcp": 129 // Linux (use-vc), FreeBSD (usevc) and OpenBSD (tcp) option: 130 // http://man7.org/linux/man-pages/man5/resolv.conf.5.html 131 // "Sets RES_USEVC in _res.options. 132 // This option forces the use of TCP for DNS resolutions." 133 // https://www.freebsd.org/cgi/man.cgi?query=resolv.conf&sektion=5&manpath=freebsd-release-ports 134 // https://man.openbsd.org/resolv.conf.5 135 conf.useTCP = true 136 default: 137 conf.unknownOpt = true 138 } 139 } 140 141 case "lookup": 142 // OpenBSD option: 143 // https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/resolv.conf.5 144 // "the legal space-separated values are: bind, file, yp" 145 conf.lookup = f[1:] 146 147 default: 148 conf.unknownOpt = true 149 } 150 } 151 if len(conf.servers) == 0 { 152 conf.servers = defaultNS 153 } 154 if len(conf.search) == 0 { 155 conf.search = dnsDefaultSearch() 156 } 157 return conf 158 } 159 160 // serverOffset returns an offset that can be used to determine 161 // indices of servers in c.servers when making queries. 162 // When the rotate option is enabled, this offset increases. 163 // Otherwise it is always 0. 164 func (c *dnsConfig) serverOffset() uint32 { 165 if c.rotate { 166 return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start 167 } 168 return 0 169 } 170 171 func dnsDefaultSearch() []string { 172 hn, err := getHostname() 173 if err != nil { 174 // best effort 175 return nil 176 } 177 if i := bytealg.IndexByteString(hn, '.'); i >= 0 && i < len(hn)-1 { 178 return []string{ensureRooted(hn[i+1:])} 179 } 180 return nil 181 } 182 183 func hasPrefix(s, prefix string) bool { 184 return len(s) >= len(prefix) && s[:len(prefix)] == prefix 185 } 186 187 func ensureRooted(s string) string { 188 if len(s) > 0 && s[len(s)-1] == '.' { 189 return s 190 } 191 return s + "." 192 }