diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/config/config.go | 64 | ||||
-rw-r--r-- | libpod/config/default.go | 7 | ||||
-rw-r--r-- | libpod/container.go | 3 | ||||
-rw-r--r-- | libpod/image/config.go | 2 | ||||
-rw-r--r-- | libpod/networking_linux.go | 206 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 11 | ||||
-rw-r--r-- | libpod/options.go | 4 | ||||
-rw-r--r-- | libpod/runtime.go | 6 |
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 { |