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
|
enableProxy bool
|
||||||
hairpin bool
|
hairpin bool
|
||||||
busyPortIPv4 int
|
busyPortIPv4 int
|
||||||
rootless bool
|
newPDC func() nat.PortDriverClient
|
||||||
hostAddrs []string
|
hostAddrs []string
|
||||||
noProxy6To4 bool
|
noProxy6To4 bool
|
||||||
|
|
||||||
@@ -667,7 +667,7 @@ func TestAddPortMappings(t *testing.T) {
|
|||||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
||||||
},
|
},
|
||||||
enableProxy: true,
|
enableProxy: true,
|
||||||
rootless: true,
|
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(true) },
|
||||||
expPBs: []types.PortBinding{
|
expPBs: []types.PortBinding{
|
||||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
|
{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},
|
{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},
|
{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",
|
name: "rootless without proxy",
|
||||||
epAddrV4: ctrIP4,
|
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: 22}},
|
||||||
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
|
||||||
},
|
},
|
||||||
rootless: true,
|
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(true) },
|
||||||
hairpin: true,
|
hairpin: true,
|
||||||
expPBs: []types.PortBinding{
|
expPBs: []types.PortBinding{
|
||||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
|
{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},
|
{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
|
var pdc nat.PortDriverClient
|
||||||
if tc.rootless {
|
if tc.newPDC != nil {
|
||||||
pdc = newMockPortDriverClient()
|
pdc = tc.newPDC()
|
||||||
}
|
}
|
||||||
|
|
||||||
pms := &drvregistry.PortMappers{}
|
pms := &drvregistry.PortMappers{}
|
||||||
@@ -780,7 +795,7 @@ func TestAddPortMappings(t *testing.T) {
|
|||||||
n.firewallerNetwork = fwn
|
n.firewallerNetwork = fwn
|
||||||
|
|
||||||
expChildIP := func(hostIP net.IP) net.IP {
|
expChildIP := func(hostIP net.IP) net.IP {
|
||||||
if !tc.rootless {
|
if pdc == nil {
|
||||||
return hostIP
|
return hostIP
|
||||||
}
|
}
|
||||||
if hostIP.To4() == nil {
|
if hostIP.To4() == nil {
|
||||||
@@ -938,16 +953,21 @@ func (p mockPortDriverPort) String() string {
|
|||||||
|
|
||||||
type mockPortDriverClient struct {
|
type mockPortDriverClient struct {
|
||||||
openPorts map[mockPortDriverPort]bool
|
openPorts map[mockPortDriverPort]bool
|
||||||
|
supportV6 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockPortDriverClient() *mockPortDriverClient {
|
func newMockPortDriverClient(supportV6 bool) *mockPortDriverClient {
|
||||||
return &mockPortDriverClient{
|
return &mockPortDriverClient{
|
||||||
openPorts: map[mockPortDriverPort]bool{},
|
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 hostIP.Is6() {
|
||||||
|
if !c.supportV6 {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
return netip.IPv6Loopback()
|
return netip.IPv6Loopback()
|
||||||
}
|
}
|
||||||
return netip.MustParseAddr("127.0.0.1")
|
return netip.MustParseAddr("127.0.0.1")
|
||||||
|
|||||||
@@ -75,14 +75,38 @@ func NewPortDriverClient(ctx context.Context) (*PortDriverClient, error) {
|
|||||||
return pdc, nil
|
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
|
// ChildHostIP returns the address that must be used in the child network
|
||||||
// namespace in place of hostIP, a host IP address. In particular, port
|
// namespace in place of hostIP, a host IP address. In particular, port
|
||||||
// mappings from host IP addresses, and DNAT rules, must use this child
|
// mappings from host IP addresses, and DNAT rules, must use this child
|
||||||
// address in place of the real host address.
|
// address in place of the real host address. It may return an invalid
|
||||||
func (c *PortDriverClient) ChildHostIP(hostIP netip.Addr) netip.Addr {
|
// 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 {
|
if c == nil {
|
||||||
return hostIP
|
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() {
|
if c.childIP.IsValid() {
|
||||||
return c.childIP
|
return c.childIP
|
||||||
}
|
}
|
||||||
@@ -117,20 +141,8 @@ func (c *PortDriverClient) AddPort(
|
|||||||
if c == nil {
|
if c == nil {
|
||||||
return func() error { return nil }, 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 {
|
if _, ok := c.protos[apiProto]; !ok {
|
||||||
// This happens when apiProto="tcp6", portDriverName="slirp4netns",
|
// This happens when apiProto="tcp6", portDriverName="slirp4netns",
|
||||||
// because "slirp4netns" port driver does not support listening on IPv6 yet.
|
// because "slirp4netns" port driver does not support listening on IPv6 yet.
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ func StartProxy(pb types.PortBinding,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
stopped.Store(true)
|
stopped.Store(true)
|
||||||
|
log.G(context.Background()).WithField("pb", pb).Debug("Stopping userland proxy")
|
||||||
if err := cmd.Process.Signal(os.Interrupt); err != nil {
|
if err := cmd.Process.Signal(os.Interrupt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/containerd/log"
|
"github.com/containerd/log"
|
||||||
@@ -13,12 +14,13 @@ import (
|
|||||||
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
|
||||||
"github.com/moby/moby/v2/daemon/libnetwork/portmapperapi"
|
"github.com/moby/moby/v2/daemon/libnetwork/portmapperapi"
|
||||||
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
||||||
|
"github.com/moby/moby/v2/internal/sliceutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const driverName = "nat"
|
const driverName = "nat"
|
||||||
|
|
||||||
type PortDriverClient interface {
|
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)
|
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 := len(cfg) - 1; i >= 0; i-- {
|
||||||
for i := range cfg {
|
var supported bool
|
||||||
cfg[i] = setChildHostIP(pm.pdc, cfg[i])
|
if cfg[i], supported = setChildHostIP(pm.pdc, cfg[i]); !supported {
|
||||||
addrs = append(addrs, cfg[i].ChildHostIP)
|
cfg = slices.Delete(cfg, i, i+1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addrs := sliceutil.Map(cfg, func(req portmapperapi.PortBindingReq) net.IP {
|
||||||
|
return req.ChildHostIP
|
||||||
|
})
|
||||||
|
|
||||||
pa := portallocator.NewOSAllocator()
|
pa := portallocator.NewOSAllocator()
|
||||||
allocatedPort, socks, err := pa.RequestPortsInRange(addrs, proto, int(hostPort), int(hostPortEnd))
|
allocatedPort, socks, err := pa.RequestPortsInRange(addrs, proto, int(hostPort), int(hostPortEnd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -127,14 +135,21 @@ func (pm PortMapper) UnmapPorts(ctx context.Context, pbs []portmapperapi.PortBin
|
|||||||
return errors.Join(errs...)
|
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 {
|
if pdc == nil {
|
||||||
req.ChildHostIP = req.HostIP
|
req.ChildHostIP = req.HostIP
|
||||||
return req
|
return req, true
|
||||||
}
|
}
|
||||||
hip, _ := netip.AddrFromSlice(req.HostIP)
|
hip, _ := netip.AddrFromSlice(req.HostIP)
|
||||||
req.ChildHostIP = pdc.ChildHostIP(hip.Unmap()).AsSlice()
|
chip := pdc.ChildHostIP(req.Proto.String(), hip.Unmap())
|
||||||
return req
|
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
|
// configPortDriver passes the port binding's details to rootlesskit, and updates the
|
||||||
|
|||||||
Reference in New Issue
Block a user