From 2dd5864a51977b702b7dbb11f3fd7161176b6f52 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Tue, 2 Mar 2021 01:23:19 +0900 Subject: [PATCH] rootlesskit-docker-proxy: support libnetwork >= 20201216 convention The `-proto` argument of `docker-proxy` is like "tcp", but we need to convert it to "tcp4" or "tcp6" explicitly when calling RootlessKit API, for libnetwork >= 20201216. If the port driver does not support "tcp6" (especially when the port driver is slirp4netns), `rootlesskit-docker-proxy` skips exposing the port via RootlessKit API, without showing an error. (We can't raise an error here, because `docker run -p 8080:80` always causes `rootlesskit-docker-proxy -host-ip ::` as well as `r-d-p -h-i 0.0.0.0`) See https://github.com/moby/libnetwork/pull/2604/files#diff-8fa48beed55dd033bf8e4f8c40b31cf69d0b2cc5d4bb53cde8594670ea6c938aR20 See also https://github.com/rootless-containers/rootlesskit/issues/231 Using this version of `rootlesskit-docker-proxy` with libnetwork < 20201216 is also fine, because Rootless Docker had never officially supported IPv6. Signed-off-by: Akihiro Suda --- cmd/rootlesskit-docker-proxy/main.go | 106 ++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 10 deletions(-) diff --git a/cmd/rootlesskit-docker-proxy/main.go b/cmd/rootlesskit-docker-proxy/main.go index df83cd5b..d0475b49 100644 --- a/cmd/rootlesskit-docker-proxy/main.go +++ b/cmd/rootlesskit-docker-proxy/main.go @@ -5,15 +5,18 @@ import ( "flag" "fmt" "log" + "net" "os" "os/exec" "os/signal" "path/filepath" "strconv" + "strings" "syscall" "github.com/rootless-containers/rootlesskit/pkg/api/client" "github.com/rootless-containers/rootlesskit/pkg/port" + "github.com/sirupsen/logrus" "github.com/pkg/errors" ) @@ -35,6 +38,84 @@ func main() { } } +func isIPv6(ipStr string) bool { + ip := net.ParseIP(ipStr) + if ip == nil { + return false + } + return ip.To4() == nil +} + +func getPortDriverProtos(c client.Client) (string, map[string]struct{}, error) { + info, err := c.Info(context.Background()) + if err != nil { + return "", nil, errors.Wrap(err, "failed to call info API, probably RootlessKit binary is too old (needs to be v0.14.0 or later)") + } + if info.PortDriver == nil { + return "", nil, errors.New("no port driver is available") + } + m := make(map[string]struct{}, len(info.PortDriver.Protos)) + for _, p := range info.PortDriver.Protos { + m[p] = struct{}{} + } + return info.PortDriver.Driver, m, nil +} + +func callRootlessKitAPI(c client.Client, + hostIP string, hostPort int, + dockerProxyProto string) (func() error, error) { + // dockerProxyProto is like "tcp", but we need to convert it to "tcp4" or "tcp6" explicitly + // for libnetwork >= 20201216 + // + // See https://github.com/moby/libnetwork/pull/2604/files#diff-8fa48beed55dd033bf8e4f8c40b31cf69d0b2cc5d4bb53cde8594670ea6c938aR20 + // See also https://github.com/rootless-containers/rootlesskit/issues/231 + apiProto := dockerProxyProto + if !strings.HasSuffix(apiProto, "4") && !strings.HasSuffix(apiProto, "6") { + if isIPv6(hostIP) { + apiProto += "6" + } else { + apiProto += "4" + } + } + portDriverName, apiProtos, err := getPortDriverProtos(c) + if err != nil { + return nil, err + } + if _, ok := apiProtos[apiProto]; !ok { + // This happens when apiProto="tcp6", portDriverName="slirp4netns", + // because "slirp4netns" port driver does not support listening on IPv6 yet. + // + // Note that "slirp4netns" port driver is not used by default, + // even when network driver is set to "slirp4netns". + // + // Most users are using "builtin" port driver and will not see this warning. + logrus.Warnf("protocol %q is not supported by the RootlessKit port driver %q, ignoring request for %q", + apiProto, + portDriverName, + net.JoinHostPort(hostIP, strconv.Itoa(hostPort))) + return nil, nil + } + + pm := c.PortManager() + p := port.Spec{ + Proto: apiProto, + ParentIP: hostIP, + ParentPort: hostPort, + ChildPort: hostPort, + } + st, err := pm.AddPort(context.Background(), p) + if err != nil { + return nil, errors.Wrap(err, "error while calling PortManager.AddPort()") + } + deferFunc := func() error { + if dErr := pm.RemovePort(context.Background(), st.ID); dErr != nil { + return errors.Wrap(err, "error while calling PortManager.RemovePort()") + } + return nil + } + return deferFunc, nil +} + func xmain(f *os.File) error { containerIP := flag.String("container-ip", "", "container ip") containerPort := flag.Int("container-port", -1, "container port") @@ -52,23 +133,28 @@ func xmain(f *os.File) error { if err != nil { return errors.Wrap(err, "error while connecting to RootlessKit API socket") } - pm := c.PortManager() - p := port.Spec{ - Proto: *proto, - ParentIP: *hostIP, - ParentPort: *hostPort, - ChildPort: *hostPort, + + deferFunc, err := callRootlessKitAPI(c, *hostIP, *hostPort, *proto) + if deferFunc != nil { + defer func() { + if dErr := deferFunc(); dErr != nil { + logrus.Warn(dErr) + } + }() } - st, err := pm.AddPort(context.Background(), p) if err != nil { - return errors.Wrap(err, "error while calling PortManager.AddPort()") + return err + } + + realProxyHostIP := "127.0.0.1" + if isIPv6(*hostIP) { + realProxyHostIP = "::1" } - defer pm.RemovePort(context.Background(), st.ID) cmd := exec.Command(realProxy, "-container-ip", *containerIP, "-container-port", strconv.Itoa(*containerPort), - "-host-ip", "127.0.0.1", + "-host-ip", realProxyHostIP, "-host-port", strconv.Itoa(*hostPort), "-proto", *proto) cmd.Stdout = os.Stdout