mirror of
https://github.com/containers/podman.git
synced 2025-11-30 10:07:33 +08:00
update github.com/rootless-containers/rootlesskit to v2
Contains a breaking change but also besides this renovate is not able to update the import paths so this needs to be done by hand. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
14
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/builtin.go
generated
vendored
Normal file
14
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/builtin.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/child"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent"
|
||||
)
|
||||
|
||||
var (
|
||||
NewParentDriver func(logWriter io.Writer, stateDir string) (port.ParentDriver, error) = parent.NewDriver
|
||||
NewChildDriver func(logWriter io.Writer) port.ChildDriver = child.NewDriver
|
||||
)
|
||||
165
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/child/child.go
generated
vendored
Normal file
165
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/child/child.go
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
package child
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/lowlevelmsgutil"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg"
|
||||
opaquepkg "github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/opaque"
|
||||
)
|
||||
|
||||
func NewDriver(logWriter io.Writer) port.ChildDriver {
|
||||
return &childDriver{
|
||||
logWriter: logWriter,
|
||||
}
|
||||
}
|
||||
|
||||
type childDriver struct {
|
||||
logWriter io.Writer
|
||||
}
|
||||
|
||||
func (d *childDriver) RunChildDriver(opaque map[string]string, quit <-chan struct{}, detachedNetNSPath string) error {
|
||||
socketPath := opaque[opaquepkg.SocketPath]
|
||||
if socketPath == "" {
|
||||
return errors.New("socket path not set")
|
||||
}
|
||||
childReadyPipePath := opaque[opaquepkg.ChildReadyPipePath]
|
||||
if childReadyPipePath == "" {
|
||||
return errors.New("child ready pipe path not set")
|
||||
}
|
||||
childReadyPipeW, err := os.OpenFile(childReadyPipePath, os.O_WRONLY, os.ModeNamedPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ln, err := net.ListenUnix("unix", &net.UnixAddr{
|
||||
Name: socketPath,
|
||||
Net: "unix",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// write nothing, just close
|
||||
if err = childReadyPipeW.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
stopAccept := make(chan struct{}, 1)
|
||||
go func() {
|
||||
<-quit
|
||||
stopAccept <- struct{}{}
|
||||
ln.Close()
|
||||
}()
|
||||
for {
|
||||
c, err := ln.AcceptUnix()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-stopAccept:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if rerr := d.routine(c, detachedNetNSPath); rerr != nil {
|
||||
rep := msg.Reply{
|
||||
Error: rerr.Error(),
|
||||
}
|
||||
lowlevelmsgutil.MarshalToWriter(c, &rep)
|
||||
}
|
||||
c.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *childDriver) routine(c *net.UnixConn, detachedNetNSPath string) error {
|
||||
var req msg.Request
|
||||
if _, err := lowlevelmsgutil.UnmarshalFromReader(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
switch req.Type {
|
||||
case msg.RequestTypeInit:
|
||||
return d.handleConnectInit(c, &req)
|
||||
case msg.RequestTypeConnect:
|
||||
if detachedNetNSPath == "" {
|
||||
return d.handleConnectRequest(c, &req)
|
||||
} else {
|
||||
return ns.WithNetNSPath(detachedNetNSPath, func(_ ns.NetNS) error {
|
||||
return d.handleConnectRequest(c, &req)
|
||||
})
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown request type %q", req.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *childDriver) handleConnectInit(c *net.UnixConn, req *msg.Request) error {
|
||||
_, err := lowlevelmsgutil.MarshalToWriter(c, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *childDriver) handleConnectRequest(c *net.UnixConn, req *msg.Request) error {
|
||||
switch req.Proto {
|
||||
case "tcp":
|
||||
case "tcp4":
|
||||
case "tcp6":
|
||||
case "udp":
|
||||
case "udp4":
|
||||
case "udp6":
|
||||
default:
|
||||
return fmt.Errorf("unknown proto: %q", req.Proto)
|
||||
}
|
||||
// dialProto does not need "4", "6" suffix
|
||||
dialProto := strings.TrimSuffix(strings.TrimSuffix(req.Proto, "6"), "4")
|
||||
var dialer net.Dialer
|
||||
ip := req.IP
|
||||
if ip == "" {
|
||||
ip = "127.0.0.1"
|
||||
} else {
|
||||
p := net.ParseIP(ip)
|
||||
if p == nil {
|
||||
return fmt.Errorf("invalid IP: %q", ip)
|
||||
}
|
||||
ip = p.String()
|
||||
}
|
||||
targetConn, err := dialer.Dial(dialProto, net.JoinHostPort(ip, strconv.Itoa(req.Port)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetConn.Close() // no effect on duplicated FD
|
||||
targetConnFiler, ok := targetConn.(filer)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown target connection: %+v", targetConn)
|
||||
}
|
||||
targetConnFile, err := targetConnFiler.File()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetConnFile.Close()
|
||||
oob := unix.UnixRights(int(targetConnFile.Fd()))
|
||||
f, err := c.File()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
for {
|
||||
err = unix.Sendmsg(int(f.Fd()), []byte("dummy"), oob, nil, 0)
|
||||
if err != unix.EINTR {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// filer is implemented by *net.TCPConn and *net.UDPConn
|
||||
type filer interface {
|
||||
File() (f *os.File, err error)
|
||||
}
|
||||
141
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg/msg.go
generated
vendored
Normal file
141
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg/msg.go
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/lowlevelmsgutil"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestTypeInit = "init"
|
||||
RequestTypeConnect = "connect"
|
||||
)
|
||||
|
||||
// Request and Response are encoded as JSON with uint32le length header.
|
||||
type Request struct {
|
||||
Type string // "init" or "connect"
|
||||
Proto string // "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"
|
||||
IP string
|
||||
Port int
|
||||
}
|
||||
|
||||
// Reply may contain FD as OOB
|
||||
type Reply struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
// Initiate sends "init" request to the child UNIX socket.
|
||||
func Initiate(c *net.UnixConn) error {
|
||||
req := Request{
|
||||
Type: RequestTypeInit,
|
||||
}
|
||||
if _, err := lowlevelmsgutil.MarshalToWriter(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.CloseWrite(); err != nil {
|
||||
return err
|
||||
}
|
||||
var rep Reply
|
||||
if _, err := lowlevelmsgutil.UnmarshalFromReader(c, &rep); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.CloseRead()
|
||||
}
|
||||
|
||||
// ConnectToChild connects to the child UNIX socket, and obtains TCP or UDP socket FD
|
||||
// that corresponds to the port spec.
|
||||
func ConnectToChild(c *net.UnixConn, spec port.Spec) (int, error) {
|
||||
req := Request{
|
||||
Type: RequestTypeConnect,
|
||||
Proto: spec.Proto,
|
||||
Port: spec.ChildPort,
|
||||
IP: spec.ChildIP,
|
||||
}
|
||||
if _, err := lowlevelmsgutil.MarshalToWriter(c, &req); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := c.CloseWrite(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
oobSpace := unix.CmsgSpace(4)
|
||||
oob := make([]byte, oobSpace)
|
||||
var (
|
||||
oobN int
|
||||
err error
|
||||
)
|
||||
for {
|
||||
_, oobN, _, _, err = c.ReadMsgUnix(nil, oob)
|
||||
if err != unix.EINTR {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if oobN != oobSpace {
|
||||
return 0, fmt.Errorf("expected OOB space %d, got %d", oobSpace, oobN)
|
||||
}
|
||||
oob = oob[:oobN]
|
||||
fd, err := parseFDFromOOB(oob)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := c.CloseRead(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// ConnectToChildWithSocketPath wraps ConnectToChild
|
||||
func ConnectToChildWithSocketPath(socketPath string, spec port.Spec) (int, error) {
|
||||
var dialer net.Dialer
|
||||
conn, err := dialer.Dial("unix", socketPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
c := conn.(*net.UnixConn)
|
||||
return ConnectToChild(c, spec)
|
||||
}
|
||||
|
||||
// ConnectToChildWithRetry retries ConnectToChild every (i*5) milliseconds.
|
||||
func ConnectToChildWithRetry(socketPath string, spec port.Spec, retries int) (int, error) {
|
||||
for i := 0; i < retries; i++ {
|
||||
fd, err := ConnectToChildWithSocketPath(socketPath, spec)
|
||||
if i == retries-1 && err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err == nil {
|
||||
return fd, err
|
||||
}
|
||||
// TODO: backoff
|
||||
time.Sleep(time.Duration(i*5) * time.Millisecond)
|
||||
}
|
||||
// NOT REACHED
|
||||
return 0, errors.New("reached max retry")
|
||||
}
|
||||
|
||||
func parseFDFromOOB(oob []byte) (int, error) {
|
||||
scms, err := unix.ParseSocketControlMessage(oob)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(scms) != 1 {
|
||||
return 0, fmt.Errorf("unexpected scms: %v", scms)
|
||||
}
|
||||
scm := scms[0]
|
||||
fds, err := unix.ParseUnixRights(&scm)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(fds) != 1 {
|
||||
return 0, fmt.Errorf("unexpected fds: %v", fds)
|
||||
}
|
||||
return fds[0], nil
|
||||
}
|
||||
6
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/opaque/opaque.go
generated
vendored
Normal file
6
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/opaque/opaque.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package opaque
|
||||
|
||||
const (
|
||||
SocketPath = "builtin.socketpath"
|
||||
ChildReadyPipePath = "builtin.readypipepath"
|
||||
)
|
||||
210
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/parent.go
generated
vendored
Normal file
210
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/parent.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
package parent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/api"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/opaque"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/tcp"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/portutil"
|
||||
)
|
||||
|
||||
// NewDriver for builtin driver.
|
||||
func NewDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) {
|
||||
// TODO: consider using socketpair FD instead of socket file
|
||||
socketPath := filepath.Join(stateDir, ".bp.sock")
|
||||
childReadyPipePath := filepath.Join(stateDir, ".bp-ready.pipe")
|
||||
// remove the path just in case the previous rootlesskit instance crashed
|
||||
if err := os.RemoveAll(childReadyPipePath); err != nil {
|
||||
return nil, fmt.Errorf("cannot remove %s: %w", childReadyPipePath, err)
|
||||
}
|
||||
if err := syscall.Mkfifo(childReadyPipePath, 0600); err != nil {
|
||||
return nil, fmt.Errorf("cannot mkfifo %s: %w", childReadyPipePath, err)
|
||||
}
|
||||
d := driver{
|
||||
logWriter: logWriter,
|
||||
socketPath: socketPath,
|
||||
childReadyPipePath: childReadyPipePath,
|
||||
ports: make(map[int]*port.Status, 0),
|
||||
stoppers: make(map[int]func(context.Context) error, 0),
|
||||
nextID: 1,
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
logWriter io.Writer
|
||||
socketPath string
|
||||
childReadyPipePath string
|
||||
mu sync.Mutex
|
||||
ports map[int]*port.Status
|
||||
stoppers map[int]func(context.Context) error
|
||||
nextID int
|
||||
}
|
||||
|
||||
func (d *driver) Info(ctx context.Context) (*api.PortDriverInfo, error) {
|
||||
info := &api.PortDriverInfo{
|
||||
Driver: "builtin",
|
||||
Protos: []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"},
|
||||
DisallowLoopbackChildIP: false,
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (d *driver) OpaqueForChild() map[string]string {
|
||||
return map[string]string{
|
||||
opaque.SocketPath: d.socketPath,
|
||||
opaque.ChildReadyPipePath: d.childReadyPipePath,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, _ *port.ChildContext) error {
|
||||
childReadyPipeR, err := os.OpenFile(d.childReadyPipePath, os.O_RDONLY, os.ModeNamedPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.ReadAll(childReadyPipeR); err != nil {
|
||||
return err
|
||||
}
|
||||
childReadyPipeR.Close()
|
||||
var dialer net.Dialer
|
||||
conn, err := dialer.Dial("unix", d.socketPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = msg.Initiate(conn.(*net.UnixConn))
|
||||
conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
initComplete <- struct{}{}
|
||||
<-quit
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEPERM(err error) bool {
|
||||
k := "permission denied"
|
||||
// As of Go 1.14, errors.Is(err, syscall.EPERM) does not seem to work for
|
||||
// "listen tcp 0.0.0.0:80: bind: permission denied" error from net.ListenTCP().
|
||||
return errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), k)
|
||||
}
|
||||
|
||||
// annotateEPERM annotates origErr for human-readability
|
||||
func annotateEPERM(origErr error, spec port.Spec) error {
|
||||
// Read "net.ipv4.ip_unprivileged_port_start" value (typically 1024)
|
||||
// TODO: what for IPv6?
|
||||
// NOTE: sync.Once should not be used here
|
||||
b, e := os.ReadFile("/proc/sys/net/ipv4/ip_unprivileged_port_start")
|
||||
if e != nil {
|
||||
return origErr
|
||||
}
|
||||
start, e := strconv.Atoi(strings.TrimSpace(string(b)))
|
||||
if e != nil {
|
||||
return origErr
|
||||
}
|
||||
if spec.ParentPort >= start {
|
||||
// origErr is unrelated to ip_unprivileged_port_start
|
||||
return origErr
|
||||
}
|
||||
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)
|
||||
if filepath.Base(os.Args[0]) == "rootlesskit" {
|
||||
// NOTE: The following sentence is appended only if Args[0] == "rootlesskit", because it does not apply to Podman (as of Podman v1.9).
|
||||
// Podman launches the parent driver in the child user namespace (but in the parent network namespace), which disables the file capability.
|
||||
text += ", or set CAP_NET_BIND_SERVICE on rootlesskit binary"
|
||||
}
|
||||
text += fmt.Sprintf(", or choose a larger port number (>= %d)", start)
|
||||
return fmt.Errorf(text+": %w", origErr)
|
||||
}
|
||||
|
||||
func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
|
||||
d.mu.Lock()
|
||||
err := portutil.ValidatePortSpec(spec, d.ports)
|
||||
d.mu.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// NOTE: routineStopCh is close-only channel. Do not send any data.
|
||||
// See commit 4803f18fae1e39d200d98f09e445a97ccd6f5526 `Revert "port/builtin: RemovePort() block until conn is closed"`
|
||||
routineStopCh := make(chan struct{})
|
||||
routineStoppedCh := make(chan error)
|
||||
routineStop := func(ctx context.Context) error {
|
||||
close(routineStopCh)
|
||||
select {
|
||||
case stoppedResult, stoppedResultOk := <-routineStoppedCh:
|
||||
if stoppedResultOk {
|
||||
return stoppedResult
|
||||
}
|
||||
return errors.New("routineStoppedCh was closed without sending data?")
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("timed out while waiting for routineStoppedCh after closing routineStopCh: %w", err)
|
||||
}
|
||||
}
|
||||
switch spec.Proto {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
err = tcp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter)
|
||||
case "udp", "udp4", "udp6":
|
||||
err = udp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter)
|
||||
default:
|
||||
// NOTREACHED
|
||||
return nil, errors.New("spec was not validated?")
|
||||
}
|
||||
if err != nil {
|
||||
if isEPERM(err) {
|
||||
err = annotateEPERM(err, spec)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
d.mu.Lock()
|
||||
id := d.nextID
|
||||
st := port.Status{
|
||||
ID: id,
|
||||
Spec: spec,
|
||||
}
|
||||
d.ports[id] = &st
|
||||
d.stoppers[id] = routineStop
|
||||
d.nextID++
|
||||
d.mu.Unlock()
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
func (d *driver) ListPorts(ctx context.Context) ([]port.Status, error) {
|
||||
var ports []port.Status
|
||||
d.mu.Lock()
|
||||
for _, p := range d.ports {
|
||||
ports = append(ports, *p)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func (d *driver) RemovePort(ctx context.Context, id int) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
stop, ok := d.stoppers[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown id: %d", id)
|
||||
}
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
err := stop(ctx)
|
||||
delete(d.stoppers, id)
|
||||
delete(d.ports, id)
|
||||
return err
|
||||
}
|
||||
108
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/tcp/tcp.go
generated
vendored
Normal file
108
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/tcp/tcp.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg"
|
||||
)
|
||||
|
||||
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, stoppedCh chan error, logWriter io.Writer) error {
|
||||
ln, err := net.Listen(spec.Proto, net.JoinHostPort(spec.ParentIP, strconv.Itoa(spec.ParentPort)))
|
||||
if err != nil {
|
||||
fmt.Fprintf(logWriter, "listen: %v\n", err)
|
||||
return err
|
||||
}
|
||||
newConns := make(chan net.Conn)
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
fmt.Fprintf(logWriter, "accept: %v\n", err)
|
||||
close(newConns)
|
||||
return
|
||||
}
|
||||
newConns <- c
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer func() {
|
||||
stoppedCh <- ln.Close()
|
||||
close(stoppedCh)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case c, ok := <-newConns:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := copyConnToChild(c, socketPath, spec, stopCh); err != nil {
|
||||
fmt.Fprintf(logWriter, "copyConnToChild: %v\n", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
case <-stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
// no wait
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyConnToChild(c net.Conn, socketPath string, spec port.Spec, stopCh <-chan struct{}) error {
|
||||
defer c.Close()
|
||||
// get fd from the child as an SCM_RIGHTS cmsg
|
||||
fd, err := msg.ConnectToChildWithRetry(socketPath, spec, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f := os.NewFile(uintptr(fd), "")
|
||||
defer f.Close()
|
||||
fc, err := net.FileConn(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fc.Close()
|
||||
bicopy(c, fc, stopCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
// bicopy is based on libnetwork/cmd/proxy/tcp_proxy.go .
|
||||
// NOTE: sendfile(2) cannot be used for sockets
|
||||
func bicopy(x, y net.Conn, quit <-chan struct{}) {
|
||||
var wg sync.WaitGroup
|
||||
var broker = func(to, from net.Conn) {
|
||||
io.Copy(to, from)
|
||||
if fromTCP, ok := from.(*net.TCPConn); ok {
|
||||
fromTCP.CloseRead()
|
||||
}
|
||||
if toTCP, ok := to.(*net.TCPConn); ok {
|
||||
toTCP.CloseWrite()
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
wg.Add(2)
|
||||
go broker(x, y)
|
||||
go broker(y, x)
|
||||
finish := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(finish)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-quit:
|
||||
case <-finish:
|
||||
}
|
||||
x.Close()
|
||||
y.Close()
|
||||
<-finish
|
||||
}
|
||||
61
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp/udp.go
generated
vendored
Normal file
61
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp/udp.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg"
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp/udpproxy"
|
||||
)
|
||||
|
||||
func Run(socketPath string, spec port.Spec, stopCh <-chan struct{}, stoppedCh chan error, logWriter io.Writer) error {
|
||||
addr, err := net.ResolveUDPAddr(spec.Proto, net.JoinHostPort(spec.ParentIP, strconv.Itoa(spec.ParentPort)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := net.ListenUDP(spec.Proto, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
udpp := &udpproxy.UDPProxy{
|
||||
LogWriter: logWriter,
|
||||
Listener: c,
|
||||
BackendDial: func() (*net.UDPConn, error) {
|
||||
// get fd from the child as an SCM_RIGHTS cmsg
|
||||
fd, err := msg.ConnectToChildWithRetry(socketPath, spec, 10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := os.NewFile(uintptr(fd), "")
|
||||
defer f.Close()
|
||||
fc, err := net.FileConn(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uc, ok := fc.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file conn doesn't implement *net.UDPConn: %+v", fc)
|
||||
}
|
||||
return uc, nil
|
||||
},
|
||||
}
|
||||
go udpp.Run()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
// udpp.Close closes ln as well
|
||||
udpp.Close()
|
||||
stoppedCh <- nil
|
||||
close(stoppedCh)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
// no wait
|
||||
return nil
|
||||
}
|
||||
150
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp/udpproxy/udp_proxy.go
generated
vendored
Normal file
150
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp/udpproxy/udp_proxy.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Package udpproxy is from https://raw.githubusercontent.com/docker/libnetwork/fec6476dfa21380bf8ee4d74048515d968c1ee63/cmd/proxy/udp_proxy.go
|
||||
package udpproxy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// UDPConnTrackTimeout is the timeout used for UDP connection tracking
|
||||
UDPConnTrackTimeout = 90 * time.Second
|
||||
// UDPBufSize is the buffer size for the UDP proxy
|
||||
UDPBufSize = 65507
|
||||
)
|
||||
|
||||
// A net.Addr where the IP is split into two fields so you can use it as a key
|
||||
// in a map:
|
||||
type connTrackKey struct {
|
||||
IPHigh uint64
|
||||
IPLow uint64
|
||||
Port int
|
||||
}
|
||||
|
||||
func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
|
||||
if len(addr.IP) == net.IPv4len {
|
||||
return &connTrackKey{
|
||||
IPHigh: 0,
|
||||
IPLow: uint64(binary.BigEndian.Uint32(addr.IP)),
|
||||
Port: addr.Port,
|
||||
}
|
||||
}
|
||||
return &connTrackKey{
|
||||
IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
|
||||
IPLow: binary.BigEndian.Uint64(addr.IP[8:]),
|
||||
Port: addr.Port,
|
||||
}
|
||||
}
|
||||
|
||||
type connTrackMap map[connTrackKey]*net.UDPConn
|
||||
|
||||
// UDPProxy is proxy for which handles UDP datagrams.
|
||||
// From libnetwork udp_proxy.go .
|
||||
type UDPProxy struct {
|
||||
LogWriter io.Writer
|
||||
Listener *net.UDPConn
|
||||
BackendDial func() (*net.UDPConn, error)
|
||||
connTrackTable connTrackMap
|
||||
connTrackLock sync.Mutex
|
||||
}
|
||||
|
||||
func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
|
||||
defer func() {
|
||||
proxy.connTrackLock.Lock()
|
||||
delete(proxy.connTrackTable, *clientKey)
|
||||
proxy.connTrackLock.Unlock()
|
||||
proxyConn.Close()
|
||||
}()
|
||||
|
||||
readBuf := make([]byte, UDPBufSize)
|
||||
for {
|
||||
proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
|
||||
again:
|
||||
read, err := proxyConn.Read(readBuf)
|
||||
if err != nil {
|
||||
if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
|
||||
// This will happen if the last write failed
|
||||
// (e.g: nothing is actually listening on the
|
||||
// proxied port on the container), ignore it
|
||||
// and continue until UDPConnTrackTimeout
|
||||
// expires:
|
||||
goto again
|
||||
}
|
||||
return
|
||||
}
|
||||
for i := 0; i != read; {
|
||||
written, err := proxy.Listener.WriteToUDP(readBuf[i:read], clientAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
i += written
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts forwarding the traffic using UDP.
|
||||
func (proxy *UDPProxy) Run() {
|
||||
proxy.connTrackTable = make(connTrackMap)
|
||||
readBuf := make([]byte, UDPBufSize)
|
||||
for {
|
||||
read, from, err := proxy.Listener.ReadFromUDP(readBuf)
|
||||
if err != nil {
|
||||
// NOTE: Apparently ReadFrom doesn't return
|
||||
// ECONNREFUSED like Read do (see comment in
|
||||
// UDPProxy.replyLoop)
|
||||
if !isClosedError(err) {
|
||||
fmt.Fprintf(proxy.LogWriter, "Stopping proxy on udp: %v\n", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
fromKey := newConnTrackKey(from)
|
||||
proxy.connTrackLock.Lock()
|
||||
proxyConn, hit := proxy.connTrackTable[*fromKey]
|
||||
if !hit {
|
||||
proxyConn, err = proxy.BackendDial()
|
||||
if err != nil {
|
||||
fmt.Fprintf(proxy.LogWriter, "Can't proxy a datagram to udp: %v\n", err)
|
||||
proxy.connTrackLock.Unlock()
|
||||
continue
|
||||
}
|
||||
proxy.connTrackTable[*fromKey] = proxyConn
|
||||
go proxy.replyLoop(proxyConn, from, fromKey)
|
||||
}
|
||||
proxy.connTrackLock.Unlock()
|
||||
for i := 0; i != read; {
|
||||
written, err := proxyConn.Write(readBuf[i:read])
|
||||
if err != nil {
|
||||
fmt.Fprintf(proxy.LogWriter, "Can't proxy a datagram to udp: %v\n", err)
|
||||
break
|
||||
}
|
||||
i += written
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops forwarding the traffic.
|
||||
func (proxy *UDPProxy) Close() {
|
||||
proxy.Listener.Close()
|
||||
proxy.connTrackLock.Lock()
|
||||
defer proxy.connTrackLock.Unlock()
|
||||
for _, conn := range proxy.connTrackTable {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func isClosedError(err error) bool {
|
||||
/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
|
||||
* See:
|
||||
* http://golang.org/src/pkg/net/net.go
|
||||
* https://code.google.com/p/go/issues/detail?id=4337
|
||||
* https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
|
||||
*/
|
||||
return strings.HasSuffix(err.Error(), "use of closed network connection")
|
||||
}
|
||||
60
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/port.go
generated
vendored
Normal file
60
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/port.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package port
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/api"
|
||||
)
|
||||
|
||||
type Spec struct {
|
||||
// Proto is one of ["tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"].
|
||||
// "tcp" may cause listening on both IPv4 and IPv6. (Corresponds to Go's net.Listen .)
|
||||
Proto string `json:"proto,omitempty"`
|
||||
ParentIP string `json:"parentIP,omitempty"` // IPv4 or IPv6 address. can be empty (0.0.0.0).
|
||||
ParentPort int `json:"parentPort,omitempty"`
|
||||
ChildPort int `json:"childPort,omitempty"`
|
||||
// ChildIP is an IPv4 or IPv6 address.
|
||||
// Default values:
|
||||
// - builtin driver: 127.0.0.1
|
||||
// - slirp4netns driver: slirp4netns's child IP, e.g., 10.0.2.100
|
||||
ChildIP string `json:"childIP,omitempty"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
ID int `json:"id"`
|
||||
Spec Spec `json:"spec"`
|
||||
}
|
||||
|
||||
// Manager MUST be thread-safe.
|
||||
type Manager interface {
|
||||
AddPort(ctx context.Context, spec Spec) (*Status, error)
|
||||
ListPorts(ctx context.Context) ([]Status, error)
|
||||
RemovePort(ctx context.Context, id int) error
|
||||
}
|
||||
|
||||
// ChildContext is used for RunParentDriver
|
||||
type ChildContext struct {
|
||||
// IP of the tap device
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// ParentDriver is a driver for the parent process.
|
||||
type ParentDriver interface {
|
||||
Manager
|
||||
Info(ctx context.Context) (*api.PortDriverInfo, error)
|
||||
// OpaqueForChild typically consists of socket path
|
||||
// for controlling child from parent
|
||||
OpaqueForChild() map[string]string
|
||||
// RunParentDriver signals initComplete when ParentDriver is ready to
|
||||
// serve as Manager.
|
||||
// RunParentDriver blocks until quit is signaled.
|
||||
//
|
||||
// ChildContext is optional.
|
||||
RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, cctx *ChildContext) error
|
||||
}
|
||||
|
||||
type ChildDriver interface {
|
||||
// RunChildDriver is executed in the child's namespaces, excluding detached-netns.
|
||||
RunChildDriver(opaque map[string]string, quit <-chan struct{}, detachedNetNSPath string) error
|
||||
}
|
||||
162
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/portutil/portutil.go
generated
vendored
Normal file
162
vendor/github.com/rootless-containers/rootlesskit/v2/pkg/port/portutil/portutil.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package portutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/rootless-containers/rootlesskit/v2/pkg/port"
|
||||
)
|
||||
|
||||
// ParsePortSpec parses a Docker-like representation of PortSpec, but with
|
||||
// support for both "parent IP" and "child IP" (optional);
|
||||
// e.g. "127.0.0.1:8080:80/tcp", or "127.0.0.1:8080:10.0.2.100:80/tcp"
|
||||
//
|
||||
// Format is as follows:
|
||||
//
|
||||
// <parent IP>:<parent port>[:<child IP>]:<child port>/<proto>
|
||||
//
|
||||
// Note that (child IP being optional) the format can either contain 5 or 4
|
||||
// components. When using IPv6 IP addresses, addresses must use square brackets
|
||||
// to prevent the colons being mistaken for delimiters. For example:
|
||||
//
|
||||
// [::1]:8080:[::2]:80/udp
|
||||
func ParsePortSpec(portSpec string) (*port.Spec, error) {
|
||||
const (
|
||||
parentIP = iota
|
||||
parentPort = iota
|
||||
childIP = iota
|
||||
childPort = iota
|
||||
proto = iota
|
||||
)
|
||||
|
||||
var (
|
||||
s scanner.Scanner
|
||||
err error
|
||||
parts = make([]string, 5)
|
||||
index = parentIP
|
||||
delimiter = ':'
|
||||
)
|
||||
|
||||
// First get the "proto" and "parent-port" at the end. These parts are
|
||||
// required, whereas "ParentIP" is optional. Removing them first makes
|
||||
// it easier to parse the remaining parts, as otherwise the third part
|
||||
// could be _either_ an IP-address _or_ a Port.
|
||||
|
||||
// Get the proto
|
||||
protoPos := strings.LastIndex(portSpec, "/")
|
||||
if protoPos < 0 {
|
||||
return nil, fmt.Errorf("missing proto in PortSpec string: %q", portSpec)
|
||||
}
|
||||
parts[proto] = portSpec[protoPos+1:]
|
||||
err = validateProto(parts[proto])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PortSpec string: %q: %w", portSpec, err)
|
||||
}
|
||||
|
||||
// Get the parent port
|
||||
portPos := strings.LastIndex(portSpec, ":")
|
||||
if portPos < 0 {
|
||||
return nil, fmt.Errorf("unexpected PortSpec string: %q", portSpec)
|
||||
}
|
||||
parts[childPort] = portSpec[portPos+1 : protoPos]
|
||||
|
||||
// Scan the remainder "<IP-address>:<port>[:<IP-address>]"
|
||||
s.Init(strings.NewReader(portSpec[:portPos]))
|
||||
|
||||
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
|
||||
if index > childPort {
|
||||
return nil, fmt.Errorf("unexpected PortSpec string: %q", portSpec)
|
||||
}
|
||||
|
||||
switch tok {
|
||||
case '[':
|
||||
// Start of IPv6 IP-address; value ends at closing bracket (])
|
||||
delimiter = ']'
|
||||
continue
|
||||
case delimiter:
|
||||
if delimiter == ']' {
|
||||
// End of IPv6 IP-address
|
||||
delimiter = ':'
|
||||
// Skip the next token, which should be a colon delimiter (:)
|
||||
tok = s.Scan()
|
||||
}
|
||||
index++
|
||||
continue
|
||||
default:
|
||||
parts[index] += s.TokenText()
|
||||
}
|
||||
}
|
||||
|
||||
if parts[parentIP] != "" && net.ParseIP(parts[parentIP]) == nil {
|
||||
return nil, fmt.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec)
|
||||
}
|
||||
if parts[childIP] != "" && net.ParseIP(parts[childIP]) == nil {
|
||||
return nil, fmt.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec)
|
||||
}
|
||||
|
||||
ps := &port.Spec{
|
||||
Proto: parts[proto],
|
||||
ParentIP: parts[parentIP],
|
||||
ChildIP: parts[childIP],
|
||||
}
|
||||
|
||||
ps.ParentPort, err = strconv.Atoi(parts[parentPort])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected ChildPort in PortSpec string: %q: %w", portSpec, err)
|
||||
}
|
||||
|
||||
ps.ChildPort, err = strconv.Atoi(parts[childPort])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected ParentPort in PortSpec string: %q: %w", portSpec, err)
|
||||
}
|
||||
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
// ValidatePortSpec validates *port.Spec.
|
||||
// existingPorts can be optionally passed for detecting conflicts.
|
||||
func ValidatePortSpec(spec port.Spec, existingPorts map[int]*port.Status) error {
|
||||
if err := validateProto(spec.Proto); err != nil {
|
||||
return err
|
||||
}
|
||||
if spec.ParentIP != "" {
|
||||
if net.ParseIP(spec.ParentIP) == nil {
|
||||
return fmt.Errorf("invalid ParentIP: %q", spec.ParentIP)
|
||||
}
|
||||
}
|
||||
if spec.ChildIP != "" {
|
||||
if net.ParseIP(spec.ChildIP) == nil {
|
||||
return fmt.Errorf("invalid ChildIP: %q", spec.ChildIP)
|
||||
}
|
||||
}
|
||||
if spec.ParentPort <= 0 || spec.ParentPort > 65535 {
|
||||
return fmt.Errorf("invalid ParentPort: %q", spec.ParentPort)
|
||||
}
|
||||
if spec.ChildPort <= 0 || spec.ChildPort > 65535 {
|
||||
return fmt.Errorf("invalid ChildPort: %q", spec.ChildPort)
|
||||
}
|
||||
for id, p := range existingPorts {
|
||||
sp := p.Spec
|
||||
sameProto := sp.Proto == spec.Proto
|
||||
sameParent := sp.ParentIP == spec.ParentIP && sp.ParentPort == spec.ParentPort
|
||||
if sameProto && sameParent {
|
||||
return fmt.Errorf("conflict with ID %d", id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProto(proto string) error {
|
||||
switch proto {
|
||||
case
|
||||
"tcp", "tcp4", "tcp6",
|
||||
"udp", "udp4", "udp6",
|
||||
"sctp", "sctp4", "sctp6":
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown proto: %q", proto)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user