summaryrefslogtreecommitdiff
path: root/vendor/github.com
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2022-04-19 13:58:35 +0200
committerPaul Holzinger <pholzing@redhat.com>2022-04-22 12:59:49 +0200
commit696bcd2773cf6c255855db9cf2ef724547626438 (patch)
tree5e2256d4a006c152b12a14f1067b8949f250d656 /vendor/github.com
parent1fcefc94f9e3e6d9dedeccc83fa3964461519b7f (diff)
downloadpodman-696bcd2773cf6c255855db9cf2ef724547626438.tar.gz
podman-696bcd2773cf6c255855db9cf2ef724547626438.tar.bz2
podman-696bcd2773cf6c255855db9cf2ef724547626438.zip
use etchosts package from c/common
Use the new logic from c/common to create the hosts file. This will help to better allign the hosts files between buildah and podman. Also this fixes several bugs: - remove host entries when container is stopped and has a netNsCtr - add entries for containers in a pod - do not duplicate entries in the hosts file - use the correct slirp ip when an userns is used Features: - configure host.containers.internal entry in containers.conf - configure base hosts file in containers.conf Fixes #12003 Fixes #13224 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/containers/common/libnetwork/etchosts/hosts.go339
-rw-r--r--vendor/github.com/containers/common/libnetwork/etchosts/ip.go91
-rw-r--r--vendor/github.com/containers/common/libnetwork/etchosts/util.go30
3 files changed, 460 insertions, 0 deletions
diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go b/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go
new file mode 100644
index 000000000..ce248a181
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go
@@ -0,0 +1,339 @@
+package etchosts
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/common/pkg/util"
+)
+
+const (
+ hostContainersInternal = "host.containers.internal"
+ localhost = "localhost"
+)
+
+type HostEntries []HostEntry
+
+type HostEntry struct {
+ IP string
+ Names []string
+}
+
+// Params for the New() function call
+type Params struct {
+ // BaseFile is the file where we read entries from and add entries to
+ // the target hosts file. If the name is empty it will not read any entries.
+ BaseFile string
+ // ExtraHosts is a slice of entries in the "hostname:ip" format.
+ // Optional.
+ ExtraHosts []string
+ // ContainerIPs should contain the main container ipv4 and ipv6 if available
+ // with the container name and host name as names set.
+ // Optional.
+ ContainerIPs HostEntries
+ // HostContainersInternalIP is the IP for the host.containers.internal entry.
+ // Optional.
+ HostContainersInternalIP string
+ // TargetFile where the hosts are written to.
+ TargetFile string
+}
+
+// New will create a new hosts file and write this to the target file.
+// This function does not prevent any kind of concurrency problems, it is
+// the callers responsibility to avoid concurrent writes to this file.
+// The extraHosts are written first, then the hosts from the file baseFile and the
+// containerIps. The container ip entry is only added when the name was not already
+// added before.
+func New(params *Params) error {
+ if err := new(params); err != nil {
+ return fmt.Errorf("failed to create new hosts file: %w", err)
+ }
+ return nil
+}
+
+// Add adds the given entries to the hosts file, entries are only added if
+// they are not already present.
+// Add is not atomic because it will keep the current file inode. This is
+// required to keep bind mounts for containers working.
+func Add(file string, entries HostEntries) error {
+ if err := add(file, entries); err != nil {
+ return fmt.Errorf("failed to add entries to hosts file: %w", err)
+ }
+ return nil
+}
+
+// AddIfExists will add the given entries only if one of the existsEntries
+// is in the hosts file. This API is required for podman network connect.
+// Since we want to add the same host name for each network ip we want to
+// add duplicates and the normal Add() call prevents us from doing so.
+// However since we also do not want to overwrite potential entries that
+// were added by users manually we first have to check if there are the
+// current expected entries in the file. Note that this will only check
+// for one match not all. It will also only check that the ip and one of
+// the hostnames match like Remove().
+func AddIfExists(file string, existsEntries, newEntries HostEntries) error {
+ if err := addIfExists(file, existsEntries, newEntries); err != nil {
+ return fmt.Errorf("failed to add entries to hosts file: %w", err)
+ }
+ return nil
+}
+
+// Remove will remove the given entries from the file. An entry will be
+// removed when the ip and at least one name matches. Not all names have
+// to match. If the given entries are not present in the file no error is
+// returned.
+// Remove is not atomic because it will keep the current file inode. This is
+// required to keep bind mounts for containers working.
+func Remove(file string, entries HostEntries) error {
+ if err := remove(file, entries); err != nil {
+ return fmt.Errorf("failed to remove entries from hosts file: %w", err)
+ }
+ return nil
+}
+
+// new see comment on New()
+func new(params *Params) error {
+ entries, err := parseExtraHosts(params.ExtraHosts)
+ if err != nil {
+ return err
+ }
+ entries2, err := parseHostsFile(params.BaseFile)
+ if err != nil {
+ return err
+ }
+ entries = append(entries, entries2...)
+
+ // preallocate the slice with enough space for the 3 special entries below
+ containerIPs := make(HostEntries, 0, len(params.ContainerIPs)+3)
+
+ // if localhost was not added we add it
+ // https://github.com/containers/podman/issues/11411
+ lh := []string{localhost}
+ l1 := HostEntry{IP: "127.0.0.1", Names: lh}
+ l2 := HostEntry{IP: "::1", Names: lh}
+ containerIPs = append(containerIPs, l1, l2)
+ if params.HostContainersInternalIP != "" {
+ e := HostEntry{IP: params.HostContainersInternalIP, Names: []string{hostContainersInternal}}
+ containerIPs = append(containerIPs, e)
+ }
+ containerIPs = append(containerIPs, params.ContainerIPs...)
+
+ if err := writeHostFile(params.TargetFile, entries, containerIPs); err != nil {
+ return err
+ }
+ return nil
+}
+
+// add see comment on Add()
+func add(file string, entries HostEntries) error {
+ currentEntries, err := parseHostsFile(file)
+ if err != nil {
+ return err
+ }
+
+ names := make(map[string]struct{})
+ for _, entry := range currentEntries {
+ for _, name := range entry.Names {
+ names[name] = struct{}{}
+ }
+ }
+
+ // open file in append mode since we only add, we do not have to write existing entries again
+ f, err := os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0o644)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ return addEntriesIfNotExists(f, entries, names)
+}
+
+// addIfExists see comment on AddIfExists()
+func addIfExists(file string, existsEntries, newEntries HostEntries) error {
+ // special case when there are no existing entries do a normal add
+ // this can happen when we connect a network which was not connected
+ // to any other networks before
+ if len(existsEntries) == 0 {
+ return add(file, newEntries)
+ }
+
+ currentEntries, err := parseHostsFile(file)
+ if err != nil {
+ return err
+ }
+
+ for _, entry := range currentEntries {
+ if !checkIfEntryExists(entry, existsEntries) {
+ // keep looking for existing entries
+ continue
+ }
+ // if we have a matching existing entry add the new entries
+ // open file in append mode since we only add, we do not have to write existing entries again
+ f, err := os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0o644)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ for _, e := range newEntries {
+ if _, err = f.WriteString(formatLine(e.IP, e.Names)); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ // no match found is no error
+ return nil
+}
+
+// remove see comment on Remove()
+func remove(file string, entries HostEntries) error {
+ currentEntries, err := parseHostsFile(file)
+ if err != nil {
+ return err
+ }
+
+ f, err := os.Create(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ for _, entry := range currentEntries {
+ if checkIfEntryExists(entry, entries) {
+ continue
+ }
+ if _, err = f.WriteString(formatLine(entry.IP, entry.Names)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func checkIfEntryExists(current HostEntry, entries HostEntries) bool {
+ // check if the current entry equals one of the given entries
+ for _, rm := range entries {
+ if current.IP == rm.IP {
+ // it is enough if one of the names match, in this case we remove the full entry
+ for _, name := range current.Names {
+ if util.StringInSlice(name, rm.Names) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+// parseExtraHosts converts a slice of "name:ip" string to entries.
+// Because podman and buildah both store the extra hosts in this format
+// we convert it here instead of having to this on the caller side.
+func parseExtraHosts(extraHosts []string) (HostEntries, error) {
+ entries := make(HostEntries, 0, len(extraHosts))
+ for _, entry := range extraHosts {
+ values := strings.SplitN(entry, ":", 2)
+ if len(values) != 2 {
+ return nil, fmt.Errorf("unable to parse host entry %q: incorrect format", entry)
+ }
+ if values[0] == "" {
+ return nil, fmt.Errorf("hostname in host entry %q is empty", entry)
+ }
+ if values[1] == "" {
+ return nil, fmt.Errorf("IP address in host entry %q is empty", entry)
+ }
+ e := HostEntry{IP: values[1], Names: []string{values[0]}}
+ entries = append(entries, e)
+ }
+ return entries, nil
+}
+
+// parseHostsFile parses a given host file and returns all entries in it.
+// Note that this will remove all comments and spaces.
+func parseHostsFile(file string) (HostEntries, error) {
+ // empty file is valid, in this case we skip adding entries from the file
+ if file == "" {
+ return nil, nil
+ }
+
+ f, err := os.Open(file)
+ if err != nil {
+ // do not error when the default hosts file does not exists
+ // https://github.com/containers/podman/issues/12667
+ if errors.Is(err, os.ErrNotExist) && file == config.DefaultHostsFile {
+ return nil, nil
+ }
+ return nil, err
+ }
+ defer f.Close()
+
+ entries := HostEntries{}
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ // split of the comments
+ line := scanner.Text()
+ if c := strings.IndexByte(line, '#'); c != -1 {
+ line = line[:c]
+ }
+ fields := strings.Fields(line)
+ // if we only have a ip without names we skip it
+ if len(fields) < 2 {
+ continue
+ }
+
+ e := HostEntry{IP: fields[0], Names: fields[1:]}
+ entries = append(entries, e)
+ }
+
+ return entries, scanner.Err()
+}
+
+// writeHostFile write the entries to the given file
+func writeHostFile(file string, userEntries, containerIPs HostEntries) error {
+ f, err := os.Create(file)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ names := make(map[string]struct{})
+ for _, entry := range userEntries {
+ for _, name := range entry.Names {
+ names[name] = struct{}{}
+ }
+ if _, err = f.WriteString(formatLine(entry.IP, entry.Names)); err != nil {
+ return err
+ }
+ }
+
+ return addEntriesIfNotExists(f, containerIPs, names)
+}
+
+// addEntriesIfNotExists only adds the entries for names that are not already
+// in the hosts file, otherwise we start overwriting user entries
+func addEntriesIfNotExists(f io.StringWriter, containerIPs HostEntries, names map[string]struct{}) error {
+ for _, entry := range containerIPs {
+ freeNames := make([]string, 0, len(entry.Names))
+ for _, name := range entry.Names {
+ if _, ok := names[name]; !ok {
+ freeNames = append(freeNames, name)
+ }
+ }
+ if len(freeNames) > 0 {
+ if _, err := f.WriteString(formatLine(entry.IP, freeNames)); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// formatLine converts the given ip and names to a valid hosts line.
+// The returned string includes the newline.
+func formatLine(ip string, names []string) string {
+ return ip + "\t" + strings.Join(names, " ") + "\n"
+}
diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/ip.go b/vendor/github.com/containers/common/libnetwork/etchosts/ip.go
new file mode 100644
index 000000000..3d14b7147
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/etchosts/ip.go
@@ -0,0 +1,91 @@
+package etchosts
+
+import (
+ "net"
+
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/libnetwork/util"
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/storage/pkg/unshare"
+)
+
+// GetHostContainersInternalIP return the host.containers.internal ip
+// if netStatus is not nil then networkInterface also must be non nil otherwise this function panics
+func GetHostContainersInternalIP(conf *config.Config, netStatus map[string]types.StatusBlock, networkInterface types.ContainerNetwork) string {
+ switch conf.Containers.HostContainersInternalIP {
+ case "":
+ // if empty (default) we will automatically choose one below
+ // if machine we let the gvproxy dns server handle the dns name so do not add it
+ if conf.Engine.MachineEnabled {
+ return ""
+ }
+ case "none":
+ return ""
+ default:
+ return conf.Containers.HostContainersInternalIP
+ }
+ ip := ""
+ // Only use the bridge ip when root, as rootless the interfaces are created
+ // inside the special netns and not the host so we cannot use them.
+ if unshare.IsRootless() {
+ return getLocalIP()
+ }
+ for net, status := range netStatus {
+ network, err := networkInterface.NetworkInspect(net)
+ // only add the host entry for bridge networks
+ // ip/macvlan gateway is normally not on the host
+ if err != nil || network.Driver != types.BridgeNetworkDriver {
+ continue
+ }
+ for _, netInt := range status.Interfaces {
+ for _, netAddress := range netInt.Subnets {
+ if netAddress.Gateway != nil {
+ if util.IsIPv4(netAddress.Gateway) {
+ return netAddress.Gateway.String()
+ }
+ // ipv6 address but keep looking since we prefer to use ipv4
+ ip = netAddress.Gateway.String()
+ }
+ }
+ }
+ }
+ if ip != "" {
+ return ip
+ }
+ return getLocalIP()
+}
+
+// getLocalIP returns the non loopback local IP of the host
+func getLocalIP() string {
+ addrs, err := net.InterfaceAddrs()
+ if err != nil {
+ return ""
+ }
+ ip := ""
+ for _, address := range addrs {
+ // check the address type and if it is not a loopback the display it
+ if ipnet, ok := address.(*net.IPNet); ok && ipnet.IP.IsGlobalUnicast() {
+ if util.IsIPv4(ipnet.IP) {
+ return ipnet.IP.String()
+ }
+ // if ipv6 we keep looking for an ipv4 address
+ ip = ipnet.IP.String()
+ }
+ }
+ return ip
+}
+
+// GetNetworkHostEntries returns HostEntries for all ips in the network status
+// with the given hostnames.
+func GetNetworkHostEntries(netStatus map[string]types.StatusBlock, names ...string) HostEntries {
+ hostEntries := make(HostEntries, 0, len(netStatus))
+ for _, status := range netStatus {
+ for _, netInt := range status.Interfaces {
+ for _, netAddress := range netInt.Subnets {
+ e := HostEntry{IP: netAddress.IPNet.IP.String(), Names: names}
+ hostEntries = append(hostEntries, e)
+ }
+ }
+ }
+ return hostEntries
+}
diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/util.go b/vendor/github.com/containers/common/libnetwork/etchosts/util.go
new file mode 100644
index 000000000..d78284594
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/etchosts/util.go
@@ -0,0 +1,30 @@
+package etchosts
+
+import (
+ "fmt"
+
+ "github.com/containers/common/pkg/config"
+ securejoin "github.com/cyphar/filepath-securejoin"
+)
+
+// GetBaseHostFile return the hosts file which should be used as base.
+// The first param should be the config value config.Containers.BaseHostsFile
+// The second param should be the root path to the mounted image. This is
+// required when the user conf value is set to "image".
+func GetBaseHostFile(confValue, imageRoot string) (string, error) {
+ switch confValue {
+ case "":
+ return config.DefaultHostsFile, nil
+ case "none":
+ return "", nil
+ case "image":
+ // use secure join to prevent problems with symlinks
+ path, err := securejoin.SecureJoin(imageRoot, config.DefaultHostsFile)
+ if err != nil {
+ return "", fmt.Errorf("failed to get /etc/hosts path in image: %w", err)
+ }
+ return path, nil
+ default:
+ return confValue, nil
+ }
+}