From ddf1d2cb38aee698cc176f4ab291b22c6a47644d Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 12 May 2022 13:23:58 +0200 Subject: update c/common to latest Signed-off-by: Paul Holzinger --- vendor/github.com/containers/common/libimage/inspect.go | 2 +- vendor/github.com/containers/common/libimage/pull.go | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'vendor/github.com') diff --git a/vendor/github.com/containers/common/libimage/inspect.go b/vendor/github.com/containers/common/libimage/inspect.go index 05d60edfc..ae06acd2c 100644 --- a/vendor/github.com/containers/common/libimage/inspect.go +++ b/vendor/github.com/containers/common/libimage/inspect.go @@ -128,7 +128,7 @@ func (i *Image) Inspect(ctx context.Context, options *InspectOptions) (*ImageDat Config: &ociImage.Config, Version: info.DockerVersion, Size: size, - VirtualSize: size, // TODO: they should be different (inherited from Podman) + VirtualSize: size, // NOTE: same as size. Inherited from Docker where it's scheduled for deprecation. Digest: i.Digest(), Labels: info.Labels, RootFS: &RootFS{ diff --git a/vendor/github.com/containers/common/libimage/pull.go b/vendor/github.com/containers/common/libimage/pull.go index 771756160..d204ef1c4 100644 --- a/vendor/github.com/containers/common/libimage/pull.go +++ b/vendor/github.com/containers/common/libimage/pull.go @@ -533,9 +533,6 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str sys := r.systemContextCopy() resolved, err := shortnames.Resolve(sys, imageName) if err != nil { - // TODO: that is a too big of a hammer since we should only - // ignore errors that indicate that there's no alias and no - // USRs. Must be addressed in c/image first. if localImage != nil && pullPolicy == config.PullPolicyNewer { return []string{resolvedImageName}, nil } -- cgit v1.2.3-54-g00ecf From 90d80cf81e21cc0ff47829d78e4d44f8e0028a6c Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 12 May 2022 14:34:37 +0200 Subject: 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 --- libpod/container_internal.go | 23 +- libpod/container_internal_linux.go | 163 +++----------- libpod/networking_linux.go | 30 ++- pkg/resolvconf/dns/resolvconf.go | 28 --- pkg/resolvconf/resolvconf.go | 250 --------------------- .../common/libnetwork/resolvconf/resolv.go | 182 +++++++++++++++ .../common/libnetwork/resolvconf/resolvconf.go | 156 +++++++++++++ vendor/modules.txt | 1 + 8 files changed, 390 insertions(+), 443 deletions(-) delete mode 100644 pkg/resolvconf/dns/resolvconf.go delete mode 100644 pkg/resolvconf/resolvconf.go create mode 100644 vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go create mode 100644 vendor/github.com/containers/common/libnetwork/resolvconf/resolvconf.go (limited to 'vendor/github.com') 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/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go deleted file mode 100644 index f23cd61b0..000000000 --- a/pkg/resolvconf/resolvconf.go +++ /dev/null @@ -1,250 +0,0 @@ -// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf. -// Originally from github.com/docker/libnetwork/resolvconf. -package resolvconf - -import ( - "bytes" - "io/ioutil" - "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 = "/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+)?` - - localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.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: -// 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) { - // 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 - } - 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"))...) - } - hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf)) - if err != nil { - return nil, err - } - return &File{Content: cleanedResolvConf, Hash: hash}, nil -} - -// getLines parses input into lines and strips away comments. -func getLines(input []byte, commentMarker []byte) [][]byte { - lines := bytes.Split(input, []byte("\n")) - var output [][]byte - for _, currentLine := range lines { - var commentIndex = bytes.Index(currentLine, commentMarker) - 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, []byte("#")) { - ns := nsRegexp.FindSubmatch(line) - if len(ns) > 0 { - nameservers = append(nameservers, string(ns[1])) - } - } - 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 -// 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, []byte("#")) { - 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, []byte("#")) { - 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) (*File, error) { - content := bytes.NewBuffer(nil) - if len(dnsSearch) > 0 { - if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { - if _, err := content.WriteString("search " + searchString + "\n"); err != nil { - return nil, err - } - } - } - for _, dns := range dns { - if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { - return nil, 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 - } - } - } - - 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) -} 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) +} 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 -- cgit v1.2.3-54-g00ecf