mirror of
https://github.com/moby/moby.git
synced 2026-08-03 14:41:03 +00:00
Follow-up to PR 52804, applying thaJeztah's review suggestion: check IsLoopback first for both address families (preserving any requested loopback address), and only fall back to the canonical loopback for the family otherwise. No behavior change; ::1 now returns through the loopback-preserving branch instead of the IPv6 fallback, with the same result. Signed-off-by: Andrew Liu <andrewjliu22@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
189 lines
6.0 KiB
Go
189 lines
6.0 KiB
Go
// RootlessKit integration - if required by RootlessKit's port driver, let it know
|
|
// about port mappings as they're added and removed.
|
|
//
|
|
// This is based on / copied from rootlesskit-docker-proxy, which was previously
|
|
// installed as a proxy for docker-proxy:
|
|
// https://github.com/rootless-containers/rootlesskit/blob/4fb2e2cb80bf13eb28b7f2a4317b63406b89ad32/cmd/rootlesskit-docker-proxy/main.go
|
|
|
|
package rlkclient
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/rootless-containers/rootlesskit/v3/pkg/api/client"
|
|
"github.com/rootless-containers/rootlesskit/v3/pkg/port"
|
|
)
|
|
|
|
type PortDriverClient struct {
|
|
client client.Client
|
|
portDriverName string
|
|
protos map[string]struct{}
|
|
childIP netip.Addr
|
|
}
|
|
|
|
func NewPortDriverClient(ctx context.Context) (*PortDriverClient, error) {
|
|
stateDir := os.Getenv("ROOTLESSKIT_STATE_DIR")
|
|
if stateDir == "" {
|
|
return nil, errors.New("$ROOTLESSKIT_STATE_DIR needs to be set")
|
|
}
|
|
socketPath := filepath.Join(stateDir, "api.sock")
|
|
c, err := client.New(socketPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while connecting to RootlessKit API socket: %w", err)
|
|
}
|
|
|
|
info, err := c.Info(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call info API, probably RootlessKit binary is too old (needs to be v0.14.0 or later): %w", err)
|
|
}
|
|
|
|
// info.PortDriver is currently nil for "none" and "implicit", but this may change in future
|
|
if info.PortDriver == nil || info.PortDriver.Driver == "none" || info.PortDriver.Driver == "implicit" {
|
|
return nil, nil
|
|
}
|
|
|
|
pdc := &PortDriverClient{
|
|
client: c,
|
|
portDriverName: info.PortDriver.Driver,
|
|
}
|
|
|
|
if info.PortDriver.DisallowLoopbackChildIP {
|
|
// i.e., port-driver="slirp4netns"
|
|
if info.NetworkDriver.ChildIP == nil {
|
|
return nil, fmt.Errorf("RootlessKit port driver (%q) does not allow loopback child IP, but network driver (%q) has no non-loopback IP",
|
|
info.PortDriver.Driver, info.NetworkDriver.Driver)
|
|
}
|
|
childIP, ok := netip.AddrFromSlice(info.NetworkDriver.ChildIP)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to use child IP %s from network driver (%q)",
|
|
info.NetworkDriver.ChildIP, info.NetworkDriver.Driver)
|
|
}
|
|
pdc.childIP = childIP.Unmap()
|
|
}
|
|
|
|
pdc.protos = make(map[string]struct{}, len(info.PortDriver.Protos))
|
|
for _, p := range info.PortDriver.Protos {
|
|
pdc.protos[p] = struct{}{}
|
|
}
|
|
|
|
return pdc, nil
|
|
}
|
|
|
|
// proto normalizes the protocol to match what the rootlesskit API expects.
|
|
func (c *PortDriverClient) proto(proto string, hostIP netip.Addr) string {
|
|
// proto 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 := proto
|
|
if !strings.HasSuffix(apiProto, "4") && !strings.HasSuffix(apiProto, "6") {
|
|
if hostIP.Is6() {
|
|
apiProto += "6"
|
|
} else {
|
|
apiProto += "4"
|
|
}
|
|
}
|
|
return apiProto
|
|
}
|
|
|
|
// ChildHostIP returns the address that must be used in the child network
|
|
// namespace in place of hostIP, a host IP address. In particular, port
|
|
// mappings from host IP addresses, and DNAT rules, must use this child
|
|
// address in place of the real host address. It may return an invalid
|
|
// netip.Addr if the proto and IP family aren't supported.
|
|
func (c *PortDriverClient) ChildHostIP(proto string, hostIP netip.Addr) netip.Addr {
|
|
if c == nil {
|
|
return hostIP
|
|
}
|
|
if _, ok := c.protos[c.proto(proto, hostIP)]; !ok {
|
|
// This happens when apiProto="tcp6", portDriverName="slirp4netns",
|
|
// because "slirp4netns" port driver does not support listening on IPv6 yet.
|
|
return netip.Addr{}
|
|
}
|
|
if c.childIP.IsValid() {
|
|
return c.childIP
|
|
}
|
|
// Child namespaces only listen on loopback addresses. Preserve requested
|
|
// loopback addresses (the child namespace's lo covers all of 127.0.0.0/8,
|
|
// and collapsing them makes bindings on the same port but distinct
|
|
// loopback addresses collide); otherwise use the canonical loopback
|
|
// address for the family.
|
|
if hostIP.IsLoopback() {
|
|
return hostIP
|
|
}
|
|
if hostIP.Is6() {
|
|
return netip.IPv6Loopback()
|
|
}
|
|
return netip.MustParseAddr("127.0.0.1")
|
|
}
|
|
|
|
// ProtocolUnsupportedError is returned when apiProto is not supported by portDriverName.
|
|
type ProtocolUnsupportedError struct {
|
|
apiProto string
|
|
portDriverName string
|
|
}
|
|
|
|
func (e *ProtocolUnsupportedError) Error() string {
|
|
return fmt.Sprintf("protocol %q is not supported by the RootlessKit port driver %q",
|
|
e.apiProto, e.portDriverName)
|
|
}
|
|
|
|
// AddPort makes a request to RootlessKit asking it to set up a port
|
|
// mapping between a host IP address and a child host IP address.
|
|
//
|
|
// AddPort may return [ProtocolUnsupportedError].
|
|
func (c *PortDriverClient) AddPort(
|
|
ctx context.Context,
|
|
proto string,
|
|
hostIP netip.Addr,
|
|
childIP netip.Addr,
|
|
hostPort int,
|
|
) (func() error, error) {
|
|
if c == nil {
|
|
return func() error { return nil }, nil
|
|
}
|
|
|
|
apiProto := c.proto(proto, hostIP)
|
|
if _, ok := c.protos[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.
|
|
err := &ProtocolUnsupportedError{
|
|
apiProto: apiProto,
|
|
portDriverName: c.portDriverName,
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
pm := c.client.PortManager()
|
|
p := port.Spec{
|
|
Proto: apiProto,
|
|
ParentIP: hostIP.String(),
|
|
ParentPort: hostPort,
|
|
ChildIP: childIP.String(),
|
|
ChildPort: hostPort,
|
|
}
|
|
st, err := pm.AddPort(ctx, p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error while calling RootlessKit PortManager.AddPort(): %w", err)
|
|
}
|
|
deferFunc := func() error {
|
|
if dErr := pm.RemovePort(context.WithoutCancel(ctx), st.ID); dErr != nil {
|
|
return fmt.Errorf("error while calling RootlessKit PortManager.RemovePort(): %w", dErr)
|
|
}
|
|
return nil
|
|
}
|
|
return deferFunc, nil
|
|
}
|