1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
package resolvconf
import (
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/containers/storage/pkg/fileutils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
const (
localhost = "127.0.0.1"
systemdResolvedIP = "127.0.0.53"
)
// Params for the New() function.
type Params struct {
// Path is the path to new resolv.conf file which should be created.
Path string
// Namespaces is the list of container namespaces.
// This is required to fist check for a resolv.conf under /etc/netns,
// created by "ip netns". Also used to check if the container has a
// netns in which case localhost nameserver must be filtered.
Namespaces []specs.LinuxNamespace
// IPv6Enabled will filter ipv6 nameservers when not set to true.
IPv6Enabled bool
// KeepHostServers can be set when it is required to still keep the
// original resolv.conf nameservers even when explicit Nameservers
// are set. In this case they will be appended to the given values.
KeepHostServers bool
// KeepHostSearches can be set when it is required to still keep the
// original resolv.conf search domains even when explicit search domains
// are set in Searches.
KeepHostSearches bool
// KeepHostOptions can be set when it is required to still keep the
// original resolv.conf options even when explicit options are set in
// Options.
KeepHostOptions bool
// Nameservers is a list of nameservers the container should use,
// instead of the default ones from the host. Set KeepHostServers
// in order to also keep the hosts resolv.conf nameservers.
Nameservers []string
// Searches is a list of dns search domains the container should use,
// instead of the default ones from the host. Set KeepHostSearches
// in order to also keep the hosts resolv.conf search domains.
Searches []string
// Options is a list of dns options the container should use,
// instead of the default ones from the host. Set KeepHostOptions
// in order to also keep the hosts resolv.conf options.
Options []string
// resolvConfPath is the path which should be used as base to get the dns
// options. This should only be used for testing purposes. For all other
// callers this defaults to /etc/resolv.conf.
resolvConfPath string
}
func getDefaultResolvConf(params *Params) ([]byte, bool, error) {
resolveConf := DefaultResolvConf
// this is only used by testing
if params.resolvConfPath != "" {
resolveConf = params.resolvConfPath
}
hostNS := true
for _, ns := range params.Namespaces {
if ns.Type == specs.NetworkNamespace {
hostNS = false
if ns.Path != "" && !strings.HasPrefix(ns.Path, "/proc/") {
// check for netns created by "ip netns"
path := filepath.Join("/etc/netns", filepath.Base(ns.Path), "resolv.conf")
err := fileutils.Exists(path)
if err == nil {
resolveConf = path
}
}
break
}
}
contents, err := os.ReadFile(resolveConf)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, false, err
}
if hostNS {
return contents, hostNS, nil
}
ns := getNameservers(contents)
// Check for local only resolver, in this case we want to get the real nameservers
// since localhost is not reachable from the netns.
if len(ns) == 1 {
var path string
switch ns[0] {
case systemdResolvedIP:
// used by systemd-resolved
path = "/run/systemd/resolve/resolv.conf"
case localhost:
// used by NetworkManager https://github.com/containers/podman/issues/13599
path = "/run/NetworkManager/no-stub-resolv.conf"
}
if path != "" {
// read the actual resolv.conf file for
resolvedContents, err := os.ReadFile(path)
if err != nil {
// do not error when the file does not exists, the detection logic is not perfect
if !errors.Is(err, os.ErrNotExist) {
return nil, false, fmt.Errorf("local resolver detected, but could not read real resolv.conf at %q: %w", path, err)
}
} else {
logrus.Debugf("found local resolver, using %q to get the nameservers", path)
contents = resolvedContents
}
}
}
return contents, hostNS, nil
}
// unsetSearchDomainsIfNeeded removes the search domain when they contain a single dot as element.
func unsetSearchDomainsIfNeeded(searches []string) []string {
if slices.Contains(searches, ".") {
return nil
}
return searches
}
// New creates a new resolv.conf file with the given params.
func New(params *Params) error {
// short path, if everything is given there is no need to actually read the hosts /etc/resolv.conf
if len(params.Nameservers) > 0 && len(params.Options) > 0 && len(params.Searches) > 0 &&
!params.KeepHostServers && !params.KeepHostOptions && !params.KeepHostSearches {
return build(params.Path, params.Nameservers, unsetSearchDomainsIfNeeded(params.Searches), params.Options)
}
content, hostNS, err := getDefaultResolvConf(params)
if err != nil {
return fmt.Errorf("failed to get the default /etc/resolv.conf content: %w", err)
}
content = filterResolvDNS(content, params.IPv6Enabled, !hostNS)
nameservers := params.Nameservers
if len(nameservers) == 0 || params.KeepHostServers {
nameservers = append(nameservers, getNameservers(content)...)
}
searches := unsetSearchDomainsIfNeeded(params.Searches)
// if no params.Searches then use host ones
// otherwise make sure that they were no explicitly unset before adding host ones
if len(params.Searches) == 0 || (params.KeepHostSearches && len(searches) > 0) {
searches = append(searches, getSearchDomains(content)...)
}
options := params.Options
if len(options) == 0 || params.KeepHostOptions {
options = append(options, getOptions(content)...)
}
return build(params.Path, nameservers, searches, options)
}
// Add will add the given nameservers to the given resolv.conf file.
// It will add the nameserver in front of the existing ones.
func Add(path string, nameservers []string) error {
contents, err := os.ReadFile(path)
if err != nil {
return err
}
nameservers = append(nameservers, getNameservers(contents)...)
return build(path, nameservers, getSearchDomains(contents), getOptions(contents))
}
// Remove the given nameserver from the given resolv.conf file.
func Remove(path string, nameservers []string) error {
contents, err := os.ReadFile(path)
if err != nil {
return err
}
oldNameservers := getNameservers(contents)
newNameserver := make([]string, 0, len(oldNameservers))
for _, ns := range oldNameservers {
if !slices.Contains(nameservers, ns) {
newNameserver = append(newNameserver, ns)
}
}
return build(path, newNameserver, getSearchDomains(contents), getOptions(contents))
}
|