coredns/plugin/hosts/hostsfile.go
Paco Xu e5945d2767
hosts: add hostsfile as label for coredns_hosts_entries (#6801)
Signed-off-by: Paco Xu <paco.xu@daocloud.io>
2024-08-06 07:13:22 -04:00

259 lines
5.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
// 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),
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 _, 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
h.RUnlock()
if h.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
}
family := 0
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
}
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
}
// lookupStaticHost looks up the IP addresses for the given host from the hosts file.
func (h *Hostsfile) lookupStaticHost(m map[string][]net.IP, host string) []net.IP {
h.RLock()
defer h.RUnlock()
if len(m) == 0 {
return nil
}
ips, ok := m[host]
if !ok {
return nil
}
ipsCp := make([]net.IP, len(ips))
copy(ipsCp, ips)
return ipsCp
}
// LookupStaticHostV4 looks up the IPv4 addresses for the given host from the hosts file.
func (h *Hostsfile) LookupStaticHostV4(host string) []net.IP {
host = strings.ToLower(host)
ip1 := h.lookupStaticHost(h.hmap.name4, host)
ip2 := h.lookupStaticHost(h.inline.name4, host)
return append(ip1, ip2...)
}
// LookupStaticHostV6 looks up the IPv6 addresses for the given host from the hosts file.
func (h *Hostsfile) LookupStaticHostV6(host string) []net.IP {
host = strings.ToLower(host)
ip1 := h.lookupStaticHost(h.hmap.name6, host)
ip2 := h.lookupStaticHost(h.inline.name6, host)
return append(ip1, ip2...)
}
// 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
}