github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/port/builtin/parent/parent.go (about)

     1  package parent
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/rootless-containers/rootlesskit/v2/pkg/api"
    18  	"github.com/rootless-containers/rootlesskit/v2/pkg/port"
    19  	"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg"
    20  	"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/opaque"
    21  	"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/tcp"
    22  	"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp"
    23  	"github.com/rootless-containers/rootlesskit/v2/pkg/port/portutil"
    24  )
    25  
    26  // NewDriver for builtin driver.
    27  func NewDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) {
    28  	// TODO: consider using socketpair FD instead of socket file
    29  	socketPath := filepath.Join(stateDir, ".bp.sock")
    30  	childReadyPipePath := filepath.Join(stateDir, ".bp-ready.pipe")
    31  	// remove the path just in case the previous rootlesskit instance crashed
    32  	if err := os.RemoveAll(childReadyPipePath); err != nil {
    33  		return nil, fmt.Errorf("cannot remove %s: %w", childReadyPipePath, err)
    34  	}
    35  	if err := syscall.Mkfifo(childReadyPipePath, 0600); err != nil {
    36  		return nil, fmt.Errorf("cannot mkfifo %s: %w", childReadyPipePath, err)
    37  	}
    38  	d := driver{
    39  		logWriter:          logWriter,
    40  		socketPath:         socketPath,
    41  		childReadyPipePath: childReadyPipePath,
    42  		ports:              make(map[int]*port.Status, 0),
    43  		stoppers:           make(map[int]func(context.Context) error, 0),
    44  		nextID:             1,
    45  	}
    46  	return &d, nil
    47  }
    48  
    49  type driver struct {
    50  	logWriter          io.Writer
    51  	socketPath         string
    52  	childReadyPipePath string
    53  	mu                 sync.Mutex
    54  	ports              map[int]*port.Status
    55  	stoppers           map[int]func(context.Context) error
    56  	nextID             int
    57  }
    58  
    59  func (d *driver) Info(ctx context.Context) (*api.PortDriverInfo, error) {
    60  	info := &api.PortDriverInfo{
    61  		Driver:                  "builtin",
    62  		Protos:                  []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"},
    63  		DisallowLoopbackChildIP: false,
    64  	}
    65  	return info, nil
    66  }
    67  
    68  func (d *driver) OpaqueForChild() map[string]string {
    69  	return map[string]string{
    70  		opaque.SocketPath:         d.socketPath,
    71  		opaque.ChildReadyPipePath: d.childReadyPipePath,
    72  	}
    73  }
    74  
    75  func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, _ *port.ChildContext) error {
    76  	childReadyPipeR, err := os.OpenFile(d.childReadyPipePath, os.O_RDONLY, os.ModeNamedPipe)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if _, err = io.ReadAll(childReadyPipeR); err != nil {
    81  		return err
    82  	}
    83  	childReadyPipeR.Close()
    84  	var dialer net.Dialer
    85  	conn, err := dialer.Dial("unix", d.socketPath)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	err = msg.Initiate(conn.(*net.UnixConn))
    90  	conn.Close()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	initComplete <- struct{}{}
    95  	<-quit
    96  	return nil
    97  }
    98  
    99  func isEPERM(err error) bool {
   100  	k := "permission denied"
   101  	// As of Go 1.14, errors.Is(err, syscall.EPERM) does not seem to work for
   102  	// "listen tcp 0.0.0.0:80: bind: permission denied" error from net.ListenTCP().
   103  	return errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), k)
   104  }
   105  
   106  // annotateEPERM annotates origErr for human-readability
   107  func annotateEPERM(origErr error, spec port.Spec) error {
   108  	// Read "net.ipv4.ip_unprivileged_port_start" value (typically 1024)
   109  	// TODO: what for IPv6?
   110  	// NOTE: sync.Once should not be used here
   111  	b, e := os.ReadFile("/proc/sys/net/ipv4/ip_unprivileged_port_start")
   112  	if e != nil {
   113  		return origErr
   114  	}
   115  	start, e := strconv.Atoi(strings.TrimSpace(string(b)))
   116  	if e != nil {
   117  		return origErr
   118  	}
   119  	if spec.ParentPort >= start {
   120  		// origErr is unrelated to ip_unprivileged_port_start
   121  		return origErr
   122  	}
   123  	text := fmt.Sprintf("cannot expose privileged port %d, you can add 'net.ipv4.ip_unprivileged_port_start=%d' to /etc/sysctl.conf (currently %d)", spec.ParentPort, spec.ParentPort, start)
   124  	if filepath.Base(os.Args[0]) == "rootlesskit" {
   125  		// NOTE: The following sentence is appended only if Args[0] == "rootlesskit", because it does not apply to Podman (as of Podman v1.9).
   126  		// Podman launches the parent driver in the child user namespace (but in the parent network namespace), which disables the file capability.
   127  		text += ", or set CAP_NET_BIND_SERVICE on rootlesskit binary"
   128  	}
   129  	text += fmt.Sprintf(", or choose a larger port number (>= %d)", start)
   130  	return fmt.Errorf(text+": %w", origErr)
   131  }
   132  
   133  func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
   134  	d.mu.Lock()
   135  	err := portutil.ValidatePortSpec(spec, d.ports)
   136  	d.mu.Unlock()
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	// NOTE: routineStopCh is close-only channel. Do not send any data.
   141  	// See commit 4803f18fae1e39d200d98f09e445a97ccd6f5526 `Revert "port/builtin: RemovePort() block until conn is closed"`
   142  	routineStopCh := make(chan struct{})
   143  	routineStoppedCh := make(chan error)
   144  	routineStop := func(ctx context.Context) error {
   145  		close(routineStopCh)
   146  		select {
   147  		case stoppedResult, stoppedResultOk := <-routineStoppedCh:
   148  			if stoppedResultOk {
   149  				return stoppedResult
   150  			}
   151  			return errors.New("routineStoppedCh was closed without sending data?")
   152  		case <-ctx.Done():
   153  			return fmt.Errorf("timed out while waiting for routineStoppedCh after closing routineStopCh: %w", err)
   154  		}
   155  	}
   156  	switch spec.Proto {
   157  	case "tcp", "tcp4", "tcp6":
   158  		err = tcp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter)
   159  	case "udp", "udp4", "udp6":
   160  		err = udp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter)
   161  	default:
   162  		return nil, fmt.Errorf("unsupported port protocol %s", spec.Proto)
   163  	}
   164  	if err != nil {
   165  		if isEPERM(err) {
   166  			err = annotateEPERM(err, spec)
   167  		}
   168  		return nil, err
   169  	}
   170  	d.mu.Lock()
   171  	id := d.nextID
   172  	st := port.Status{
   173  		ID:   id,
   174  		Spec: spec,
   175  	}
   176  	d.ports[id] = &st
   177  	d.stoppers[id] = routineStop
   178  	d.nextID++
   179  	d.mu.Unlock()
   180  	return &st, nil
   181  }
   182  
   183  func (d *driver) ListPorts(ctx context.Context) ([]port.Status, error) {
   184  	var ports []port.Status
   185  	d.mu.Lock()
   186  	for _, p := range d.ports {
   187  		ports = append(ports, *p)
   188  	}
   189  	d.mu.Unlock()
   190  	return ports, nil
   191  }
   192  
   193  func (d *driver) RemovePort(ctx context.Context, id int) error {
   194  	d.mu.Lock()
   195  	defer d.mu.Unlock()
   196  	stop, ok := d.stoppers[id]
   197  	if !ok {
   198  		return fmt.Errorf("unknown id: %d", id)
   199  	}
   200  	if _, ok := ctx.Deadline(); !ok {
   201  		var cancel context.CancelFunc
   202  		ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
   203  		defer cancel()
   204  	}
   205  	err := stop(ctx)
   206  	delete(d.stoppers, id)
   207  	delete(d.ports, id)
   208  	return err
   209  }