github.com/rootless-containers/rootlesskit/v2@v2.3.4/cmd/rootlesskit-docker-proxy/main.go (about)

     1  // Package main provides the `rootlesskit-docker-proxy` binary (DEPRECATED)
     2  // that was used by Docker prior to v28 for supporting rootless mode.
     3  //
     4  // The rootlesskit-docker-proxy binary is no longer needed since Docker v28,
     5  // as the functionality of rootlesskit-docker-proxy is now provided by dockerd itself.
     6  //
     7  // https://github.com/moby/moby/pull/48132/commits/dac7ffa3404138a4f291c16586e5a2c68dad4151
     8  //
     9  // rootlesskit-docker-proxy will be removed in RootlessKit v3.
    10  package main
    11  
    12  import (
    13  	"context"
    14  	"errors"
    15  	"flag"
    16  	"fmt"
    17  	"log"
    18  	"net"
    19  	"os"
    20  	"os/exec"
    21  	"os/signal"
    22  	"path/filepath"
    23  	"strconv"
    24  	"strings"
    25  	"syscall"
    26  
    27  	"github.com/rootless-containers/rootlesskit/v2/pkg/api"
    28  	"github.com/rootless-containers/rootlesskit/v2/pkg/api/client"
    29  	"github.com/rootless-containers/rootlesskit/v2/pkg/port"
    30  	"github.com/sirupsen/logrus"
    31  )
    32  
    33  const (
    34  	realProxy = "docker-proxy"
    35  )
    36  
    37  // drop-in replacement for docker-proxy.
    38  // needs to be executed in the child namespace.
    39  func main() {
    40  	f := os.NewFile(3, "signal-parent")
    41  	defer f.Close()
    42  	if err := xmain(f); err != nil {
    43  		// success: "0\n" (written by realProxy)
    44  		// error: "1\n" (written by either rootlesskit-docker-proxy or realProxy)
    45  		fmt.Fprintf(f, "1\n%s", err)
    46  		log.Fatal(err)
    47  	}
    48  }
    49  
    50  func isIPv6(ipStr string) bool {
    51  	ip := net.ParseIP(ipStr)
    52  	if ip == nil {
    53  		return false
    54  	}
    55  	return ip.To4() == nil
    56  }
    57  
    58  func getPortDriverProtos(info *api.Info) (string, map[string]struct{}, error) {
    59  	if info.PortDriver == nil {
    60  		return "", nil, errors.New("no port driver is available")
    61  	}
    62  	m := make(map[string]struct{}, len(info.PortDriver.Protos))
    63  	for _, p := range info.PortDriver.Protos {
    64  		m[p] = struct{}{}
    65  	}
    66  	return info.PortDriver.Driver, m, nil
    67  }
    68  
    69  type protocolUnsupportedError struct {
    70  	apiProto       string
    71  	portDriverName string
    72  	hostIP         string
    73  	hostPort       int
    74  }
    75  
    76  func (e *protocolUnsupportedError) Error() string {
    77  	return fmt.Sprintf("protocol %q is not supported by the RootlessKit port driver %q, discarding request for %q",
    78  		e.apiProto,
    79  		e.portDriverName,
    80  		net.JoinHostPort(e.hostIP, strconv.Itoa(e.hostPort)))
    81  }
    82  
    83  func callRootlessKitAPI(c client.Client, info *api.Info,
    84  	hostIP string, hostPort int,
    85  	dockerProxyProto, childIP string) (func() error, error) {
    86  	// dockerProxyProto is like "tcp", but we need to convert it to "tcp4" or "tcp6" explicitly
    87  	// for libnetwork >= 20201216
    88  	//
    89  	// See https://github.com/moby/libnetwork/pull/2604/files#diff-8fa48beed55dd033bf8e4f8c40b31cf69d0b2cc5d4bb53cde8594670ea6c938aR20
    90  	// See also https://github.com/rootless-containers/rootlesskit/issues/231
    91  	apiProto := dockerProxyProto
    92  	if !strings.HasSuffix(apiProto, "4") && !strings.HasSuffix(apiProto, "6") {
    93  		if isIPv6(hostIP) {
    94  			apiProto += "6"
    95  		} else {
    96  			apiProto += "4"
    97  		}
    98  	}
    99  	portDriverName, apiProtos, err := getPortDriverProtos(info)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	if _, ok := apiProtos[apiProto]; !ok {
   104  		// This happens when apiProto="tcp6", portDriverName="slirp4netns",
   105  		// because "slirp4netns" port driver does not support listening on IPv6 yet.
   106  		//
   107  		// Note that "slirp4netns" port driver is not used by default,
   108  		// even when network driver is set to "slirp4netns".
   109  		//
   110  		// Most users are using "builtin" port driver and will not see this warning.
   111  		err := &protocolUnsupportedError{
   112  			apiProto:       apiProto,
   113  			portDriverName: portDriverName,
   114  			hostIP:         hostIP,
   115  			hostPort:       hostPort,
   116  		}
   117  		return nil, err
   118  	}
   119  
   120  	pm := c.PortManager()
   121  	p := port.Spec{
   122  		Proto:      apiProto,
   123  		ParentIP:   hostIP,
   124  		ParentPort: hostPort,
   125  		ChildIP:    childIP,
   126  		ChildPort:  hostPort,
   127  	}
   128  	st, err := pm.AddPort(context.Background(), p)
   129  	if err != nil {
   130  		return nil, fmt.Errorf("error while calling PortManager.AddPort(): %w", err)
   131  	}
   132  	deferFunc := func() error {
   133  		if dErr := pm.RemovePort(context.Background(), st.ID); dErr != nil {
   134  			return fmt.Errorf("error while calling PortManager.RemovePort(): %w", err)
   135  		}
   136  		return nil
   137  	}
   138  	return deferFunc, nil
   139  }
   140  
   141  func xmain(f *os.File) error {
   142  	containerIP := flag.String("container-ip", "", "container ip")
   143  	containerPort := flag.Int("container-port", -1, "container port")
   144  	hostIP := flag.String("host-ip", "", "host ip")
   145  	hostPort := flag.Int("host-port", -1, "host port")
   146  	proto := flag.String("proto", "tcp", "proxy protocol")
   147  	flag.Parse()
   148  
   149  	stateDir := os.Getenv("ROOTLESSKIT_STATE_DIR")
   150  	if stateDir == "" {
   151  		return errors.New("$ROOTLESSKIT_STATE_DIR needs to be set")
   152  	}
   153  	socketPath := filepath.Join(stateDir, "api.sock")
   154  	c, err := client.New(socketPath)
   155  	if err != nil {
   156  		return fmt.Errorf("error while connecting to RootlessKit API socket: %w", err)
   157  	}
   158  
   159  	info, err := c.Info(context.Background())
   160  	if err != nil {
   161  		return fmt.Errorf("failed to call info API, probably RootlessKit binary is too old (needs to be v0.14.0 or later): %w", err)
   162  	}
   163  
   164  	// info.PortDriver is currently nil for "none" and "implicit", but this may change in future
   165  	if info.PortDriver == nil || info.PortDriver.Driver == "none" || info.PortDriver.Driver == "implicit" {
   166  		realProxyExe, err := exec.LookPath(realProxy)
   167  		if err != nil {
   168  			return err
   169  		}
   170  		return syscall.Exec(realProxyExe, append([]string{realProxy}, os.Args[1:]...), os.Environ())
   171  	}
   172  
   173  	// use loopback IP as the child IP, when port-driver="builtin"
   174  	childIP := "127.0.0.1"
   175  	if isIPv6(*hostIP) {
   176  		childIP = "::1"
   177  	}
   178  
   179  	if info.PortDriver.DisallowLoopbackChildIP {
   180  		// i.e., port-driver="slirp4netns"
   181  		if info.NetworkDriver.ChildIP == nil {
   182  			return fmt.Errorf("port driver (%q) does not allow loopback child IP, but network driver (%q) has no non-loopback IP",
   183  				info.PortDriver.Driver, info.NetworkDriver.Driver)
   184  		}
   185  		childIP = info.NetworkDriver.ChildIP.String()
   186  	}
   187  
   188  	deferFunc, err := callRootlessKitAPI(c, info, *hostIP, *hostPort, *proto, childIP)
   189  	if deferFunc != nil {
   190  		defer func() {
   191  			if dErr := deferFunc(); dErr != nil {
   192  				logrus.Warn(dErr)
   193  			}
   194  		}()
   195  	}
   196  	if err != nil {
   197  		if _, ok := err.(*protocolUnsupportedError); ok {
   198  			logrus.Warn(err)
   199  			// exit without executing realProxy (https://github.com/rootless-containers/rootlesskit/issues/250)
   200  			fmt.Fprint(f, "0\n")
   201  			return nil
   202  		}
   203  		return err
   204  	}
   205  
   206  	cmd := exec.Command(realProxy,
   207  		"-container-ip", *containerIP,
   208  		"-container-port", strconv.Itoa(*containerPort),
   209  		"-host-ip", childIP,
   210  		"-host-port", strconv.Itoa(*hostPort),
   211  		"-proto", *proto)
   212  	cmd.Stdout = os.Stdout
   213  	cmd.Stderr = os.Stderr
   214  	cmd.Env = os.Environ()
   215  	cmd.ExtraFiles = append(cmd.ExtraFiles, f)
   216  	cmd.SysProcAttr = &syscall.SysProcAttr{
   217  		Pdeathsig: syscall.SIGKILL,
   218  	}
   219  	if err := cmd.Start(); err != nil {
   220  		return fmt.Errorf("error while starting %s: %w", realProxy, err)
   221  	}
   222  
   223  	ch := make(chan os.Signal, 1)
   224  	signal.Notify(ch, os.Interrupt)
   225  	<-ch
   226  	if err := cmd.Process.Kill(); err != nil {
   227  		return fmt.Errorf("error while killing %s: %w", realProxy, err)
   228  	}
   229  	return nil
   230  }