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