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>
This commit is contained in:
Paul Holzinger
2022-05-12 14:34:37 +02:00
parent ddf1d2cb38
commit 90d80cf81e
7 changed files with 266 additions and 319 deletions

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

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"
@ -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 := 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...)
} else {
options = resolvconf.GetOptions(resolv.Content)
}
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

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

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

1
vendor/modules.txt vendored
View File

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