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 5package net 6 7import ( 8 "errors" 9 "internal/bytealg" 10 "io/fs" 11 "net/netip" 12 "sync" 13 "time" 14) 15 16const cacheMaxAge = 5 * time.Second 17 18func parseLiteralIP(addr string) string { 19 ip, err := netip.ParseAddr(addr) 20 if err != nil { 21 return "" 22 } 23 return ip.String() 24} 25 26type byName struct { 27 addrs []string 28 canonicalName string 29} 30 31// hosts contains known host entries. 32var hosts struct { 33 sync.Mutex 34 35 // Key for the list of literal IP addresses must be a host 36 // name. It would be part of DNS labels, a FQDN or an absolute 37 // FQDN. 38 // For now the key is converted to lower case for convenience. 39 byName map[string]byName 40 41 // Key for the list of host names must be a literal IP address 42 // including IPv6 address with zone identifier. 43 // We don't support old-classful IP address notation. 44 byAddr map[string][]string 45 46 expire time.Time 47 path string 48 mtime time.Time 49 size int64 50} 51 52func readHosts() { 53 now := time.Now() 54 hp := hostsFilePath 55 56 if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 { 57 return 58 } 59 mtime, size, err := stat(hp) 60 if err == nil && hosts.path == hp && hosts.mtime.Equal(mtime) && hosts.size == size { 61 hosts.expire = now.Add(cacheMaxAge) 62 return 63 } 64 65 hs := make(map[string]byName) 66 is := make(map[string][]string) 67 68 file, err := open(hp) 69 if err != nil { 70 if !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, fs.ErrPermission) { 71 return 72 } 73 } 74 75 if file != nil { 76 defer file.close() 77 for line, ok := file.readLine(); ok; line, ok = file.readLine() { 78 if i := bytealg.IndexByteString(line, '#'); i >= 0 { 79 // Discard comments. 80 line = line[0:i] 81 } 82 f := getFields(line) 83 if len(f) < 2 { 84 continue 85 } 86 addr := parseLiteralIP(f[0]) 87 if addr == "" { 88 continue 89 } 90 91 var canonical string 92 for i := 1; i < len(f); i++ { 93 name := absDomainName(f[i]) 94 h := []byte(f[i]) 95 lowerASCIIBytes(h) 96 key := absDomainName(string(h)) 97 98 if i == 1 { 99 canonical = key 100 } 101 102 is[addr] = append(is[addr], name) 103 104 if v, ok := hs[key]; ok { 105 hs[key] = byName{ 106 addrs: append(v.addrs, addr), 107 canonicalName: v.canonicalName, 108 } 109 continue 110 } 111 112 hs[key] = byName{ 113 addrs: []string{addr}, 114 canonicalName: canonical, 115 } 116 } 117 } 118 } 119 // Update the data cache. 120 hosts.expire = now.Add(cacheMaxAge) 121 hosts.path = hp 122 hosts.byName = hs 123 hosts.byAddr = is 124 hosts.mtime = mtime 125 hosts.size = size 126} 127 128// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts. 129func lookupStaticHost(host string) ([]string, string) { 130 hosts.Lock() 131 defer hosts.Unlock() 132 readHosts() 133 if len(hosts.byName) != 0 { 134 if hasUpperCase(host) { 135 lowerHost := []byte(host) 136 lowerASCIIBytes(lowerHost) 137 host = string(lowerHost) 138 } 139 if byName, ok := hosts.byName[absDomainName(host)]; ok { 140 ipsCp := make([]string, len(byName.addrs)) 141 copy(ipsCp, byName.addrs) 142 return ipsCp, byName.canonicalName 143 } 144 } 145 return nil, "" 146} 147 148// lookupStaticAddr looks up the hosts for the given address from /etc/hosts. 149func lookupStaticAddr(addr string) []string { 150 hosts.Lock() 151 defer hosts.Unlock() 152 readHosts() 153 addr = parseLiteralIP(addr) 154 if addr == "" { 155 return nil 156 } 157 if len(hosts.byAddr) != 0 { 158 if hosts, ok := hosts.byAddr[addr]; ok { 159 hostsCp := make([]string, len(hosts)) 160 copy(hostsCp, hosts) 161 return hostsCp 162 } 163 } 164 return nil 165} 166