Merge pull request #14220 from Luap99/resolvconf

use resolvconf package from c/common/libnetwork
This commit is contained in:
OpenShift Merge Robot 2022-06-07 18:00:34 -04:00 committed by GitHub
commit b4c981893d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 275 additions and 332 deletions

2
go.mod
View file

@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.1.1
github.com/containernetworking/plugins v1.1.1
github.com/containers/buildah v1.26.1-0.20220524184833-5500333c2e06
github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824
github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.21.2-0.20220520105616-e594853d6471
github.com/containers/ocicrypt v1.1.4-0.20220428134531-566b808bdf6f

4
go.sum
View file

@ -339,8 +339,8 @@ github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19
github.com/containers/buildah v1.26.1-0.20220524184833-5500333c2e06 h1:Tx1IfKch/SnsCk1YrdyR4B2AcS1TKLYxbSMXzmQXafU=
github.com/containers/buildah v1.26.1-0.20220524184833-5500333c2e06/go.mod h1:oB0PwsW+rhePNsBimCnEz4YMLx8QxZBjHi/DPnXhUCg=
github.com/containers/common v0.48.1-0.20220519181648-280c6f69fa82/go.mod h1:Ru/JjL1CTHzlxghVMhchzcFUwHLvlIeR5/SUMw8VUOI=
github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824 h1:5gMIUUpIK9DvHrrlj1Tik8GfCh5DEuVqm0JnYHWYUDw=
github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824/go.mod h1:Ru/JjL1CTHzlxghVMhchzcFUwHLvlIeR5/SUMw8VUOI=
github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1 h1:oq9ol4U/HEJfDYCp9aKBFDBaE16Y1RZN0GJ4eIkrJoo=
github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1/go.mod h1:Ru/JjL1CTHzlxghVMhchzcFUwHLvlIeR5/SUMw8VUOI=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.21.2-0.20220511203756-fe4fd4ed8be4/go.mod h1:OsX9sFexyGF0FCNAjfcVFv3IwMqDyLyV/WQY/roLPcE=

View file

@ -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
@ -1007,7 +1008,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
@ -1023,24 +1024,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

View file

@ -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"
@ -2316,49 +2315,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 {
@ -2378,34 +2338,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.
@ -2417,38 +2361,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.
@ -2489,31 +2429,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
@ -2528,34 +2450,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

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -2,7 +2,6 @@ package integration
import (
"os"
"strings"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/storage/pkg/stringid"
@ -94,7 +93,7 @@ var _ = Describe("Podman network connect and disconnect", func() {
exec2 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(Exit(0))
Expect(strings.Contains(exec2.OutputToString(), ns)).To(BeTrue())
Expect(exec2.OutputToString()).To(ContainSubstring(ns))
dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"})
dis.WaitWithDefaultTimeout()
@ -113,7 +112,7 @@ var _ = Describe("Podman network connect and disconnect", func() {
exec3 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"})
exec3.WaitWithDefaultTimeout()
Expect(exec3).Should(Exit(0))
Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeFalse())
Expect(exec3.OutputToString()).ToNot(ContainSubstring(ns))
// make sure stats still works https://github.com/containers/podman/issues/13824
stats := podmanTest.Podman([]string{"stats", "test", "--no-stream"})
@ -211,7 +210,7 @@ var _ = Describe("Podman network connect and disconnect", func() {
exec2 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"})
exec2.WaitWithDefaultTimeout()
Expect(exec2).Should(Exit(0))
Expect(strings.Contains(exec2.OutputToString(), ns)).To(BeFalse())
Expect(exec2.OutputToString()).ToNot(ContainSubstring(ns))
ip := "10.11.100.99"
mac := "44:11:44:11:44:11"
@ -240,7 +239,7 @@ var _ = Describe("Podman network connect and disconnect", func() {
exec3 := podmanTest.Podman([]string{"exec", "-it", "test", "cat", "/etc/resolv.conf"})
exec3.WaitWithDefaultTimeout()
Expect(exec3).Should(Exit(0))
Expect(strings.Contains(exec3.OutputToString(), ns)).To(BeTrue())
Expect(exec3.OutputToString()).To(ContainSubstring(ns))
// make sure stats works https://github.com/containers/podman/issues/13824
stats := podmanTest.Podman([]string{"stats", "test", "--no-stream"})

View file

@ -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{

View file

@ -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
}

View file

@ -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))
}

View file

@ -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)
}

3
vendor/modules.txt vendored
View file

@ -109,7 +109,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
# github.com/containers/common v0.48.1-0.20220523155016-2fd37da97824
# github.com/containers/common v0.48.1-0.20220528105338-54c8092c69a1
## explicit
github.com/containers/common/libimage
github.com/containers/common/libimage/define
@ -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