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