github.com/jaegerpicker/docker@v0.7.7-0.20150325003727-22dba32b4dab/pkg/resolvconf/resolvconf.go (about)

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