hosts.mx raw

   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  package net
   6  
   7  import (
   8  	"errors"
   9  	"internal/bytealg"
  10  	"io/fs"
  11  	"net/netip"
  12  	"sync"
  13  	"time"
  14  )
  15  
  16  const cacheMaxAge = 5 * time.Second
  17  
  18  func parseLiteralIP(addr string) string {
  19  	ip, err := netip.ParseAddr(addr)
  20  	if err != nil {
  21  		return ""
  22  	}
  23  	return ip.String()
  24  }
  25  
  26  type byName struct {
  27  	addrs         [][]byte
  28  	canonicalName string
  29  }
  30  
  31  // hosts contains known host entries.
  32  var 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][][]byte
  45  
  46  	expire time.Time
  47  	path   string
  48  	mtime  time.Time
  49  	size   int64
  50  }
  51  
  52  func 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 := map[string]byName{}
  66  	is := map[string][][]byte{}
  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:         [][]byte{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.
 129  func lookupStaticHost(host string) ([][]byte, 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 := [][]byte{: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.
 149  func lookupStaticAddr(addr string) [][]byte {
 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 := [][]byte{:len(hosts)}
 160  			copy(hostsCp, hosts)
 161  			return hostsCp
 162  		}
 163  	}
 164  	return nil
 165  }
 166