aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2022-05-12 14:34:37 +0200
committerPaul Holzinger <pholzing@redhat.com>2022-06-07 15:17:04 +0200
commit90d80cf81e21cc0ff47829d78e4d44f8e0028a6c (patch)
treeba13633162fd728de417a69feeff5ff103cbe6bb /vendor/github.com
parentddf1d2cb38aee698cc176f4ab291b22c6a47644d (diff)
downloadpodman-90d80cf81e21cc0ff47829d78e4d44f8e0028a6c.tar.gz
podman-90d80cf81e21cc0ff47829d78e4d44f8e0028a6c.tar.bz2
podman-90d80cf81e21cc0ff47829d78e4d44f8e0028a6c.zip
use resolvconf package from c/common/libnetwork
Podman and Buildah should use the same code the generate the resolv.conf file. This mostly moved the podman code into c/common and created a better API for it so buildah can use it as well. [NO NEW TESTS NEEDED] All existing tests should continue to pass. Fixes #13599 (There is no way to test this in CI without breaking the hosts resolv.conf) Signed-off-by: Paul Holzinger <pholzing@redhat.com>
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go182
-rw-r--r--vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go156
2 files changed, 338 insertions, 0 deletions
diff --git a/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go b/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go
new file mode 100644
index 000000000..c451d3b49
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go
@@ -0,0 +1,182 @@
+package resolvconf
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/common/pkg/util"
+ "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 content even when custom Nameserver/Searches/Options
+ // are set. In this case they will be appended to the given values.
+ KeepHostServers bool
+ // Nameservers is a list of nameservers the container should use,
+ // instead of the default ones from the host.
+ Nameservers []string
+ // Searches is a list of dns search domains the container should use,
+ // instead of the default ones from the host.
+ Searches []string
+ // Options is a list of dns options the container should use,
+ // instead of the default ones from the host.
+ 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 := os.Stat(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 util.StringInSlice(".", 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 {
+ 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.KeepHostServers && len(searches) > 0) {
+ searches = append(searches, getSearchDomains(content)...)
+ }
+
+ options := params.Options
+ if len(options) == 0 || params.KeepHostServers {
+ 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 !util.StringInSlice(ns, nameservers) {
+ newNameserver = append(newNameserver, ns)
+ }
+ }
+
+ return build(path, newNameserver, getSearchDomains(contents), getOptions(contents))
+}
diff --git a/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go b/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go
new file mode 100644
index 000000000..54b8c3227
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go
@@ -0,0 +1,156 @@
+// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf.
+// Originally from github.com/docker/libnetwork/resolvconf but heavily modified to better work with podman.
+package resolvconf
+
+import (
+ "bytes"
+ "os"
+ "regexp"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // DefaultResolvConf points to the default file used for dns configuration on a linux machine.
+ DefaultResolvConf = "/etc/resolv.conf"
+)
+
+var (
+ // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS.
+ defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
+ defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
+ ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
+ ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
+ // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
+ // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
+ // -- e.g. other link-local types -- either won't work in containers or are unnecessary.
+ // For readability and sufficiency for Docker purposes this seemed more reasonable than a
+ // 1000+ character regexp with exact and complete IPv6 validation.
+ ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})(%\w+)?`
+
+ // ipLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
+ ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
+
+ localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`)
+ nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
+ nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+ searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+ optionsRegexp = regexp.MustCompile(`^\s*options\s*(([^\s]+\s*)*)$`)
+)
+
+// filterResolvDNS cleans up the config in resolvConf. It has two main jobs:
+// 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided
+// resolv.conf, removing local nameserver entries, and, if the resulting
+// cleaned config has no defined nameservers left, adds default DNS entries
+// 2. Given the caller provides the enable/disable state of IPv6, the filter
+// code will remove all IPv6 nameservers if it is not enabled for containers
+//
+func filterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) []byte {
+ // If we're using the host netns, we have nothing to do besides hash the file.
+ if !netnsEnabled {
+ return resolvConf
+ }
+ cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
+ // if IPv6 is not enabled, also clean out any IPv6 address nameserver
+ if !ipv6Enabled {
+ cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
+ }
+ // if the resulting resolvConf has no more nameservers defined, add appropriate
+ // default DNS servers for IPv4 and (optionally) IPv6
+ if len(getNameservers(cleanedResolvConf)) == 0 {
+ logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v", defaultIPv4Dns)
+ dns := defaultIPv4Dns
+ if ipv6Enabled {
+ logrus.Infof("IPv6 enabled; Adding default IPv6 external servers: %v", defaultIPv6Dns)
+ dns = append(dns, defaultIPv6Dns...)
+ }
+ cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
+ }
+ return cleanedResolvConf
+}
+
+// getLines parses input into lines and strips away comments.
+func getLines(input []byte) [][]byte {
+ lines := bytes.Split(input, []byte("\n"))
+ var output [][]byte
+ for _, currentLine := range lines {
+ commentIndex := bytes.Index(currentLine, []byte("#"))
+ if commentIndex == -1 {
+ output = append(output, currentLine)
+ } else {
+ output = append(output, currentLine[:commentIndex])
+ }
+ }
+ return output
+}
+
+// getNameservers returns nameservers (if any) listed in /etc/resolv.conf.
+func getNameservers(resolvConf []byte) []string {
+ nameservers := []string{}
+ for _, line := range getLines(resolvConf) {
+ ns := nsRegexp.FindSubmatch(line)
+ if len(ns) > 0 {
+ nameservers = append(nameservers, string(ns[1]))
+ }
+ }
+ return nameservers
+}
+
+// getSearchDomains returns search domains (if any) listed in /etc/resolv.conf
+// If more than one search line is encountered, only the contents of the last
+// one is returned.
+func getSearchDomains(resolvConf []byte) []string {
+ domains := []string{}
+ for _, line := range getLines(resolvConf) {
+ match := searchRegexp.FindSubmatch(line)
+ if match == nil {
+ continue
+ }
+ domains = strings.Fields(string(match[1]))
+ }
+ return domains
+}
+
+// getOptions returns options (if any) listed in /etc/resolv.conf
+// If more than one options line is encountered, only the contents of the last
+// one is returned.
+func getOptions(resolvConf []byte) []string {
+ options := []string{}
+ for _, line := range getLines(resolvConf) {
+ match := optionsRegexp.FindSubmatch(line)
+ if match == nil {
+ continue
+ }
+ options = strings.Fields(string(match[1]))
+ }
+ return options
+}
+
+// build writes a configuration file to path containing a "nameserver" entry
+// for every element in dns, a "search" entry for every element in
+// dnsSearch, and an "options" entry for every element in dnsOptions.
+func build(path string, dns, dnsSearch, dnsOptions []string) error {
+ content := new(bytes.Buffer)
+ if len(dnsSearch) > 0 {
+ if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
+ if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
+ return err
+ }
+ }
+ }
+ for _, dns := range dns {
+ if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
+ return err
+ }
+ }
+ if len(dnsOptions) > 0 {
+ if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" {
+ if _, err := content.WriteString("options " + optsString + "\n"); err != nil {
+ return err
+ }
+ }
+ }
+
+ return os.WriteFile(path, content.Bytes(), 0o644)
+}