mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
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:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
182
vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go
generated
vendored
Normal file
182
vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go
generated
vendored
Normal 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))
|
||||
}
|
@ -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
1
vendor/modules.txt
vendored
@ -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
|
||||
|
Reference in New Issue
Block a user