github.com/xuyutom/docker@v1.6.0/pkg/networkfs/resolvconf/resolvconf.go (about)

     1  package resolvconf
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"regexp"
     7  	"strings"
     8  	"sync"
     9  
    10  	log "github.com/Sirupsen/logrus"
    11  	"github.com/docker/docker/utils"
    12  )
    13  
    14  var (
    15  	// Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
    16  	defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
    17  	defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
    18  	ipv4NumBlock   = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
    19  	ipv4Address    = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
    20  	// This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
    21  	// will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
    22  	// -- e.g. other link-local types -- either won't work in containers or are unnecessary.
    23  	// For readability and sufficiency for Docker purposes this seemed more reasonable than a
    24  	// 1000+ character regexp with exact and complete IPv6 validation
    25  	ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
    26  	ipLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
    27  
    28  	localhostIPRegexp = regexp.MustCompile(ipLocalhost)
    29  	localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`)
    30  	nsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
    31  	nsRegexp          = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
    32  	searchRegexp      = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
    33  )
    34  
    35  var lastModified struct {
    36  	sync.Mutex
    37  	sha256   string
    38  	contents []byte
    39  }
    40  
    41  func Get() ([]byte, error) {
    42  	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	return resolv, nil
    47  }
    48  
    49  // Retrieves the host /etc/resolv.conf file, checks against the last hash
    50  // and, if modified since last check, returns the bytes and new hash.
    51  // This feature is used by the resolv.conf updater for containers
    52  func GetIfChanged() ([]byte, string, error) {
    53  	lastModified.Lock()
    54  	defer lastModified.Unlock()
    55  
    56  	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
    57  	if err != nil {
    58  		return nil, "", err
    59  	}
    60  	newHash, err := utils.HashData(bytes.NewReader(resolv))
    61  	if err != nil {
    62  		return nil, "", err
    63  	}
    64  	if lastModified.sha256 != newHash {
    65  		lastModified.sha256 = newHash
    66  		lastModified.contents = resolv
    67  		return resolv, newHash, nil
    68  	}
    69  	// nothing changed, so return no data
    70  	return nil, "", nil
    71  }
    72  
    73  // retrieve the last used contents and hash of the host resolv.conf
    74  // Used by containers updating on restart
    75  func GetLastModified() ([]byte, string) {
    76  	lastModified.Lock()
    77  	defer lastModified.Unlock()
    78  
    79  	return lastModified.contents, lastModified.sha256
    80  }
    81  
    82  // FilterResolvDns has two main jobs:
    83  // 1. It looks for localhost (127.*|::1) entries in the provided
    84  //    resolv.conf, removing local nameserver entries, and, if the resulting
    85  //    cleaned config has no defined nameservers left, adds default DNS entries
    86  // 2. Given the caller provides the enable/disable state of IPv6, the filter
    87  //    code will remove all IPv6 nameservers if it is not enabled for containers
    88  //
    89  // It also returns a boolean to notify the caller if changes were made at all
    90  func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
    91  	changed := false
    92  	cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
    93  	// if IPv6 is not enabled, also clean out any IPv6 address nameserver
    94  	if !ipv6Enabled {
    95  		cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
    96  	}
    97  	// if the resulting resolvConf has no more nameservers defined, add appropriate
    98  	// default DNS servers for IPv4 and (optionally) IPv6
    99  	if len(GetNameservers(cleanedResolvConf)) == 0 {
   100  		log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns)
   101  		dns := defaultIPv4Dns
   102  		if ipv6Enabled {
   103  			log.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns)
   104  			dns = append(dns, defaultIPv6Dns...)
   105  		}
   106  		cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
   107  	}
   108  	if !bytes.Equal(resolvConf, cleanedResolvConf) {
   109  		changed = true
   110  	}
   111  	return cleanedResolvConf, changed
   112  }
   113  
   114  // getLines parses input into lines and strips away comments.
   115  func getLines(input []byte, commentMarker []byte) [][]byte {
   116  	lines := bytes.Split(input, []byte("\n"))
   117  	var output [][]byte
   118  	for _, currentLine := range lines {
   119  		var commentIndex = bytes.Index(currentLine, commentMarker)
   120  		if commentIndex == -1 {
   121  			output = append(output, currentLine)
   122  		} else {
   123  			output = append(output, currentLine[:commentIndex])
   124  		}
   125  	}
   126  	return output
   127  }
   128  
   129  // returns true if the IP string matches the localhost IP regular expression.
   130  // Used for determining if nameserver settings are being passed which are
   131  // localhost addresses
   132  func IsLocalhost(ip string) bool {
   133  	return localhostIPRegexp.MatchString(ip)
   134  }
   135  
   136  // GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
   137  func GetNameservers(resolvConf []byte) []string {
   138  	nameservers := []string{}
   139  	for _, line := range getLines(resolvConf, []byte("#")) {
   140  		var ns = nsRegexp.FindSubmatch(line)
   141  		if len(ns) > 0 {
   142  			nameservers = append(nameservers, string(ns[1]))
   143  		}
   144  	}
   145  	return nameservers
   146  }
   147  
   148  // GetNameserversAsCIDR returns nameservers (if any) listed in
   149  // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
   150  // This function's output is intended for net.ParseCIDR
   151  func GetNameserversAsCIDR(resolvConf []byte) []string {
   152  	nameservers := []string{}
   153  	for _, nameserver := range GetNameservers(resolvConf) {
   154  		nameservers = append(nameservers, nameserver+"/32")
   155  	}
   156  	return nameservers
   157  }
   158  
   159  // GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
   160  // If more than one search line is encountered, only the contents of the last
   161  // one is returned.
   162  func GetSearchDomains(resolvConf []byte) []string {
   163  	domains := []string{}
   164  	for _, line := range getLines(resolvConf, []byte("#")) {
   165  		match := searchRegexp.FindSubmatch(line)
   166  		if match == nil {
   167  			continue
   168  		}
   169  		domains = strings.Fields(string(match[1]))
   170  	}
   171  	return domains
   172  }
   173  
   174  func Build(path string, dns, dnsSearch []string) error {
   175  	content := bytes.NewBuffer(nil)
   176  	for _, dns := range dns {
   177  		if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
   178  			return err
   179  		}
   180  	}
   181  	if len(dnsSearch) > 0 {
   182  		if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
   183  			if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
   184  				return err
   185  			}
   186  		}
   187  	}
   188  
   189  	return ioutil.WriteFile(path, content.Bytes(), 0644)
   190  }