diff options
author | Paul Holzinger <pholzing@redhat.com> | 2022-05-12 14:34:37 +0200 |
---|---|---|
committer | Paul Holzinger <pholzing@redhat.com> | 2022-06-07 15:17:04 +0200 |
commit | 90d80cf81e21cc0ff47829d78e4d44f8e0028a6c (patch) | |
tree | ba13633162fd728de417a69feeff5ff103cbe6bb | |
parent | ddf1d2cb38aee698cc176f4ab291b22c6a47644d (diff) | |
download | podman-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>
-rw-r--r-- | libpod/container_internal.go | 23 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 163 | ||||
-rw-r--r-- | libpod/networking_linux.go | 30 | ||||
-rw-r--r-- | pkg/resolvconf/dns/resolvconf.go | 28 | ||||
-rw-r--r-- | vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go | 182 | ||||
-rw-r--r-- | vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go (renamed from pkg/resolvconf/resolvconf.go) | 158 | ||||
-rw-r--r-- | vendor/modules.txt | 1 |
7 files changed, 266 insertions, 319 deletions
diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 7494eb3ec..bbf8c831c 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -17,6 +17,7 @@ import ( "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/pkg/cgroups" "github.com/containers/common/pkg/chown" "github.com/containers/common/pkg/config" @@ -986,7 +987,7 @@ func (c *Container) checkDependenciesRunning() ([]string, error) { } func (c *Container) completeNetworkSetup() error { - var outResolvConf []string + var nameservers []string netDisabled, err := c.NetworkDisabled() if err != nil { return err @@ -1004,7 +1005,7 @@ func (c *Container) completeNetworkSetup() error { // collect any dns servers that cni tells us to use (dnsname) for _, status := range c.getNetworkStatus() { for _, server := range status.DNSServerIPs { - outResolvConf = append(outResolvConf, fmt.Sprintf("nameserver %s", server)) + nameservers = append(nameservers, server.String()) } } // check if we have a bindmount for /etc/hosts @@ -1020,24 +1021,12 @@ func (c *Container) completeNetworkSetup() error { } // check if we have a bindmount for resolv.conf - resolvBindMount := state.BindMounts["/etc/resolv.conf"] - if len(outResolvConf) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 { + resolvBindMount := state.BindMounts[resolvconf.DefaultResolvConf] + if len(nameservers) < 1 || resolvBindMount == "" || len(c.config.NetNsCtr) > 0 { return nil } - // read the existing resolv.conf - b, err := ioutil.ReadFile(resolvBindMount) - if err != nil { - return err - } - for _, line := range strings.Split(string(b), "\n") { - // only keep things that don't start with nameserver from the old - // resolv.conf file - if !strings.HasPrefix(line, "nameserver") { - outResolvConf = append([]string{line}, outResolvConf...) - } - } // write and return - return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644) + return resolvconf.Add(resolvBindMount, nameservers) } // Initialize a container, creating it in the runtime diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 0056b8e86..3602d06ce 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -9,7 +9,6 @@ import ( "io" "io/ioutil" "math" - "net" "os" "os/user" "path" @@ -29,6 +28,7 @@ import ( "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/cgroups" @@ -44,7 +44,6 @@ import ( "github.com/containers/podman/v4/pkg/checkpoint/crutils" "github.com/containers/podman/v4/pkg/criu" "github.com/containers/podman/v4/pkg/lookup" - "github.com/containers/podman/v4/pkg/resolvconf" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" "github.com/containers/podman/v4/utils" @@ -2308,49 +2307,10 @@ rootless=%d // generateResolvConf generates a containers resolv.conf func (c *Container) generateResolvConf() error { var ( - nameservers []string networkNameServers []string networkSearchDomains []string ) - hostns := true - resolvConf := "/etc/resolv.conf" - for _, namespace := range c.config.Spec.Linux.Namespaces { - if namespace.Type == spec.NetworkNamespace { - hostns = false - if namespace.Path != "" && !strings.HasPrefix(namespace.Path, "/proc/") { - definedPath := filepath.Join("/etc/netns", filepath.Base(namespace.Path), "resolv.conf") - _, err := os.Stat(definedPath) - if err == nil { - resolvConf = definedPath - } else if !os.IsNotExist(err) { - return err - } - } - break - } - } - - contents, err := ioutil.ReadFile(resolvConf) - // resolv.conf doesn't have to exists - if err != nil && !os.IsNotExist(err) { - return err - } - - ns := resolvconf.GetNameservers(contents) - // check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver - if !hostns && len(ns) == 1 && ns[0] == "127.0.0.53" { - // read the actual resolv.conf file for systemd-resolved - resolvedContents, err := ioutil.ReadFile("/run/systemd/resolve/resolv.conf") - if err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf") - } - } else { - contents = resolvedContents - } - } - netStatus := c.getNetworkStatus() for _, status := range netStatus { if status.DNSServerIPs != nil { @@ -2370,34 +2330,18 @@ func (c *Container) generateResolvConf() error { return err } - // Ensure that the container's /etc/resolv.conf is compatible with its - // network configuration. - resolv, err := resolvconf.FilterResolvDNS(contents, ipv6, !hostns) - if err != nil { - return errors.Wrapf(err, "error parsing host resolv.conf") + nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer)) + nameservers = append(nameservers, c.runtime.config.Containers.DNSServers...) + for _, ip := range c.config.DNSServer { + nameservers = append(nameservers, ip.String()) } - - dns := make([]net.IP, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer)) - for _, i := range c.runtime.config.Containers.DNSServers { - result := net.ParseIP(i) - if result == nil { - return errors.Wrapf(define.ErrInvalidArg, "invalid IP address %s", i) - } - dns = append(dns, result) - } - dns = append(dns, c.config.DNSServer...) // If the user provided dns, it trumps all; then dns masq; then resolv.conf var search []string - switch { - case len(dns) > 0: - // We store DNS servers as net.IP, so need to convert to string - for _, server := range dns { - nameservers = append(nameservers, server.String()) - } - default: - // Make a new resolv.conf + keepHostServers := false + if len(nameservers) == 0 { + keepHostServers = true // first add the nameservers from the networks status - nameservers = append(nameservers, networkNameServers...) + nameservers = networkNameServers // when we add network dns server we also have to add the search domains search = networkSearchDomains // slirp4netns has a built in DNS forwarder. @@ -2409,38 +2353,34 @@ func (c *Container) generateResolvConf() error { nameservers = append(nameservers, slirp4netnsDNS.String()) } } - nameservers = append(nameservers, resolvconf.GetNameservers(resolv.Content)...) } if len(c.config.DNSSearch) > 0 || len(c.runtime.config.Containers.DNSSearches) > 0 { - if !cutil.StringInSlice(".", c.config.DNSSearch) { - search = append(search, c.runtime.config.Containers.DNSSearches...) - search = append(search, c.config.DNSSearch...) - } - } else { - search = append(search, resolvconf.GetSearchDomains(resolv.Content)...) + customSearch := make([]string, 0, len(c.config.DNSSearch)+len(c.runtime.config.Containers.DNSSearches)) + customSearch = append(customSearch, c.runtime.config.Containers.DNSSearches...) + customSearch = append(customSearch, c.config.DNSSearch...) + search = customSearch } - var options []string - if len(c.config.DNSOption) > 0 || len(c.runtime.config.Containers.DNSOptions) > 0 { - options = c.runtime.config.Containers.DNSOptions - options = append(options, c.config.DNSOption...) - } else { - options = resolvconf.GetOptions(resolv.Content) - } + options := make([]string, 0, len(c.config.DNSOption)+len(c.runtime.config.Containers.DNSOptions)) + options = append(options, c.runtime.config.Containers.DNSOptions...) + options = append(options, c.config.DNSOption...) destPath := filepath.Join(c.state.RunDir, "resolv.conf") - if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "container %s", c.ID()) - } - - // Build resolv.conf - if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil { + if err := resolvconf.New(&resolvconf.Params{ + IPv6Enabled: ipv6, + KeepHostServers: keepHostServers, + Nameservers: nameservers, + Namespaces: c.config.Spec.Linux.Namespaces, + Options: options, + Path: destPath, + Searches: search, + }); err != nil { return errors.Wrapf(err, "error building resolv.conf for container %s", c.ID()) } - return c.bindMountRootFile(destPath, "/etc/resolv.conf") + return c.bindMountRootFile(destPath, resolvconf.DefaultResolvConf) } // Check if a container uses IPv6. @@ -2481,31 +2421,13 @@ func (c *Container) addNameserver(ips []string) error { } // Do we have a resolv.conf at all? - path, ok := c.state.BindMounts["/etc/resolv.conf"] + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] if !ok { return nil } - // Read in full contents, parse out existing nameservers - contents, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ns := resolvconf.GetNameservers(contents) - options := resolvconf.GetOptions(contents) - search := resolvconf.GetSearchDomains(contents) - - // We could verify that it doesn't already exist - // but extra nameservers shouldn't harm anything. - // Ensure we are the first entry in resolv.conf though, otherwise we - // might be after user-added servers. - ns = append(ips, ns...) - - // We're rewriting the container's resolv.conf as part of this, but we - // hold the container lock, so there should be no risk of parallel - // modification. - if _, err := resolvconf.Build(path, ns, search, options); err != nil { - return errors.Wrapf(err, "error adding new nameserver to container %s resolv.conf", c.ID()) + if err := resolvconf.Add(path, ips); err != nil { + return fmt.Errorf("adding new nameserver to container %s resolv.conf: %w", c.ID(), err) } return nil @@ -2520,34 +2442,13 @@ func (c *Container) removeNameserver(ips []string) error { } // Do we have a resolv.conf at all? - path, ok := c.state.BindMounts["/etc/resolv.conf"] + path, ok := c.state.BindMounts[resolvconf.DefaultResolvConf] if !ok { return nil } - // Read in full contents, parse out existing nameservers - contents, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ns := resolvconf.GetNameservers(contents) - options := resolvconf.GetOptions(contents) - search := resolvconf.GetSearchDomains(contents) - - toRemove := make(map[string]bool) - for _, ip := range ips { - toRemove[ip] = true - } - - newNS := make([]string, 0, len(ns)) - for _, server := range ns { - if !toRemove[server] { - newNS = append(newNS, server) - } - } - - if _, err := resolvconf.Build(path, newNS, search, options); err != nil { - return errors.Wrapf(err, "error removing nameservers from container %s resolv.conf", c.ID()) + if err := resolvconf.Remove(path, ips); err != nil { + return fmt.Errorf("removing nameservers from container %s resolv.conf: %w", c.ID(), err) } return nil diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 37fa9b5f5..ee80b00fe 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -21,6 +21,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/common/libnetwork/etchosts" + "github.com/containers/common/libnetwork/resolvconf" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/machine" @@ -30,11 +31,10 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/namespaces" - "github.com/containers/podman/v4/pkg/resolvconf" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/lockfile" - spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -526,23 +526,19 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) { return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String()) } } - conf, err := resolvconf.Get() - if err != nil { - return nil, err - } - conf, err = resolvconf.FilterResolvDNS(conf.Content, netOptions.enableIPv6, true) - if err != nil { - return nil, err - } - searchDomains := resolvconf.GetSearchDomains(conf.Content) - dnsOptions := resolvconf.GetOptions(conf.Content) - nameServers := resolvconf.GetNameservers(conf.Content) - _, err = resolvconf.Build(filepath.Join(rootlessNetNsDir, "resolv.conf"), append([]string{resolveIP.String()}, nameServers...), searchDomains, dnsOptions) - if err != nil { + if err := resolvconf.New(&resolvconf.Params{ + Path: filepath.Join(rootlessNetNsDir, "resolv.conf"), + // fake the netns since we want to filter localhost + Namespaces: []specs.LinuxNamespace{ + {Type: specs.NetworkNamespace}, + }, + IPv6Enabled: netOptions.enableIPv6, + KeepHostServers: true, + Nameservers: []string{resolveIP.String()}, + }); err != nil { return nil, errors.Wrap(err, "failed to create rootless netns resolv.conf") } - // create cni directories to store files // they will be bind mounted to the correct location in a extra mount ns err = os.MkdirAll(filepath.Join(rootlessNetNsDir, persistentCNIDir), 0700) @@ -1089,7 +1085,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e func (c *Container) joinedNetworkNSPath() string { for _, namespace := range c.config.Spec.Linux.Namespaces { - if namespace.Type == spec.NetworkNamespace { + if namespace.Type == specs.NetworkNamespace { return namespace.Path } } diff --git a/pkg/resolvconf/dns/resolvconf.go b/pkg/resolvconf/dns/resolvconf.go deleted file mode 100644 index cb4bd1033..000000000 --- a/pkg/resolvconf/dns/resolvconf.go +++ /dev/null @@ -1,28 +0,0 @@ -// Originally from github.com/docker/libnetwork/resolvconf/dns - -package dns - -import ( - "regexp" -) - -// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range. -const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)` - -// IPv4Localhost is a regex pattern for IPv4 localhost address range. -const IPv4Localhost = `(127\.([0-9]{1,3}\.){2}[0-9]{1,3})` - -var localhostIPRegexp = regexp.MustCompile(IPLocalhost) -var localhostIPv4Regexp = regexp.MustCompile(IPv4Localhost) - -// IsLocalhost returns true if ip matches the localhost IP regular expression. -// Used for determining if nameserver settings are being passed which are -// localhost addresses -func IsLocalhost(ip string) bool { - return localhostIPRegexp.MatchString(ip) -} - -// IsIPv4Localhost returns true if ip matches the IPv4 localhost regular expression. -func IsIPv4Localhost(ip string) bool { - return localhostIPv4Regexp.MatchString(ip) -} 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/pkg/resolvconf/resolvconf.go b/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go index f23cd61b0..54b8c3227 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go @@ -1,26 +1,23 @@ // Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf. -// Originally from github.com/docker/libnetwork/resolvconf. +// Originally from github.com/docker/libnetwork/resolvconf but heavily modified to better work with podman. package resolvconf import ( "bytes" - "io/ioutil" + "os" "regexp" "strings" - "sync" - "github.com/containers/podman/v4/pkg/resolvconf/dns" - "github.com/containers/storage/pkg/ioutils" "github.com/sirupsen/logrus" ) const ( - // DefaultResolvConf points to the default file used for dns configuration on a linux machine + // 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 + // 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]?)` @@ -29,94 +26,30 @@ var ( // 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 + // 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+)?` - localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IPLocalhost + `\s*\n*`) + // 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*)*)$`) ) -var lastModified struct { - sync.Mutex - sha256 string - contents []byte -} - -// File contains the resolv.conf content and its hash -type File struct { - Content []byte - Hash string -} - -// Get returns the contents of /etc/resolv.conf and its hash -func Get() (*File, error) { - return GetSpecific(DefaultResolvConf) -} - -// GetSpecific returns the contents of the user specified resolv.conf file and its hash -func GetSpecific(path string) (*File, error) { - resolv, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - hash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, err - } - return &File{Content: resolv, Hash: hash}, nil -} - -// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash -// and, if modified since last check, returns the bytes and new hash. -// This feature is used by the resolv.conf updater for containers -func GetIfChanged() (*File, error) { - lastModified.Lock() - defer lastModified.Unlock() - - resolv, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - return nil, err - } - newHash, err := ioutils.HashData(bytes.NewReader(resolv)) - if err != nil { - return nil, err - } - if lastModified.sha256 != newHash { - lastModified.sha256 = newHash - lastModified.contents = resolv - return &File{Content: resolv, Hash: newHash}, nil - } - // nothing changed, so return no data - return nil, nil -} - -// GetLastModified retrieves the last used contents and hash of the host resolv.conf. -// Used by containers updating on restart -func GetLastModified() *File { - lastModified.Lock() - defer lastModified.Unlock() - - return &File{Content: lastModified.contents, Hash: lastModified.sha256} -} - -// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: +// 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) (*File, error) { +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 { - hash, err := ioutils.HashData(bytes.NewReader(resolvConf)) - if err != nil { - return nil, err - } - return &File{Content: resolvConf, Hash: hash}, nil + return resolvConf } cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) // if IPv6 is not enabled, also clean out any IPv6 address nameserver @@ -125,7 +58,7 @@ func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*F } // if the resulting resolvConf has no more nameservers defined, add appropriate // default DNS servers for IPv4 and (optionally) IPv6 - if len(GetNameservers(cleanedResolvConf)) == 0 { + 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 { @@ -134,19 +67,15 @@ func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*F } cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) } - hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf)) - if err != nil { - return nil, err - } - return &File{Content: cleanedResolvConf, Hash: hash}, nil + return cleanedResolvConf } // getLines parses input into lines and strips away comments. -func getLines(input []byte, commentMarker []byte) [][]byte { +func getLines(input []byte) [][]byte { lines := bytes.Split(input, []byte("\n")) var output [][]byte for _, currentLine := range lines { - var commentIndex = bytes.Index(currentLine, commentMarker) + commentIndex := bytes.Index(currentLine, []byte("#")) if commentIndex == -1 { output = append(output, currentLine) } else { @@ -156,10 +85,10 @@ func getLines(input []byte, commentMarker []byte) [][]byte { return output } -// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf -func GetNameservers(resolvConf []byte) []string { +// getNameservers returns nameservers (if any) listed in /etc/resolv.conf. +func getNameservers(resolvConf []byte) []string { nameservers := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { + for _, line := range getLines(resolvConf) { ns := nsRegexp.FindSubmatch(line) if len(ns) > 0 { nameservers = append(nameservers, string(ns[1])) @@ -168,30 +97,12 @@ func GetNameservers(resolvConf []byte) []string { return nameservers } -// GetNameserversAsCIDR returns nameservers (if any) listed in -// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") -// This function's output is intended for net.ParseCIDR -func GetNameserversAsCIDR(resolvConf []byte) []string { - nameservers := []string{} - for _, nameserver := range GetNameservers(resolvConf) { - var address string - // If IPv6, strip zone if present - if strings.Contains(nameserver, ":") { - address = strings.Split(nameserver, "%")[0] + "/128" - } else { - address = nameserver + "/32" - } - nameservers = append(nameservers, address) - } - return nameservers -} - -// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf +// 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 { +func getSearchDomains(resolvConf []byte) []string { domains := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { + for _, line := range getLines(resolvConf) { match := searchRegexp.FindSubmatch(line) if match == nil { continue @@ -201,12 +112,12 @@ func GetSearchDomains(resolvConf []byte) []string { return domains } -// GetOptions returns options (if any) listed in /etc/resolv.conf +// 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 { +func getOptions(resolvConf []byte) []string { options := []string{} - for _, line := range getLines(resolvConf, []byte("#")) { + for _, line := range getLines(resolvConf) { match := optionsRegexp.FindSubmatch(line) if match == nil { continue @@ -216,35 +127,30 @@ func GetOptions(resolvConf []byte) []string { return options } -// Build writes a configuration file to path containing a "nameserver" entry +// 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) (*File, error) { - content := bytes.NewBuffer(nil) +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 nil, err + return err } } } for _, dns := range dns { if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { - return nil, err + return err } } if len(dnsOptions) > 0 { if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" { if _, err := content.WriteString("options " + optsString + "\n"); err != nil { - return nil, err + return err } } } - hash, err := ioutils.HashData(bytes.NewReader(content.Bytes())) - if err != nil { - return nil, err - } - - return &File{Content: content.Bytes(), Hash: hash}, ioutil.WriteFile(path, content.Bytes(), 0644) + return os.WriteFile(path, content.Bytes(), 0o644) } diff --git a/vendor/modules.txt b/vendor/modules.txt index a42943eef..a4ba3043e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -119,6 +119,7 @@ github.com/containers/common/libnetwork/etchosts github.com/containers/common/libnetwork/internal/util github.com/containers/common/libnetwork/netavark github.com/containers/common/libnetwork/network +github.com/containers/common/libnetwork/resolvconf github.com/containers/common/libnetwork/types github.com/containers/common/libnetwork/util github.com/containers/common/pkg/apparmor |