github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/libnetwork/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 "fmt" 7 "os" 8 "strings" 9 10 "github.com/docker/docker/libnetwork/internal/resolvconf" 11 "github.com/opencontainers/go-digest" 12 ) 13 14 // constants for the IP address type 15 const ( 16 IP = iota // IPv4 and IPv6 17 IPv4 18 IPv6 19 ) 20 21 // File contains the resolv.conf content and its hash 22 type File struct { 23 Content []byte 24 Hash []byte 25 } 26 27 func Path() string { 28 return resolvconf.Path() 29 } 30 31 // Get returns the contents of /etc/resolv.conf and its hash 32 func Get() (*File, error) { 33 return GetSpecific(Path()) 34 } 35 36 // GetSpecific returns the contents of the user specified resolv.conf file and its hash 37 func GetSpecific(path string) (*File, error) { 38 resolv, err := os.ReadFile(path) 39 if err != nil { 40 return nil, err 41 } 42 hash := digest.FromBytes(resolv) 43 return &File{Content: resolv, Hash: []byte(hash)}, nil 44 } 45 46 // FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: 47 // 1. It looks for localhost (127.*|::1) entries in the provided 48 // resolv.conf, removing local nameserver entries, and, if the resulting 49 // cleaned config has no defined nameservers left, adds default DNS entries 50 // 2. Given the caller provides the enable/disable state of IPv6, the filter 51 // code will remove all IPv6 nameservers if it is not enabled for containers 52 func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) { 53 rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "") 54 if err != nil { 55 return nil, err 56 } 57 rc.TransformForLegacyNw(ipv6Enabled) 58 content, err := rc.Generate(false) 59 if err != nil { 60 return nil, err 61 } 62 hash := digest.FromBytes(content) 63 return &File{Content: content, Hash: []byte(hash)}, nil 64 } 65 66 // GetNameservers returns nameservers (if any) listed in /etc/resolv.conf 67 func GetNameservers(resolvConf []byte, kind int) []string { 68 rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "") 69 if err != nil { 70 return nil 71 } 72 nsAddrs := rc.NameServers() 73 var nameservers []string 74 for _, addr := range nsAddrs { 75 if kind == IP { 76 nameservers = append(nameservers, addr.String()) 77 } else if kind == IPv4 && addr.Is4() { 78 nameservers = append(nameservers, addr.String()) 79 } else if kind == IPv6 && addr.Is6() { 80 nameservers = append(nameservers, addr.String()) 81 } 82 } 83 return nameservers 84 } 85 86 // GetNameserversAsCIDR returns nameservers (if any) listed in 87 // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") 88 // This function's output is intended for net.ParseCIDR 89 func GetNameserversAsCIDR(resolvConf []byte) []string { 90 rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "") 91 if err != nil { 92 return nil 93 } 94 nsAddrs := rc.NameServers() 95 nameservers := make([]string, 0, len(nsAddrs)) 96 for _, addr := range nsAddrs { 97 str := fmt.Sprintf("%s/%d", addr.WithZone("").String(), addr.BitLen()) 98 nameservers = append(nameservers, str) 99 } 100 return nameservers 101 } 102 103 // GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf 104 // If more than one search line is encountered, only the contents of the last 105 // one is returned. 106 func GetSearchDomains(resolvConf []byte) []string { 107 rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "") 108 if err != nil { 109 return nil 110 } 111 return rc.Search() 112 } 113 114 // GetOptions returns options (if any) listed in /etc/resolv.conf 115 // If more than one options line is encountered, only the contents of the last 116 // one is returned. 117 func GetOptions(resolvConf []byte) []string { 118 rc, err := resolvconf.Parse(bytes.NewBuffer(resolvConf), "") 119 if err != nil { 120 return nil 121 } 122 return rc.Options() 123 } 124 125 // Build generates and writes a configuration file to path containing a nameserver 126 // entry for every element in nameservers, a "search" entry for every element in 127 // dnsSearch, and an "options" entry for every element in dnsOptions. It returns 128 // a File containing the generated content and its (sha256) hash. 129 // 130 // Note that the resolv.conf file is written, but the hash file is not. 131 func Build(path string, nameservers, dnsSearch, dnsOptions []string) (*File, error) { 132 content := bytes.NewBuffer(nil) 133 if len(dnsSearch) > 0 { 134 if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { 135 if _, err := content.WriteString("search " + searchString + "\n"); err != nil { 136 return nil, err 137 } 138 } 139 } 140 for _, dns := range nameservers { 141 if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { 142 return nil, err 143 } 144 } 145 if len(dnsOptions) > 0 { 146 if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" { 147 if _, err := content.WriteString("options " + optsString + "\n"); err != nil { 148 return nil, err 149 } 150 } 151 } 152 153 if err := os.WriteFile(path, content.Bytes(), 0o644); err != nil { 154 return nil, err 155 } 156 157 hash := digest.FromBytes(content.Bytes()) 158 return &File{Content: content.Bytes(), Hash: []byte(hash)}, nil 159 }