Skip to content

Commit

Permalink
rootlesskit-docker-proxy: support libnetwork >= 20201216 convention
Browse files Browse the repository at this point in the history
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 rootless-containers#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 <[email protected]>
  • Loading branch information
AkihiroSuda authored and youmeim committed Dec 18, 2024
1 parent 18d3f55 commit 2dd5864
Showing 1 changed file with 96 additions and 10 deletions.
106 changes: 96 additions & 10 deletions cmd/rootlesskit-docker-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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")
Expand All @@ -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
Expand Down

0 comments on commit 2dd5864

Please sign in to comment.