Files
podman/vendor/github.com/containers/common/libnetwork/resolvconf/resolv.go
Urvashi Mohnani 7c8c945496 Vendor in latest c/common
Pull in updates made to the filters code for
images. Filters now perform an AND operation
except for th reference filter which does an
OR operation for positive case but an AND operation
for negative cases.

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>
2024-01-25 11:10:41 -05:00

183 lines
5.8 KiB
Go

package resolvconf
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
)
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 slices.Contains(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 !slices.Contains(nameservers, ns) {
newNameserver = append(newNameserver, ns)
}
}
return build(path, newNameserver, getSearchDomains(contents), getOptions(contents))
}