Skip to content

Commit 2faf258

Browse files
authored
Merge pull request moby#51616 from akerouanton/fix-51591
libnet/pms/nat: don't bind IPv6 ports if not supported by port driver
2 parents 9a84135 + 310aa92 commit 2faf258

File tree

4 files changed

+81
-33
lines changed

4 files changed

+81
-33
lines changed

daemon/libnetwork/drivers/bridge/port_mapping_linux_test.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func TestAddPortMappings(t *testing.T) {
220220
enableProxy bool
221221
hairpin bool
222222
busyPortIPv4 int
223-
rootless bool
223+
newPDC func() nat.PortDriverClient
224224
hostAddrs []string
225225
noProxy6To4 bool
226226

@@ -667,14 +667,29 @@ func TestAddPortMappings(t *testing.T) {
667667
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
668668
},
669669
enableProxy: true,
670-
rootless: true,
670+
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(true) },
671671
expPBs: []types.PortBinding{
672672
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
673673
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero, HostPort: firstEphemPort},
674674
{Proto: types.TCP, IP: ctrIP4.IP, Port: 80, HostIP: net.IPv4zero, HostPort: firstEphemPort + 1},
675675
{Proto: types.TCP, IP: ctrIP6.IP, Port: 80, HostIP: net.IPv6zero, HostPort: firstEphemPort + 1},
676676
},
677677
},
678+
{
679+
name: "rootless, ipv6 not supported",
680+
epAddrV4: ctrIP4,
681+
epAddrV6: ctrIP6,
682+
cfg: []portmapperapi.PortBindingReq{
683+
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 22}},
684+
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
685+
},
686+
enableProxy: true,
687+
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(false) },
688+
expPBs: []types.PortBinding{
689+
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
690+
{Proto: types.TCP, IP: ctrIP4.IP, Port: 80, HostIP: net.IPv4zero, HostPort: firstEphemPort + 1},
691+
},
692+
},
678693
{
679694
name: "rootless without proxy",
680695
epAddrV4: ctrIP4,
@@ -683,8 +698,8 @@ func TestAddPortMappings(t *testing.T) {
683698
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 22}},
684699
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 80}},
685700
},
686-
rootless: true,
687-
hairpin: true,
701+
newPDC: func() nat.PortDriverClient { return newMockPortDriverClient(true) },
702+
hairpin: true,
688703
expPBs: []types.PortBinding{
689704
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero, HostPort: firstEphemPort},
690705
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero, HostPort: firstEphemPort},
@@ -745,8 +760,8 @@ func TestAddPortMappings(t *testing.T) {
745760
}
746761

