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 +++---- 3 files changed, 51 insertions(+), 165 deletions(-) (limited to 'libpod') 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 } } -- cgit v1.2.3-54-g00ecf