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 #6324

Closed
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
2 changes: 1 addition & 1 deletion libpod/container_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ 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.

I think CNI networks are still unsupported with Slirp, so this should probably be removed unless I missed something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately without this, a any param to --net that contains a : character results in an error about rootless not supporting choosing a network CNI

Copy link
Member

Choose a reason for hiding this comment

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

Hmmm. Let me look a bit, but I suspect this is mostly because of how we're parsing at the command line level.

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

Expand Down
111 changes: 109 additions & 2 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,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 @@ -228,6 +241,12 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
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
// -c, --configure Brings up the tap interface
Expand Down Expand Up @@ -291,7 +310,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 @@ -342,7 +365,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 @@ -419,6 +442,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
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=slirp4netns"
slirpType = "slirp4netns"
)

Expand Down Expand Up @@ -385,7 +387,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