Skip to content

Commit 4b87cfe

Browse files
committed
rootful: reserve the ports on the host
When running in rootful mode, reserve the ports on the host so that the ports appears on /proc/net/tcp. This also prevents other processes from binding to the same ports. Note that in rootless mode this is not necessary because RootlessKit's port driver already reserves the ports. See lima-vm/lima issue 4085 Similar patterns are used in Docker and Podman. - moby/moby PR 48132 - containers/podman PR 23446 Signed-off-by: Akihiro Suda <[email protected]>
1 parent 645e11a commit 4b87cfe

File tree

1 file changed

+116
-1
lines changed

1 file changed

+116
-1
lines changed

pkg/ocihook/ocihook.go

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os"
2727
"os/exec"
2828
"path/filepath"
29+
"strconv"
2930
"strings"
3031
"time"
3132

@@ -420,11 +421,94 @@ func getIP6AddressOpts(opts *handlerOpts) ([]cni.NamespaceOpts, error) {
420421
return nil, nil
421422
}
422423

424+
func reserveSocket(protocol, hostAddr string) (*os.File, error) {
425+
type filer interface {
426+
File() (*os.File, error)
427+
}
428+
var f filer
429+
switch {
430+
case strings.HasPrefix(protocol, "tcp"):
431+
l, err := net.Listen(protocol, hostAddr)
432+
if err != nil {
433+
return nil, err
434+
}
435+
defer l.Close()
436+
var ok bool
437+
f, ok = l.(filer)
438+
if !ok {
439+
return nil, fmt.Errorf("cannot get file descriptor from the listener of type %T", l)
440+
}
441+
case strings.HasPrefix(protocol, "udp"):
442+
l, err := net.ListenPacket(protocol, hostAddr)
443+
if err != nil {
444+
return nil, err
445+
}
446+
defer l.Close()
447+
var ok bool
448+
f, ok = l.(filer)
449+
if !ok {
450+
return nil, fmt.Errorf("cannot get file descriptor from the listener of type %T", l)
451+
}
452+
default:
453+
return nil, fmt.Errorf("unsupported protocol %q", protocol)
454+
}
455+
return f.File()
456+
}
457+
458+
// portReserverPidFilePath returns /run/nerdctl/<namespace>/<id>/port-reserver.pid
459+
func portReserverPidFilePath(opts *handlerOpts) string {
460+
return filepath.Join("/run/nerdctl/", opts.state.Annotations[labels.Namespace], opts.state.ID, "port-reserver.pid")
461+
}
462+
423463
func applyNetworkSettings(opts *handlerOpts) (err error) {
424464
portMapOpts, err := getPortMapOpts(opts)
425465
if err != nil {
426466
return err
427467
}
468+
if !rootlessutil.IsRootlessChild() && len(opts.ports) > 0 {
469+
// When running in rootful mode, reserve the ports on the host
470+
// so that the ports appears on /proc/net/tcp.
471+
//
472+
// This also prevents other processes from binding to the same ports.
473+
//
474+
// Note that in rootless mode this is not necessary because
475+
// RootlessKit's port driver already reserves the ports.
476+
//
477+
// See https://github.com/lima-vm/lima/issues/4085
478+
//
479+
// Similar patterns are used in Docker and Podman.
480+
// - https://github.com/moby/moby/pull/48132
481+
// - https://github.com/containers/podman/pull/23446
482+
reserverCmd := exec.Command("sleep", "infinity")
483+
for _, p := range opts.ports {
484+
protocol := p.Protocol
485+
if !strings.HasSuffix(protocol, "4") && !strings.HasSuffix(protocol, "6") {
486+
// e.g. "tcp" -> "tcp4"
487+
protocol += "4"
488+
}
489+
hostAddr := net.JoinHostPort(p.HostIP, strconv.Itoa(int(p.HostPort)))
490+
f, err := reserveSocket(protocol, hostAddr)
491+
if err != nil {
492+
log.L.WithError(err).Warnf("cannot reserve the port %s/%s", hostAddr, protocol)
493+
continue
494+
}
495+
reserverCmd.ExtraFiles = append(reserverCmd.ExtraFiles, f)
496+
}
497+
if err := reserverCmd.Start(); err != nil {
498+
return fmt.Errorf("cannot start the port reserver process: %w", err)
499+
}
500+
reserverCmdPid := reserverCmd.Process.Pid
501+
log.L.Debugf("started the port reserver process (pid=%d)", reserverCmdPid)
502+
defer func() {
503+
if err != nil {
504+
log.L.Debugf("killing the port reserver process (pid=%d)", reserverCmdPid)
505+
_ = reserverCmd.Process.Kill()
506+
}
507+
}()
508+
if err := writePidFile(portReserverPidFilePath(opts), reserverCmdPid); err != nil {
509+
return fmt.Errorf("cannot write the pid file of the port reserver process: %w", err)
510+
}
511+
}
428512
nsPath, err := getNetNSPath(opts.state)
429513
if err != nil {
430514
return err
@@ -659,6 +743,11 @@ func onPostStop(opts *handlerOpts) error {
659743
if err := namst.Release(name, opts.state.ID); err != nil && !errors.Is(err, store.ErrNotFound) {
660744
return fmt.Errorf("failed to release container name %s: %w", name, err)
661745
}
746+
// Kill port-reserver process if any
747+
portReserverPidFile := portReserverPidFilePath(opts)
748+
if err = killProcessByPidFile(portReserverPidFile); err != nil {
749+
log.L.WithError(err).Errorf("failed to kill the port-reserver process")
750+
}
662751
return nil
663752
}
664753

@@ -706,7 +795,11 @@ func writePidFile(path string, pid int) error {
706795
if err != nil {
707796
return err
708797
}
709-
tempPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
798+
dir := filepath.Dir(path)
799+
if err := os.MkdirAll(dir, 0755); err != nil {
800+
return err
801+
}
802+
tempPath := filepath.Join(dir, fmt.Sprintf(".%s", filepath.Base(path)))
710803
f, err := os.OpenFile(tempPath, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
711804
if err != nil {
712805
return err
@@ -718,3 +811,25 @@ func writePidFile(path string, pid int) error {
718811
}
719812
return os.Rename(tempPath, path)
720813
}
814+
815+
func killProcessByPidFile(pidFile string) error {
816+
pidData, err := os.ReadFile(pidFile)
817+
if err != nil {
818+
if errors.Is(err, os.ErrNotExist) {
819+
err = nil
820+
}
821+
return err
822+
}
823+
pid, err := strconv.Atoi(strings.TrimSpace(string(pidData)))
824+
if err != nil {
825+
return fmt.Errorf("failed to parse pid %q from %q: %w", string(pidData), pidFile, err)
826+
}
827+
proc, err := os.FindProcess(pid)
828+
if err != nil {
829+
return fmt.Errorf("failed to find process %d: %w", pid, err)
830+
}
831+
if err := proc.Kill(); err != nil {
832+
return fmt.Errorf("failed to kill process %d: %w", pid, err)
833+
}
834+
return nil
835+
}

0 commit comments

Comments
 (0)