mirror of
https://github.com/coredns/coredns.git
synced 2026-07-10 17:50:12 -04:00
LookupStaticHostV4/V6 dereferenced h.hmap and h.inline as call arguments, which are evaluated before lookupStaticHostFamily takes the read lock. The reload path (readHosts) swaps h.hmap under h.Lock() on every reload, so the field read raced the swap for every A/AAAA lookup. This was introduced when wildcard support (#8185) refactored these methods to pass the maps as parameters; LookupStaticAddr still reads the fields inside the lock and was unaffected. Read h.hmap/h.inline inside the RLock by selecting the address family with a bool instead of passing pre-dereferenced maps. Also read h.mtime under the existing RLock in readHosts: it was read without a lock while the reload writes it under h.Lock(), and readHosts runs from both the OnStartup handler and the reload ticker goroutine. Both races are confirmed by go test -race. Signed-off-by: Pavel Lazureykis <pavel@lazureykis.dev>
293 lines
6.6 KiB
Go
293 lines
6.6 KiB
Go
// Copyright 2009 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.
|
|
|
|
// This file is a modified version of net/hosts.go from the golang repo
|
|
|
|
package hosts
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
)
|
|
|
|
// parseIP calls discards any v6 zone info, before calling net.ParseIP.
|
|
func parseIP(addr string) net.IP {
|
|
if i := strings.Index(addr, "%"); i >= 0 {
|
|
// discard ipv6 zone
|
|
addr = addr[0:i]
|
|
}
|
|
|
|
return net.ParseIP(addr)
|
|
}
|
|
|
|
type options struct {
|
|
// automatically generate IP to Hostname PTR entries
|
|
// for host entries we parse
|
|
autoReverse bool
|
|
|
|
// The TTL of the record we generate
|
|
ttl uint32
|
|
|
|
// The time between two reload of the configuration
|
|
reload time.Duration
|
|
}
|
|
|
|
func newOptions() *options {
|
|
return &options{
|
|
autoReverse: true,
|
|
ttl: 3600,
|
|
reload: 5 * time.Second,
|
|
}
|
|
}
|
|
|
|
// Map contains the IPv4/IPv6 and reverse mapping.
|
|
type Map struct {
|
|
// Key for the list of literal IP addresses must be a FQDN lowercased host name.
|
|
name4 map[string][]net.IP
|
|
name6 map[string][]net.IP
|
|
|
|
// Wildcard owner names (e.g. *.example.com.) map to IP addresses.
|
|
wildName4 map[string][]net.IP
|
|
wildName6 map[string][]net.IP
|
|
|
|
// Key for the list of host names must be a literal IP address
|
|
// including IPv6 address without zone identifier.
|
|
// We don't support old-classful IP address notation.
|
|
addr map[string][]string
|
|
}
|
|
|
|
func newMap() *Map {
|
|
return &Map{
|
|
name4: make(map[string][]net.IP),
|
|
name6: make(map[string][]net.IP),
|
|
wildName4: make(map[string][]net.IP),
|
|
wildName6: make(map[string][]net.IP),
|
|
addr: make(map[string][]string),
|
|
}
|
|
}
|
|
|
|
// Len returns the total number of addresses in the hostmap, this includes V4/V6 and any reverse addresses.
|
|
func (h *Map) Len() int {
|
|
l := 0
|
|
for _, v4 := range h.name4 {
|
|
l += len(v4)
|
|
}
|
|
for _, v6 := range h.name6 {
|
|
l += len(v6)
|
|
}
|
|
for _, v4 := range h.wildName4 {
|
|
l += len(v4)
|
|
}
|
|
for _, v6 := range h.wildName6 {
|
|
l += len(v6)
|
|
}
|
|
for _, a := range h.addr {
|
|
l += len(a)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// Hostsfile contains known host entries.
|
|
type Hostsfile struct {
|
|
sync.RWMutex
|
|
|
|
// list of zones we are authoritative for
|
|
Origins []string
|
|
|
|
// hosts maps for lookups
|
|
hmap *Map
|
|
|
|
// inline saves the hosts file that is inlined in a Corefile.
|
|
inline *Map
|
|
|
|
// path to the hosts file
|
|
path string
|
|
|
|
// mtime and size are only read and modified by a single goroutine
|
|
mtime time.Time
|
|
size int64
|
|
|
|
options *options
|
|
}
|
|
|
|
// readHosts determines if the cached data needs to be updated based on the size and modification time of the hostsfile.
|
|
func (h *Hostsfile) readHosts() {
|
|
file, err := os.Open(h.path)
|
|
if err != nil {
|
|
// We already log a warning if the file doesn't exist or can't be opened on setup. No need to return the error here.
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return
|
|
}
|
|
h.RLock()
|
|
size := h.size
|
|
mtime := h.mtime
|
|
h.RUnlock()
|
|
|
|
if mtime.Equal(stat.ModTime()) && size == stat.Size() {
|
|
return
|
|
}
|
|
|
|
newMap := h.parse(file)
|
|
log.Debugf("Parsed hosts file into %d entries", newMap.Len())
|
|
|
|
h.Lock()
|
|
|
|
h.hmap = newMap
|
|
// Update the data cache.
|
|
h.mtime = stat.ModTime()
|
|
h.size = stat.Size()
|
|
|
|
hostsEntries.WithLabelValues(h.path).Set(float64(h.inline.Len() + h.hmap.Len()))
|
|
hostsReloadTime.Set(float64(stat.ModTime().UnixNano()) / 1e9)
|
|
h.Unlock()
|
|
}
|
|
|
|
func (h *Hostsfile) initInline(inline []string) {
|
|
if len(inline) == 0 {
|
|
return
|
|
}
|
|
|
|
h.inline = h.parse(strings.NewReader(strings.Join(inline, "\n")))
|
|
}
|
|
|
|
// Parse reads the hostsfile and populates the byName and addr maps.
|
|
func (h *Hostsfile) parse(r io.Reader) *Map {
|
|
hmap := newMap()
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
line := scanner.Bytes()
|
|
if i := bytes.Index(line, []byte{'#'}); i >= 0 {
|
|
// Discard comments.
|
|
line = line[0:i]
|
|
}
|
|
f := bytes.Fields(line)
|
|
if len(f) < 2 {
|
|
continue
|
|
}
|
|
addr := parseIP(string(f[0]))
|
|
if addr == nil {
|
|
continue
|
|
}
|
|
|
|
var family int
|
|
if addr.To4() != nil {
|
|
family = 1
|
|
} else {
|
|
family = 2
|
|
}
|
|
|
|
for i := 1; i < len(f); i++ {
|
|
name := plugin.Name(string(f[i])).Normalize()
|
|
if plugin.Zones(h.Origins).Matches(name) == "" {
|
|
// name is not in Origins
|
|
continue
|
|
}
|
|
if isWildcardName(name) {
|
|
switch family {
|
|
case 1:
|
|
hmap.wildName4[name] = append(hmap.wildName4[name], addr)
|
|
case 2:
|
|
hmap.wildName6[name] = append(hmap.wildName6[name], addr)
|
|
}
|
|
continue
|
|
}
|
|
switch family {
|
|
case 1:
|
|
hmap.name4[name] = append(hmap.name4[name], addr)
|
|
case 2:
|
|
hmap.name6[name] = append(hmap.name6[name], addr)
|
|
default:
|
|
continue
|
|
}
|
|
if !h.options.autoReverse {
|
|
continue
|
|
}
|
|
hmap.addr[addr.String()] = append(hmap.addr[addr.String()], name)
|
|
}
|
|
}
|
|
|
|
return hmap
|
|
}
|
|
|
|
func (h *Hostsfile) lookupStaticHostLocked(m, wild map[string][]net.IP, host string) []net.IP {
|
|
if ips, ok := m[host]; ok {
|
|
ipsCp := make([]net.IP, len(ips))
|
|
copy(ipsCp, ips)
|
|
return ipsCp
|
|
}
|
|
if pattern := replaceWithAsteriskLabel(host); pattern != "" {
|
|
if ips, ok := wild[pattern]; ok {
|
|
ipsCp := make([]net.IP, len(ips))
|
|
copy(ipsCp, ips)
|
|
return ipsCp
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *Hostsfile) lookupStaticHostFamily(host string, v4 bool) []net.IP {
|
|
host = strings.ToLower(host)
|
|
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
|
|
// h.hmap and h.inline must be read under the lock: readHosts swaps h.hmap
|
|
// under h.Lock() on every reload.
|
|
var ip1, ip2 []net.IP
|
|
if v4 {
|
|
ip1 = h.lookupStaticHostLocked(h.hmap.name4, h.hmap.wildName4, host)
|
|
ip2 = h.lookupStaticHostLocked(h.inline.name4, h.inline.wildName4, host)
|
|
} else {
|
|
ip1 = h.lookupStaticHostLocked(h.hmap.name6, h.hmap.wildName6, host)
|
|
ip2 = h.lookupStaticHostLocked(h.inline.name6, h.inline.wildName6, host)
|
|
}
|
|
return append(ip1, ip2...)
|
|
}
|
|
|
|
// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file.
|
|
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
|
|
return h.lookupStaticHostFamily(host, true)
|
|
}
|
|
|
|
// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file.
|
|
func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP {
|
|
return h.lookupStaticHostFamily(host, false)
|
|
}
|
|
|
|
// LookupStaticAddr looks up the hosts for the given address from the hosts file.
|
|
func (h *Hostsfile) LookupStaticAddr(addr string) []string {
|
|
addr = parseIP(addr).String()
|
|
if addr == "" {
|
|
return nil
|
|
}
|
|
|
|
h.RLock()
|
|
defer h.RUnlock()
|
|
hosts1 := h.hmap.addr[addr]
|
|
hosts2 := h.inline.addr[addr]
|
|
|
|
if len(hosts1) == 0 && len(hosts2) == 0 {
|
|
return nil
|
|
}
|
|
|
|
hostsCp := make([]string, len(hosts1)+len(hosts2))
|
|
copy(hostsCp, hosts1)
|
|
copy(hostsCp[len(hosts1):], hosts2)
|
|
return hostsCp
|
|
}
|