Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow switching of port-forward approaches when rootless using slirp4netns #6025

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cmd/podman/cliconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,6 @@ type UntagValues struct {
func GetDefaultConfig() *config.Config {
var err error
conf, err := config.NewConfig("")
conf.CheckCgroupsAndAdjustConfig()
if err != nil {
logrus.Errorf("Error loading container config %v\n", err)
os.Exit(1)
Expand Down
3 changes: 3 additions & 0 deletions cmd/podman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ func before(cmd *cobra.Command, args []string) error {
return err
}

defaultContainerConfig.Engine.CgroupManager = MainGlobalOpts.CGroupManager
defaultContainerConfig.CheckCgroupsAndAdjustConfig()

if err := setupRootless(cmd, args); err != nil {
return err
}
Expand Down
5 changes: 1 addition & 4 deletions libpod/container_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,9 @@ func (c *Container) validate() error {

// Rootless has some requirements, compared to networks.
if rootless.IsRootless() {
if len(c.config.Networks) > 0 {
if !c.config.NetMode.IsSlirp4netns() && len(c.config.Networks) > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we changing this? CNI networks will never be usable with rootless Podman's current network stack.

Copy link
Contributor Author

@aleks-mariusz aleks-mariusz Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this because otherwise i cannot use the ":" character as a parameter to --net (when using rootless mode), this validation thinks i'm trying to join CNI networks which this validation prohibits when rootless.

If there's a better way to enable this functionality, please let me know.

return errors.Wrapf(define.ErrInvalidArg, "cannot join CNI networks if running rootless")
}

// TODO: Should we make sure network mode is set to Slirp if set
// at all?
}

// Can only set static IP or MAC is creating a network namespace.
Expand Down
110 changes: 108 additions & 2 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ type slirpFeatures struct {
HasEnableSeccomp bool
}

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) (*slirpFeatures, error) {
cmd := exec.Command(path, "--help")
out, err := cmd.CombinedOutput()
Expand Down Expand Up @@ -215,6 +228,11 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
if slirpFeatures.HasEnableSeccomp {
cmdArgs = append(cmdArgs, "--enable-seccomp")
}
var apiSocket string
if havePortMapping && ctr.config.NetMode.IsPortForwardViaSlirpHostFwd() {
apiSocket = filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID))
cmdArgs = append(cmdArgs, "--api-socket", apiSocket)
}

// the slirp4netns arguments being passed are describes as follows:
// from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns
Expand Down Expand Up @@ -279,7 +297,11 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
}

if havePortMapping {
return r.setupRootlessPortMapping(ctr, netnsPath)
if ctr.config.NetMode.IsPortForwardViaSlirpHostFwd() {
return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
} else {
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
}
}
return nil
}
Expand Down Expand Up @@ -330,7 +352,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t
return nil
}

func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (err error) {
func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string) (err error) {
syncR, syncW, err := os.Pipe()
if err != nil {
return errors.Wrapf(err, "failed to open pipe")
Expand Down Expand Up @@ -407,6 +429,90 @@ func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (er
return nil
}

func (r *Runtime) setupRootlessPortMappingViaSlirp(ctr *Container, cmd *exec.Cmd, apiSocket string) (err error) {
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)

// 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)
}

// 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"
}
apiCmd := 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(&apiCmd)
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)
}
}
logrus.Debug("slirp4netns port-forwarding setup via add_hostfwd is ready")
return nil
}

// Configure the network namespace using the container process
func (r *Runtime) setupNetNS(ctr *Container) (err error) {
nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
Expand Down
3 changes: 2 additions & 1 deletion libpod/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ func NewRuntime(ctx context.Context, options ...RuntimeOption) (runtime *Runtime
if err != nil {
return nil, err
}
runtime, err = newRuntimeFromConfig(ctx, conf, options...)
conf.CheckCgroupsAndAdjustConfig()
return newRuntimeFromConfig(ctx, conf, options...)
return runtime, err
}

// NewRuntimeFromConfig creates a new container runtime using the given
Expand Down
28 changes: 27 additions & 1 deletion pkg/namespaces/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const (
nsType = "ns"
podType = "pod"
privateType = "private"
rlkFwdType = "port_handler=rootlesskit"
shareableType = "shareable"
slirpFwdType = "port_handler=slirplisten"
slirpType = "slirp4netns"
)

Expand Down Expand Up @@ -380,7 +382,31 @@ func (n NetworkMode) IsBridge() bool {

// IsSlirp4netns indicates if we are running a rootless network stack
func (n NetworkMode) IsSlirp4netns() bool {
return n == slirpType
return n == slirpType || strings.HasPrefix(string(n), slirpType+":")
}

// IsPortForwardViaRootlessKit indicates if we are doing rootless port-forwarding via rootlesskit/rootlessport
func (n NetworkMode) IsPortForwardViaRootlessKit() bool {
if !n.IsSlirp4netns() {
return false
}
// below here, implied IsSlirp4netns() == true
if !strings.Contains(string(n), ":") {
return true // default type
}
// below here, also contains(":") == true
parts := strings.SplitN(string(n), ":", 2)
return len(parts) == 2 && parts[1] == rlkFwdType
}

// IsPortForwardViaSlirpHostFwd indicates if we are doing rootless port-forwarding via slirp4netns add_hostfwd()
func (n NetworkMode) IsPortForwardViaSlirpHostFwd() bool {
if !n.IsSlirp4netns() {
return false
}
// below here, implied IsSlirp4netns() == true
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[1] == slirpFwdType
}

// IsNS indicates a network namespace passed in by path (ns:<path>)
Expand Down