747762
var pdc nat.PortDriverClient
748-
if tc.rootless {
749-
pdc = newMockPortDriverClient()
763+
if tc.newPDC != nil {
764+
pdc = tc.newPDC()
750765
}
751766

752767
pms := &drvregistry.PortMappers{}
@@ -780,7 +795,7 @@ func TestAddPortMappings(t *testing.T) {
780795
n.firewallerNetwork = fwn
781796

782797
expChildIP := func(hostIP net.IP) net.IP {
783-
if !tc.rootless {
798+
if pdc == nil {
784799
return hostIP
785800
}
786801
if hostIP.To4() == nil {
@@ -938,16 +953,21 @@ func (p mockPortDriverPort) String() string {
938953

939954
type mockPortDriverClient struct {
940955
openPorts map[mockPortDriverPort]bool
956+
supportV6 bool
941957
}
942958

943-
func newMockPortDriverClient() *mockPortDriverClient {
959+
func newMockPortDriverClient(supportV6 bool) *mockPortDriverClient {
944960
return &mockPortDriverClient{
945961
openPorts: map[mockPortDriverPort]bool{},
962+
supportV6: supportV6,
946963
}
947964
}
948965

949-
func (c *mockPortDriverClient) ChildHostIP(hostIP netip.Addr) netip.Addr {
966+
func (c *mockPortDriverClient) ChildHostIP(proto string, hostIP netip.Addr) netip.Addr {
950967
if hostIP.Is6() {
968+
if !c.supportV6 {
969+
return netip.Addr{}
970+
}
951971
return netip.IPv6Loopback()
952972
}
953973
return netip.MustParseAddr("127.0.0.1")

daemon/libnetwork/internal/rlkclient/rootlesskit_client_linux.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,38 @@ func NewPortDriverClient(ctx context.Context) (*PortDriverClient, error) {
7575
return pdc, nil
7676
}
7777

78+
// proto normalizes the protocol to match what the rootlesskit API expects.
79+
func (c *PortDriverClient) proto(proto string, hostIP netip.Addr) string {
80+
// proto is like "tcp", but we need to convert it to "tcp4" or "tcp6" explicitly
81+
// for libnetwork >= 20201216
82+
//
83+
// See https://github.com/moby/libnetwork/pull/2604/files#diff-8fa48beed55dd033bf8e4f8c40b31cf69d0b2cc5d4bb53cde8594670ea6c938aR20
84+
// See also https://github.com/rootless-containers/rootlesskit/issues/231
85+
apiProto := proto
86+
if !strings.HasSuffix(apiProto, "4") && !strings.HasSuffix(apiProto, "6") {
87+
if hostIP.Is6() {
88+
apiProto += "6"
89+
} else {
90+
apiProto += "4"
91+
}
92+
}
93+
return apiProto
94+
}
95+
7896
// ChildHostIP returns the address that must be used in the child network
7997
// namespace in place of hostIP, a host IP address. In particular, port
8098
// mappings from host IP addresses, and DNAT rules, must use this child
81-
// address in place of the real host address.
82-
func (c *PortDriverClient) ChildHostIP(hostIP netip.Addr) netip.Addr {
99+
// address in place of the real host address. It may return an invalid
100+
// netip.Addr if the proto and IP family aren't supported.
101+
func (c *PortDriverClient) ChildHostIP(proto string, hostIP netip.Addr) netip.Addr {
83102
if c == nil {
84103
return hostIP
85104
}
105+
if _, ok := c.protos[c.proto(proto, hostIP)]; !ok {
106+
// This happens when apiProto="tcp6", portDriverName="slirp4netns",
107+
// because "slirp4netns" port driver does not support listening on IPv6 yet.
108+
return netip.Addr{}
109+
}
86110
if c.childIP.IsValid() {
87111
return c.childIP
88112
}
@@ -117,20 +141,8 @@ func (c *PortDriverClient) AddPort(
117141
if c == nil {
118142
return func() error { return nil }, nil
119143
}
120-
// proto is like "tcp", but we need to convert it to "tcp4" or "tcp6" explicitly
121-
// for libnetwork >= 20201216
122-
//
123-
// See https://github.com/moby/libnetwork/pull/2604/files#diff-8fa48beed55dd033bf8e4f8c40b31cf69d0b2cc5d4bb53cde8594670ea6c938aR20
124-
// See also https://github.com/rootless-containers/rootlesskit/issues/231
125-
apiProto := proto
126-
if !strings.HasSuffix(apiProto, "4") && !strings.HasSuffix(apiProto, "6") {
127-
if hostIP.Is6() {
128-
apiProto += "6"
129-
} else {
130-
apiProto += "4"
131-
}
132-
}
133144

145+
apiProto := c.proto(proto, hostIP)
134146
if _, ok := c.protos[apiProto]; !ok {
135147
// This happens when apiProto="tcp6", portDriverName="slirp4netns",
136148
// because "slirp4netns" port driver does not support listening on IPv6 yet.

daemon/libnetwork/portmapper/proxy_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func StartProxy(pb types.PortBinding,
135135
return nil
136136
}
137137
stopped.Store(true)
138+
log.G(context.Background()).WithField("pb", pb).Debug("Stopping userland proxy")
138139
if err := cmd.Process.Signal(os.Interrupt); err != nil {
139140
return err
140141
}

daemon/libnetwork/portmappers/nat/mapper_linux.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@ import (
66
"fmt"
77
"net"
88
"net/netip"
9+
"slices"
910
"strconv"
1011

1112
"github.com/containerd/log"
1213
"github.com/moby/moby/v2/daemon/libnetwork/internal/rlkclient"
1314
"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
1415
"github.com/moby/moby/v2/daemon/libnetwork/portmapperapi"
1516
"github.com/moby/moby/v2/daemon/libnetwork/types"
17+
"github.com/moby/moby/v2/internal/sliceutil"
1618
)
1719

1820
const driverName = "nat"
1921

2022
type PortDriverClient interface {
21-
ChildHostIP(hostIP netip.Addr) netip.Addr
23+
ChildHostIP(proto string, hostIP netip.Addr) netip.Addr
2224
AddPort(ctx context.Context, proto string, hostIP, childIP netip.Addr, hostPort int) (func() error, error)
2325
}
2426

@@ -73,12 +75,18 @@ func (pm PortMapper) MapPorts(ctx context.Context, cfg []portmapperapi.PortBindi
7375
}
7476
}()
7577

76-
addrs := make([]net.IP, 0, len(cfg))
77-
for i := range cfg {
78-
cfg[i] = setChildHostIP(pm.pdc, cfg[i])
79-
addrs = append(addrs, cfg[i].ChildHostIP)
78+
for i := len(cfg) - 1; i >= 0; i-- {
79+
var supported bool
80+
if cfg[i], supported = setChildHostIP(pm.pdc, cfg[i]); !supported {
81+
cfg = slices.Delete(cfg, i, i+1)
82+
continue
83+
}
8084
}
8185

86+
addrs := sliceutil.Map(cfg, func(req portmapperapi.PortBindingReq) net.IP {
87+
return req.ChildHostIP
88+
})
89+
8290
pa := portallocator.NewOSAllocator()
8391
allocatedPort, socks, err := pa.RequestPortsInRange(addrs, proto, int(hostPort), int(hostPortEnd))
8492
if err != nil {
@@ -127,14 +135,21 @@ func (pm PortMapper) UnmapPorts(ctx context.Context, pbs []portmapperapi.PortBin
127135
return errors.Join(errs...)
128136
}
129137

130-
func setChildHostIP(pdc PortDriverClient, req portmapperapi.PortBindingReq) portmapperapi.PortBindingReq {
138+
// setChildHostIP returns a modified PortBindingReq that contains the IP
139+
// address that should be used for port allocation, firewall rules, etc. It
140+
// returns false when the PortBindingReq isn't supported by the PortDriverClient.
141+
func setChildHostIP(pdc PortDriverClient, req portmapperapi.PortBindingReq) (portmapperapi.PortBindingReq, bool) {
131142
if pdc == nil {
132143
req.ChildHostIP = req.HostIP
133-
return req
144+
return req, true
134145
}
135146
hip, _ := netip.AddrFromSlice(req.HostIP)
136-
req.ChildHostIP = pdc.ChildHostIP(hip.Unmap()).AsSlice()
137-
return req
147+
chip := pdc.ChildHostIP(req.Proto.String(), hip.Unmap())
148+
if !chip.IsValid() {
149+
return req, false
150+
}
151+
req.ChildHostIP = chip.AsSlice()
152+
return req, true
138153
}
139154

140155
// configPortDriver passes the port binding's details to rootlesskit, and updates the

0 commit comments

Comments
 (0)