mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Merge pull request #51616 from akerouanton/fix-51591
libnet/pms/nat: don't bind IPv6 ports if not supported by port driver
This commit is contained in:
@@ -220,7 +220,7 @@ func TestAddPortMappings(t *testing.T) {
|
||||
enableProxy bool
|
||||
hairpin bool
|
||||
busyPortIPv4 int
|
||||
rootless bool
|
||||
newPDC func() nat.PortDriverClient
|
||||
hostAddrs []string
|
||||
noProxy6To4 bool
|
||||
|
||||
@@ -667,7 +667,7 @@ func TestAddPortMappings(t *testing.T) {
|
||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
||||
},
|
||||
enableProxy: true,
|
||||
rootless: true,
|
||||
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(true) },
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero, HostPort: firstEphemPort},
|
||||
@@ -675,6 +675,21 @@ func TestAddPortMappings(t *testing.T) {
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 80, HostIP: net.IPv6zero, HostPort: firstEphemPort + 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rootless, ipv6 not supported",
|
||||
epAddrV4: ctrIP4,
|
||||
epAddrV6: ctrIP6,
|
||||
cfg: []portmapperapi.PortBindingReq{
|
||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 22}},
|
||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
||||
},
|
||||
enableProxy: true,
|
||||
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(false) },
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 80, HostIP: net.IPv4zero, HostPort: firstEphemPort + 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rootless without proxy",
|
||||
epAddrV4: ctrIP4,
|
||||
@@ -683,8 +698,8 @@ func TestAddPortMappings(t *testing.T) {
|
||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 22}},
|
||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
||||
},
|
||||
rootless: true,
|
||||
hairpin: true,
|
||||
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(true) },
|
||||
hairpin: true,
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero, HostPort: firstEphemPort},
|
||||
@@ -745,8 +760,8 @@ func TestAddPortMappings(t *testing.T) {
|
||||
}
|
||||
|
||||
var pdc nat.PortDriverClient
|
||||
if tc.rootless {
|
||||
pdc = newMockPortDriverClient()
|
||||
if tc.newPDC != nil {
|
||||
pdc = tc.newPDC()
|
||||
}
|
||||
|
||||
pms := &drvregistry.PortMappers{}
|
||||
@@ -780,7 +795,7 @@ func TestAddPortMappings(t *testing.T) {
|
||||
n.firewallerNetwork = fwn
|
||||
|
||||
expChildIP := func(hostIP net.IP) net.IP {
|
||||
if !tc.rootless {
|
||||
if pdc == nil {
|
||||
return hostIP
|
||||
}
|
||||
if hostIP.To4() == nil {
|
||||
@@ -938,16 +953,21 @@ func (p mockPortDriverPort) String() string {
|
||||
|
||||
type mockPortDriverClient struct {
|
||||
openPorts map[mockPortDriverPort]bool
|
||||
supportV6 bool
|
||||
}
|
||||
|
||||
func newMockPortDriverClient() *mockPortDriverClient {
|
||||
func newMockPortDriverClient(supportV6 bool) *mockPortDriverClient {
|
||||
return &mockPortDriverClient{
|
||||
openPorts: map[mockPortDriverPort]bool{},
|
||||
supportV6: supportV6,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockPortDriverClient) ChildHostIP(hostIP netip.Addr) netip.Addr {
|
||||
func (c *mockPortDriverClient) ChildHostIP(proto string, hostIP netip.Addr) netip.Addr {
|
||||
if hostIP.Is6() {
|
||||
if !c.supportV6 {
|
||||
return netip.Addr{}
|
||||
}
|
||||
return netip.IPv6Loopback()
|
||||
}
|
||||
return netip.MustParseAddr("127.0.0.1")
|
||||
|
||||
@@ -75,14 +75,38 @@ func NewPortDriverClient(ctx context.Context) (*PortDriverClient, error) {
|
||||
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.
|
||||
func (c *PortDriverClient) ChildHostIP(hostIP netip.Addr) netip.Addr {
|
||||
// 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
|
||||
}
|
||||
@@ -117,20 +141,8 @@ func (c *PortDriverClient) AddPort(
|
||||
if c == nil {
|
||||
return func() error { return nil }, nil
|
||||
}
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -135,6 +135,7 @@ func StartProxy(pb types.PortBinding,
|
||||
return nil
|
||||
}
|
||||
stopped.Store(true)
|
||||
log.G(context.Background()).WithField("pb", pb).Debug("Stopping userland proxy")
|
||||
if err := cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/containerd/log"
|
||||
@@ -13,12 +14,13 @@ import (
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/portmapperapi"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||
"github.com/moby/moby/v2/internal/sliceutil"
|
||||
)
|
||||
|
||||
const driverName = "nat"
|
||||
|
||||
type PortDriverClient interface {
|
||||
ChildHostIP(hostIP netip.Addr) netip.Addr
|
||||
ChildHostIP(proto string, hostIP netip.Addr) netip.Addr
|
||||
AddPort(ctx context.Context, proto string, hostIP, childIP netip.Addr, hostPort int) (func() error, error)
|
||||
}
|
||||
|
||||
@@ -73,12 +75,18 @@ func (pm PortMapper) MapPorts(ctx context.Context, cfg []portmapperapi.PortBindi
|
||||
}
|
||||
}()
|
||||
|
||||
addrs := make([]net.IP, 0, len(cfg))
|
||||
for i := range cfg {
|
||||
cfg[i] = setChildHostIP(pm.pdc, cfg[i])
|
||||
addrs = append(addrs, cfg[i].ChildHostIP)
|
||||
for i := len(cfg) - 1; i >= 0; i-- {
|
||||
var supported bool
|
||||
if cfg[i], supported = setChildHostIP(pm.pdc, cfg[i]); !supported {
|
||||
cfg = slices.Delete(cfg, i, i+1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
addrs := sliceutil.Map(cfg, func(req portmapperapi.PortBindingReq) net.IP {
|
||||
return req.ChildHostIP
|
||||
})
|
||||
|
||||
pa := portallocator.NewOSAllocator()
|
||||
allocatedPort, socks, err := pa.RequestPortsInRange(addrs, proto, int(hostPort), int(hostPortEnd))
|
||||
if err != nil {
|
||||
@@ -127,14 +135,21 @@ func (pm PortMapper) UnmapPorts(ctx context.Context, pbs []portmapperapi.PortBin
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func setChildHostIP(pdc PortDriverClient, req portmapperapi.PortBindingReq) portmapperapi.PortBindingReq {
|
||||
// setChildHostIP returns a modified PortBindingReq that contains the IP
|
||||
// address that should be used for port allocation, firewall rules, etc. It
|
||||
// returns false when the PortBindingReq isn't supported by the PortDriverClient.
|
||||
func setChildHostIP(pdc PortDriverClient, req portmapperapi.PortBindingReq) (portmapperapi.PortBindingReq, bool) {
|
||||
if pdc == nil {
|
||||
req.ChildHostIP = req.HostIP
|
||||
return req
|
||||
return req, true
|
||||
}
|
||||
hip, _ := netip.AddrFromSlice(req.HostIP)
|
||||
req.ChildHostIP = pdc.ChildHostIP(hip.Unmap()).AsSlice()
|
||||
return req
|
||||
chip := pdc.ChildHostIP(req.Proto.String(), hip.Unmap())
|
||||
if !chip.IsValid() {
|
||||
return req, false
|
||||
}
|
||||
req.ChildHostIP = chip.AsSlice()
|
||||
return req, true
|
||||
}
|
||||
|
||||
// configPortDriver passes the port binding's details to rootlesskit, and updates the
|
||||
|
||||
Reference in New Issue
Block a user