resolvconf.go 6.1 KB

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