summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/config/config.go64
-rw-r--r--libpod/config/default.go7
-rw-r--r--libpod/container.go3
-rw-r--r--libpod/image/config.go2
-rw-r--r--libpod/networking_linux.go206
-rw-r--r--libpod/oci_conmon_linux.go11
-rw-r--r--libpod/options.go4
-rw-r--r--libpod/runtime.go6
8 files changed, 161 insertions, 142 deletions
diff --git a/libpod/config/config.go b/libpod/config/config.go
index 6240bccb0..13c128688 100644
--- a/libpod/config/config.go
+++ b/libpod/config/config.go
@@ -72,7 +72,7 @@ const (
// SetOptions contains a subset of options in a Config. It's used to indicate if
// a given option has either been set by the user or by a parsed libpod
// configuration file. If not, the corresponding option might be overwritten by
-// values from the database. This behavior guarantess backwards compat with
+// values from the database. This behavior guarantees backwards compat with
// older version of libpod and Podman.
type SetOptions struct {
// StorageConfigRunRootSet indicates if the RunRoot has been explicitly set
@@ -119,7 +119,7 @@ type Config struct {
// SetOptions contains a subset of config options. It's used to indicate if
// a given option has either been set by the user or by a parsed libpod
// configuration file. If not, the corresponding option might be
- // overwritten by values from the database. This behavior guarantess
+ // overwritten by values from the database. This behavior guarantees
// backwards compat with older version of libpod and Podman.
SetOptions
@@ -451,45 +451,47 @@ func NewConfig(userConfigPath string) (*Config, error) {
}
// Now, check if the user can access system configs and merge them if needed.
- if configs, err := systemConfigs(); err != nil {
+ configs, err := systemConfigs()
+ if err != nil {
return nil, errors.Wrapf(err, "error finding config on system")
- } else {
- migrated := false
- for _, path := range configs {
- systemConfig, err := readConfigFromFile(path)
- if err != nil {
- return nil, errors.Wrapf(err, "error reading system config %q", path)
- }
- // Handle CGroups v2 configuration migration.
- // Migrate only the first config, and do it before
- // merging.
- if !migrated {
- if err := cgroupV2Check(path, systemConfig); err != nil {
- return nil, errors.Wrapf(err, "error rewriting configuration file %s", userConfigPath)
- }
- migrated = true
- }
- // Merge the it into the config. Any unset field in config will be
- // over-written by the systemConfig.
- if err := config.mergeConfig(systemConfig); err != nil {
- return nil, errors.Wrapf(err, "error merging system config")
+ }
+
+ migrated := false
+ for _, path := range configs {
+ systemConfig, err := readConfigFromFile(path)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error reading system config %q", path)
+ }
+ // Handle CGroups v2 configuration migration.
+ // Migrate only the first config, and do it before
+ // merging.
+ if !migrated {
+ if err := cgroupV2Check(path, systemConfig); err != nil {
+ return nil, errors.Wrapf(err, "error rewriting configuration file %s", userConfigPath)
}
- logrus.Debugf("Merged system config %q: %v", path, config)
+ migrated = true
+ }
+ // Merge the it into the config. Any unset field in config will be
+ // over-written by the systemConfig.
+ if err := config.mergeConfig(systemConfig); err != nil {
+ return nil, errors.Wrapf(err, "error merging system config")
}
+ logrus.Debugf("Merged system config %q: %v", path, config)
}
// Finally, create a default config from memory and forcefully merge it into
// the config. This way we try to make sure that all fields are properly set
// and that user AND system config can partially set.
- if defaultConfig, err := defaultConfigFromMemory(); err != nil {
+ defaultConfig, err := defaultConfigFromMemory()
+ if err != nil {
return nil, errors.Wrapf(err, "error generating default config from memory")
- } else {
- // Check if we need to switch to cgroupfs and logger=file on rootless.
- defaultConfig.checkCgroupsAndLogger()
+ }
- if err := config.mergeConfig(defaultConfig); err != nil {
- return nil, errors.Wrapf(err, "error merging default config from memory")
- }
+ // Check if we need to switch to cgroupfs and logger=file on rootless.
+ defaultConfig.checkCgroupsAndLogger()
+
+ if err := config.mergeConfig(defaultConfig); err != nil {
+ return nil, errors.Wrapf(err, "error merging default config from memory")
}
// Relative paths can cause nasty bugs, because core paths we use could
diff --git a/libpod/config/default.go b/libpod/config/default.go
index 5decaeab7..c4a4efdaf 100644
--- a/libpod/config/default.go
+++ b/libpod/config/default.go
@@ -26,11 +26,12 @@ const (
// config is different for root and rootless. It also parses the storage.conf.
func defaultConfigFromMemory() (*Config, error) {
c := new(Config)
- if tmp, err := defaultTmpDir(); err != nil {
+ tmp, err := defaultTmpDir()
+ if err != nil {
return nil, err
- } else {
- c.TmpDir = tmp
}
+ c.TmpDir = tmp
+
c.EventsLogFilePath = filepath.Join(c.TmpDir, "events", "events.log")
storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
diff --git a/libpod/container.go b/libpod/container.go
index 2693190b5..edf72f4ee 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -135,6 +135,9 @@ type Container struct {
rootlessSlirpSyncR *os.File
rootlessSlirpSyncW *os.File
+ rootlessPortSyncR *os.File
+ rootlessPortSyncW *os.File
+
// A restored container should have the same IP address as before
// being checkpointed. If requestedIP is set it will be used instead
// of config.StaticIP.
diff --git a/libpod/image/config.go b/libpod/image/config.go
index 40e7fd496..bb84175a3 100644
--- a/libpod/image/config.go
+++ b/libpod/image/config.go
@@ -2,7 +2,7 @@ package image
// ImageDeleteResponse is the response for removing an image from storage and containers
// what was untagged vs actually removed
-type ImageDeleteResponse struct {
+type ImageDeleteResponse struct { //nolint
Untagged []string `json:"untagged"`
Deleted string `json:"deleted"`
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index a68338dbb..89dac2b5d 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -3,8 +3,10 @@
package libpod
import (
+ "bytes"
"crypto/rand"
"fmt"
+ "io"
"io/ioutil"
"net"
"os"
@@ -20,6 +22,7 @@ import (
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/netns"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/rootlessport"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -151,19 +154,6 @@ func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result,
return ctrNS, networkStatus, err
}
-type slirp4netnsCmdArg struct {
- Proto string `json:"proto,omitempty"`
- HostAddr string `json:"host_addr"`
- HostPort int32 `json:"host_port"`
- GuestAddr string `json:"guest_addr"`
- GuestPort int32 `json:"guest_port"`
-}
-
-type slirp4netnsCmd struct {
- Execute string `json:"execute"`
- Args slirp4netnsCmdArg `json:"arguments"`
-}
-
func checkSlirpFlags(path string) (bool, bool, bool, error) {
cmd := exec.Command(path, "--help")
out, err := cmd.CombinedOutput()
@@ -194,13 +184,9 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
defer errorhandling.CloseQuiet(syncW)
havePortMapping := len(ctr.Config().PortMappings) > 0
- apiSocket := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID))
logPath := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID))
cmdArgs := []string{}
- if havePortMapping {
- cmdArgs = append(cmdArgs, "--api-socket", apiSocket)
- }
dhp, mtu, sandbox, err := checkSlirpFlags(path)
if err != nil {
return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err)
@@ -221,15 +207,19 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
// -e, --exit-fd=FD specify the FD for terminating slirp4netns
// -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished
cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4")
+ netnsPath := ""
if !ctr.config.PostConfigureNetNS {
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
if err != nil {
return errors.Wrapf(err, "failed to create rootless network sync pipe")
}
- cmdArgs = append(cmdArgs, "--netns-type=path", ctr.state.NetNS.Path(), "tap0")
+ netnsPath = ctr.state.NetNS.Path()
+ cmdArgs = append(cmdArgs, "--netns-type=path", netnsPath, "tap0")
} else {
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR)
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW)
+ netnsPath = fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
+ // we don't use --netns-path here (unavailable for slirp4netns < v0.4)
cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0")
}
@@ -269,11 +259,27 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
}
}()
+ if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil {
+ return err
+ }
+
+ if havePortMapping {
+ return r.setupRootlessPortMapping(ctr, netnsPath)
+ }
+ return nil
+}
+
+func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error {
+ prog := filepath.Base(cmd.Path)
+ if len(cmd.Args) > 0 {
+ prog = cmd.Args[0]
+ }
b := make([]byte, 16)
for {
- if err := syncR.SetDeadline(time.Now().Add(1 * time.Second)); err != nil {
- return errors.Wrapf(err, "error setting slirp4netns pipe timeout")
+ if err := syncR.SetDeadline(time.Now().Add(timeout)); err != nil {
+ return errors.Wrapf(err, "error setting %s pipe timeout", prog)
}
+ // FIXME: return err as soon as proc exits, without waiting for timeout
if _, err := syncR.Read(b); err == nil {
break
} else {
@@ -282,7 +288,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
var status syscall.WaitStatus
pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
if err != nil {
- return errors.Wrapf(err, "failed to read slirp4netns process status")
+ return errors.Wrapf(err, "failed to read %s process status", prog)
}
if pid != cmd.Process.Pid {
continue
@@ -294,100 +300,87 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
}
logContent, err := ioutil.ReadAll(logFile)
if err != nil {
- return errors.Wrapf(err, "slirp4netns failed")
+ return errors.Wrapf(err, "%s failed", prog)
}
- return errors.Errorf("slirp4netns failed: %q", logContent)
+ return errors.Errorf("%s failed: %q", prog, logContent)
}
if status.Signaled() {
- return errors.New("slirp4netns killed by signal")
+ return errors.Errorf("%s killed by signal", prog)
}
continue
}
- return errors.Wrapf(err, "failed to read from slirp4netns sync pipe")
+ return errors.Wrapf(err, "failed to read from %s sync pipe", prog)
}
}
+ return nil
+}
- if havePortMapping {
- const pidWaitTimeout = 60 * time.Second
- chWait := make(chan error)
- go func() {
- interval := 25 * time.Millisecond
- for i := time.Duration(0); i < pidWaitTimeout; i += interval {
- // Check if the process is still running.
- var status syscall.WaitStatus
- pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
- if err != nil {
- break
- }
- if pid != cmd.Process.Pid {
- continue
- }
- if status.Exited() || status.Signaled() {
- chWait <- fmt.Errorf("slirp4netns exited with status %d", status.ExitStatus())
- }
- time.Sleep(interval)
- }
- }()
- defer close(chWait)
+func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (err error) {
+ syncR, syncW, err := os.Pipe()
+ if err != nil {
+ return errors.Wrapf(err, "failed to open pipe")
+ }
+ defer errorhandling.CloseQuiet(syncR)
+ defer errorhandling.CloseQuiet(syncW)
- // wait that API socket file appears before trying to use it.
- if _, err := WaitForFile(apiSocket, chWait, pidWaitTimeout); err != nil {
- return errors.Wrapf(err, "waiting for slirp4nets to create the api socket file %s", apiSocket)
- }
+ logPath := filepath.Join(ctr.runtime.config.TmpDir, fmt.Sprintf("rootlessport-%s.log", ctr.config.ID))
+ logFile, err := os.Create(logPath)
+ if err != nil {
+ return errors.Wrapf(err, "failed to open rootlessport log file %s", logPath)
+ }
+ defer logFile.Close()
+ // Unlink immediately the file so we won't need to worry about cleaning it up later.
+ // It is still accessible through the open fd logFile.
+ if err := os.Remove(logPath); err != nil {
+ return errors.Wrapf(err, "delete file %s", logPath)
+ }
- // for each port we want to add we need to open a connection to the slirp4netns control socket
- // and send the add_hostfwd command.
- for _, i := range ctr.config.PortMappings {
- conn, err := net.Dial("unix", apiSocket)
- if err != nil {
- return errors.Wrapf(err, "cannot open connection to %s", apiSocket)
- }
- defer func() {
- if err := conn.Close(); err != nil {
- logrus.Errorf("unable to close connection: %q", err)
- }
- }()
- hostIP := i.HostIP
- if hostIP == "" {
- hostIP = "0.0.0.0"
- }
- cmd := slirp4netnsCmd{
- Execute: "add_hostfwd",
- Args: slirp4netnsCmdArg{
- Proto: i.Protocol,
- HostAddr: hostIP,
- HostPort: i.HostPort,
- GuestPort: i.ContainerPort,
- },
- }
- // create the JSON payload and send it. Mark the end of request shutting down writes
- // to the socket, as requested by slirp4netns.
- data, err := json.Marshal(&cmd)
- if err != nil {
- return errors.Wrapf(err, "cannot marshal JSON for slirp4netns")
- }
- if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil {
- return errors.Wrapf(err, "cannot write to control socket %s", apiSocket)
- }
- if err := conn.(*net.UnixConn).CloseWrite(); err != nil {
- return errors.Wrapf(err, "cannot shutdown the socket %s", apiSocket)
- }
- buf := make([]byte, 2048)
- readLength, err := conn.Read(buf)
- if err != nil {
- return errors.Wrapf(err, "cannot read from control socket %s", apiSocket)
- }
- // if there is no 'error' key in the received JSON data, then the operation was
- // successful.
- var y map[string]interface{}
- if err := json.Unmarshal(buf[0:readLength], &y); err != nil {
- return errors.Wrapf(err, "error parsing error status from slirp4netns")
- }
- if e, found := y["error"]; found {
- return errors.Errorf("error from slirp4netns while setting up port redirection: %v", e)
- }
+ ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe()
+ if err != nil {
+ return errors.Wrapf(err, "failed to create rootless port sync pipe")
+ }
+ cfg := rootlessport.Config{
+ Mappings: ctr.config.PortMappings,
+ NetNSPath: netnsPath,
+ ExitFD: 3,
+ ReadyFD: 4,
+ TmpDir: ctr.runtime.config.TmpDir,
+ }
+ cfgJSON, err := json.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+ cfgR := bytes.NewReader(cfgJSON)
+ var stdout bytes.Buffer
+ cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid()))
+ cmd.Args = []string{rootlessport.ReexecKey}
+ // Leak one end of the pipe in rootlessport process, the other will be sent to conmon
+ cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncR, syncW)
+ cmd.Stdin = cfgR
+ // stdout is for human-readable error, stderr is for debug log
+ cmd.Stdout = &stdout
+ cmd.Stderr = io.MultiWriter(logFile, &logrusDebugWriter{"rootlessport: "})
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setpgid: true,
+ }
+ if err := cmd.Start(); err != nil {
+ return errors.Wrapf(err, "failed to start rootlessport process")
+ }
+ defer func() {
+ if err := cmd.Process.Release(); err != nil {
+ logrus.Errorf("unable to release rootlessport process: %q", err)
}
+ }()
+ if err := waitForSync(syncR, cmd, logFile, 3*time.Second); err != nil {
+ stdoutStr := stdout.String()
+ if stdoutStr != "" {
+ // err contains full debug log and too verbose, so return stdoutStr
+ logrus.Debug(err)
+ return errors.Errorf("failed to expose ports via rootlessport: %q", stdoutStr)
+ }
+ return err
}
+ logrus.Debug("rootlessport is ready")
return nil
}
@@ -587,3 +580,12 @@ func (c *Container) getContainerNetworkInfo(data *InspectContainerData) *Inspect
}
return data
}
+
+type logrusDebugWriter struct {
+ prefix string
+}
+
+func (w *logrusDebugWriter) Write(p []byte) (int, error) {
+ logrus.Debugf("%s%s", w.prefix, string(p))
+ return len(p), nil
+}
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index 78c8f4126..b09dc3a4f 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -712,7 +712,7 @@ func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (b
return true, nil
}
-// ExecCleanupContainer cleans up files created when a command is run via
+// ExecContainerCleanup cleans up files created when a command is run via
// ExecContainer. This includes the attach socket for the exec session.
func (r *ConmonOCIRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error {
// Clean up the sockets dir. Issue #3962
@@ -1003,6 +1003,15 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
}
// Leak one end in conmon, the other one will be leaked into slirp4netns
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
+
+ if ctr.rootlessPortSyncR != nil {
+ defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR)
+ }
+ if ctr.rootlessPortSyncW != nil {
+ defer errorhandling.CloseQuiet(ctr.rootlessPortSyncW)
+ // Leak one end in conmon, the other one will be leaked into rootlessport
+ cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncW)
+ }
}
err = startCommandGivenSelinux(cmd)
diff --git a/libpod/options.go b/libpod/options.go
index ebde4eecc..031f4f705 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -20,7 +20,9 @@ import (
)
var (
- NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
+ // NameRegex is a regular expression to validate container/pod names.
+ NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
+ // RegexError is thrown in presence of an invalid container/pod name.
RegexError = errors.Wrapf(define.ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*")
)
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 001d850b0..b4cbde28e 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -213,11 +213,11 @@ func getLockManager(runtime *Runtime) (lock.Manager, error) {
// Sets up containers/storage, state store, OCI runtime
func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
// Find a working conmon binary
- if cPath, err := runtime.config.FindConmon(); err != nil {
+ cPath, err := runtime.config.FindConmon()
+ if err != nil {
return err
- } else {
- runtime.conmonPath = cPath
}
+ runtime.conmonPath = cPath
// Make the static files directory if it does not exist
if err := os.MkdirAll(runtime.config.StaticDir, 0700); err != nil {