mirror of
https://github.com/moby/moby.git
synced 2026-08-08 17:11:38 +00:00
Log rather than error if port mapping is overspecified
Previously, if a port mapping specified a host IP or port that could not be used because the endpoint's network was in routed mode (so, there's no host binding), it'd be treated as an error. However: - the selected gateway endpoint may change over time, as networks are connected and disconnected - so the binding may make sense for some other endpoint. - the validation was complicated, duplicated logic in order to fail early, and wasn't complete. So, just log when fields are ignored, at the point where they're ignored. Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
@@ -1448,7 +1448,7 @@ func (d *driver) ProgramExternalConnectivity(ctx context.Context, nid, eid strin
|
||||
// Program any required port mapping and store them in the endpoint
|
||||
if endpoint.extConnConfig != nil && endpoint.extConnConfig.PortBindings != nil {
|
||||
endpoint.portMapping, err = network.addPortMappings(
|
||||
ctx,
|
||||
log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{"nid": nid, "eid": eid})),
|
||||
endpoint.addr,
|
||||
endpoint.addrv6,
|
||||
endpoint.extConnConfig.PortBindings,
|
||||
|
||||
@@ -96,11 +96,6 @@ func (n *bridgeNetwork) addPortMappings(
|
||||
containerIPv6 = epAddrV6.IP
|
||||
}
|
||||
|
||||
disableNAT4, disableNAT6 := n.getNATDisabled()
|
||||
if err := validatePortBindings(cfg, !disableNAT4, !disableNAT6, containerIPv6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bindings := make([]portBinding, 0, len(cfg)*2)
|
||||
|
||||
defer func() {
|
||||
@@ -116,6 +111,7 @@ func (n *bridgeNetwork) addPortMappings(
|
||||
|
||||
proxyPath := n.userlandProxyPath()
|
||||
pdc := n.getPortDriverClient()
|
||||
disableNAT4, disableNAT6 := n.getNATDisabled()
|
||||
|
||||
// toBind accumulates port bindings that should be allocated the same host port
|
||||
// (if required by NAT config). If the host address is unspecified, and defHostIP
|
||||
@@ -128,23 +124,36 @@ func (n *bridgeNetwork) addPortMappings(
|
||||
// bindings to collect, they're applied and toBind is reset.
|
||||
var toBind []portBindingReq
|
||||
for i, c := range sortedCfg {
|
||||
if bindingIPv4, ok := configurePortBindingIPv4(pdc, disableNAT4, c, containerIPv4, defHostIP); ok {
|
||||
if bindingIPv4, ok := configurePortBindingIPv4(ctx, pdc, disableNAT4, c, containerIPv4, defHostIP); ok {
|
||||
toBind = append(toBind, bindingIPv4)
|
||||
}
|
||||
|
||||
// If the container has no IPv6 address, allow proxying host IPv6 traffic to it
|
||||
// by setting up the binding with the IPv4 interface if the userland proxy is enabled
|
||||
// This change was added to keep backward compatibility
|
||||
// TODO(robmry) - this will silently ignore port bindings with an explicit IPv6
|
||||
// host address, when docker-proxy is disabled, and the container is IPv4-only.
|
||||
// If there's no proxying and the container has no IPv6, should probably error if ...
|
||||
// - the mapping's host address is IPv6, or
|
||||
// - the mapping has no host address, but the default address is IPv6.
|
||||
containerIP := containerIPv6
|
||||
if proxyPath != "" && (containerIPv6 == nil) {
|
||||
containerIP = containerIPv4
|
||||
if containerIPv6 == nil {
|
||||
if proxyPath == "" {
|
||||
// There's no way to map from host-IPv6 to container-IPv4 with the userland proxy
|
||||
// disabled.
|
||||
// If that is required, don't treat it as an error because, as networks are
|
||||
// connected/disconnected, the container's gateway endpoint might change to a
|
||||
// network where this config makes more sense.
|
||||
if len(c.HostIP) > 0 && c.HostIP.To4() == nil {
|
||||
log.G(ctx).WithFields(log.Fields{"mapping": c}).Info(
|
||||
"Cannot map from IPv6 to an IPv4-only container because the userland proxy is disabled")
|
||||
}
|
||||
if len(c.HostIP) == 0 && defHostIP.To4() == nil {
|
||||
log.G(ctx).WithFields(log.Fields{
|
||||
"mapping": c,
|
||||
"default": defHostIP,
|
||||
}).Info("Cannot map from default host binding address to an IPv4-only container because the userland proxy is disabled")
|
||||
}
|
||||
} else {
|
||||
containerIP = containerIPv4
|
||||
}
|
||||
}
|
||||
if bindingIPv6, ok := configurePortBindingIPv6(pdc, disableNAT6, c, containerIP, defHostIP); ok {
|
||||
if bindingIPv6, ok := configurePortBindingIPv6(ctx, pdc, disableNAT6, c, containerIP, defHostIP); ok {
|
||||
toBind = append(toBind, bindingIPv6)
|
||||
}
|
||||
|
||||
@@ -237,65 +246,6 @@ func (n *bridgeNetwork) addPortMappings(
|
||||
return bindings, nil
|
||||
}
|
||||
|
||||
// Limit the number of errors reported, because there may be a lot of port
|
||||
// bindings (host port ranges are expanded by the CLI).
|
||||
const validationErrLimit = 6
|
||||
|
||||
// validatePortBindings checks that, if NAT is disabled for all uses of a
|
||||
// PortBinding, no HostPort, or non-zero HostIP, is specified - because they have
|
||||
// no meaning. A zero HostIP is allowed, as it's used to determine the address
|
||||
// family.
|
||||
//
|
||||
// The default binding IP is not considered, meaning that no error is raised if
|
||||
// there is a default binding address that is not used but could have been.
|
||||
//
|
||||
// For example, the default is an IPv6 interface address, no HostIP is specified,
|
||||
// and NAT6 is disabled; the default is ignored and no error will be raised. (Note
|
||||
// that this example may be valid if the container has no IPv6 address, and
|
||||
// docker-proxy is used to forward between the default IPv6 address and the
|
||||
// container's IPv4. So, simply disallowing a non-zero IPv6 default when NAT6
|
||||
// is disabled for the network would be incorrect.)
|
||||
func validatePortBindings(pbs []types.PortBinding, nat4, nat6 bool, cIPv6 net.IP) error {
|
||||
var errs []error
|
||||
for i := range pbs {
|
||||
pb := &pbs[i]
|
||||
disallowHostPort := false
|
||||
if !nat4 && len(pb.HostIP) > 0 && pb.HostIP.To4() != nil && !pb.HostIP.Equal(net.IPv4zero) {
|
||||
// There's no NAT4, so don't allow a nonzero IPv4 host address in the mapping. The port will
|
||||
// be accessible via any host interface.
|
||||
errs = append(errs,
|
||||
fmt.Errorf("NAT is disabled, omit host address in port mapping %s, or use 0.0.0.0::%d to open port %d for IPv4-only",
|
||||
pb, pb.Port, pb.Port))
|
||||
// The mapping is IPv4-specific but there's no NAT4, so a host port would make no sense.
|
||||
disallowHostPort = true
|
||||
} else if !nat6 && len(pb.HostIP) > 0 && pb.HostIP.To4() == nil && !pb.HostIP.Equal(net.IPv6zero) {
|
||||
// If the container has no IPv6 address, the userland proxy will proxy between the
|
||||
// host's IPv6 address and the container's IPv4. So, even with no NAT6, it's ok for
|
||||
// an IPv6 port mapping to include a specific host address or port.
|
||||
if len(cIPv6) > 0 {
|
||||
// There's no NAT6, so don't allow an IPv6 host address in the mapping. The port will
|
||||
// accessible via any host interface.
|
||||
errs = append(errs,
|
||||
fmt.Errorf("NAT is disabled, omit host address in port mapping %s, or use [::]::%d to open port %d for IPv6-only",
|
||||
pb, pb.Port, pb.Port))
|
||||
// The mapping is IPv6-specific but there's no NAT6, so a host port would make no sense.
|
||||
disallowHostPort = true
|
||||
}
|
||||
} else if !nat4 && !nat6 {
|
||||
// There's no NAT, so it would make no sense to specify a host port.
|
||||
disallowHostPort = true
|
||||
}
|
||||
if disallowHostPort && pb.HostPort != 0 {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("host port must not be specified in mapping %s because NAT is disabled", pb))
|
||||
}
|
||||
if len(errs) >= validationErrLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// sortAndNormPBs normalises cfg by making HostPortEnd=HostPort (rather than 0) if the
|
||||
// host port isn't a range - and sorts it into the ordering defined by cmpPortBinding.
|
||||
func sortAndNormPBs(cfg []types.PortBinding) {
|
||||
@@ -360,9 +310,17 @@ func needSamePort(a, b types.PortBinding) bool {
|
||||
a.IP.Equal(b.IP)
|
||||
}
|
||||
|
||||
// configurePortBindingIPv4 returns a new port binding with the HostIP field populated
|
||||
// if a binding is required, else nil.
|
||||
func configurePortBindingIPv4(pdc portDriverClient, disableNAT bool, bnd types.PortBinding, containerIPv4, defHostIP net.IP) (portBindingReq, bool) {
|
||||
// configurePortBindingIPv4 returns a new port binding with the HostIP field
|
||||
// populated and true, if a binding is required. Else, false and an empty
|
||||
// binding.
|
||||
func configurePortBindingIPv4(
|
||||
ctx context.Context,
|
||||
pdc portDriverClient,
|
||||
disableNAT bool,
|
||||
bnd types.PortBinding,
|
||||
containerIPv4,
|
||||
defHostIP net.IP,
|
||||
) (portBindingReq, bool) {
|
||||
if len(containerIPv4) == 0 {
|
||||
return portBindingReq{}, false
|
||||
}
|
||||
@@ -376,8 +334,27 @@ func configurePortBindingIPv4(pdc portDriverClient, disableNAT bool, bnd types.P
|
||||
// The default binding address is IPv6.
|
||||
return portBindingReq{}, false
|
||||
}
|
||||
bnd.HostIP = defHostIP
|
||||
// The default binding IP is an IPv4 address, use it - unless NAT is disabled,
|
||||
// in which case it's not possible to bind to a specific host address (the port
|
||||
// mapping only opens the container's port for direct routing).
|
||||
if disableNAT {
|
||||
bnd.HostIP = net.IPv4zero
|
||||
} else {
|
||||
bnd.HostIP = defHostIP
|
||||
}
|
||||
}
|
||||
|
||||
if disableNAT && len(bnd.HostIP) != 0 && !bnd.HostIP.Equal(net.IPv4zero) {
|
||||
// Ignore the default binding when nat is disabled - it may have been set
|
||||
// up for IPv6 if nat is enabled there.
|
||||
// Don't treat this as an error because, as networks are connected/disconnected,
|
||||
// the container's gateway endpoint might change to a network where this config
|
||||
// makes more sense.
|
||||
log.G(ctx).WithFields(log.Fields{"mapping": bnd}).Info(
|
||||
"Using address 0.0.0.0 because NAT is disabled")
|
||||
bnd.HostIP = net.IPv4zero
|
||||
}
|
||||
|
||||
// Unmap the addresses if they're IPv4-mapped IPv6.
|
||||
bnd.HostIP = bnd.HostIP.To4()
|
||||
bnd.IP = containerIPv4.To4()
|
||||
@@ -387,9 +364,16 @@ func configurePortBindingIPv4(pdc portDriverClient, disableNAT bool, bnd types.P
|
||||
}), true
|
||||
}
|
||||
|
||||
// configurePortBindingIPv6 returns a new port binding with the HostIP field populated
|
||||
// if a binding is required, else nil.
|
||||
func configurePortBindingIPv6(pdc portDriverClient, disableNAT bool, bnd types.PortBinding, containerIP, defHostIP net.IP) (portBindingReq, bool) {
|
||||
// configurePortBindingIPv6 returns a new port binding with the HostIP field
|
||||
// populated and true, if a binding is required. Else, false and an empty
|
||||
// binding.
|
||||
func configurePortBindingIPv6(
|
||||
ctx context.Context,
|
||||
pdc portDriverClient,
|
||||
disableNAT bool,
|
||||
bnd types.PortBinding,
|
||||
containerIP, defHostIP net.IP,
|
||||
) (portBindingReq, bool) {
|
||||
if containerIP == nil {
|
||||
return portBindingReq{}, false
|
||||
}
|
||||
@@ -408,13 +392,31 @@ func configurePortBindingIPv6(pdc portDriverClient, disableNAT bool, bnd types.P
|
||||
// Implicit binding to "::", no explicit HostIP and the default is 0.0.0.0
|
||||
bnd.HostIP = net.IPv6zero
|
||||
} else if defHostIP.To4() == nil {
|
||||
// The default binding IP is an IPv6 address, use it.
|
||||
bnd.HostIP = defHostIP
|
||||
// The default binding IP is an IPv6 address, use it - unless NAT is disabled, in
|
||||
// which case it's not possible to bind to a specific host address (the port
|
||||
// mapping only opens the container's port for direct routing).
|
||||
if disableNAT {
|
||||
bnd.HostIP = net.IPv6zero
|
||||
} else {
|
||||
bnd.HostIP = defHostIP
|
||||
}
|
||||
} else {
|
||||
// The default binding IP is an IPv4 address, nothing to do here.
|
||||
return portBindingReq{}, false
|
||||
}
|
||||
}
|
||||
|
||||
if disableNAT && len(bnd.HostIP) != 0 && !bnd.HostIP.Equal(net.IPv6zero) {
|
||||
// Ignore the default binding when nat is disabled - it may have been set
|
||||
// up for IPv4 if nat is enabled there.
|
||||
// Don't treat this as an error because, as networks are connected/disconnected,
|
||||
// the container's gateway endpoint might change to a network where this config
|
||||
// makes more sense.
|
||||
log.G(ctx).WithFields(log.Fields{"mapping": bnd}).Info(
|
||||
"Using address [::] because NAT is disabled")
|
||||
bnd.HostIP = net.IPv6zero
|
||||
}
|
||||
|
||||
bnd.IP = containerIP
|
||||
return setChildHostIP(pdc, portBindingReq{
|
||||
PortBinding: bnd,
|
||||
@@ -480,7 +482,7 @@ func bindHostPorts(
|
||||
// successfully reserved, a portBinding is returned for each mapping.
|
||||
//
|
||||
// If NAT is disabled for any of the bindings, no host port reservation is
|
||||
// needed. These bindings are included in results, as the container port itself
|
||||
// needed. Include these bindings in results, as the container port itself
|
||||
// needs to be opened in the firewall.
|
||||
func attemptBindHostPorts(
|
||||
ctx context.Context,
|
||||
@@ -539,8 +541,12 @@ func attemptBindHostPorts(
|
||||
var pb portBinding
|
||||
if c.disableNAT {
|
||||
pb = portBinding{PortBinding: c.GetCopy()}
|
||||
pb.HostPort = 0
|
||||
pb.HostPortEnd = 0
|
||||
if pb.HostPort != 0 || pb.HostPortEnd != 0 {
|
||||
log.G(ctx).WithFields(log.Fields{"mapping": pb}).Infof(
|
||||
"Host port ignored, because NAT is disabled")
|
||||
pb.HostPort = 0
|
||||
pb.HostPortEnd = 0
|
||||
}
|
||||
} else {
|
||||
switch proto {
|
||||
case "tcp":
|
||||
|
||||
@@ -12,12 +12,14 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/internal/testutils/netnsutils"
|
||||
"github.com/docker/docker/libnetwork/iptables"
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"github.com/docker/docker/libnetwork/ns"
|
||||
"github.com/docker/docker/libnetwork/portallocator"
|
||||
"github.com/docker/docker/libnetwork/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -186,146 +188,6 @@ func loopbackUp() error {
|
||||
return nlHandle.LinkSetUp(iface)
|
||||
}
|
||||
|
||||
func TestValidatePortBindings(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
nat4 bool
|
||||
nat6 bool
|
||||
ctrIPv6 net.IP
|
||||
pbs []types.PortBinding
|
||||
expErrs []string
|
||||
}{
|
||||
{
|
||||
name: "no nat or addrs or ports",
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat with addrs",
|
||||
ctrIPv6: newIPNet(t, "fd2c:b48c:69fb::2/128").IP,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, Port: 80},
|
||||
},
|
||||
expErrs: []string{
|
||||
"NAT is disabled, omit host address in port mapping 233.252.0.2::80/tcp, or use 0.0.0.0::80 to open port 80 for IPv4-only",
|
||||
"NAT is disabled, omit host address in port mapping [2001:db8::2]::80/tcp, or use [::]::80 to open port 80 for IPv6-only",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat with zero addrs",
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "0.0.0.0/0").IP, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "::/0").IP, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat with host port",
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
expErrs: []string{
|
||||
"host port must not be specified in mapping 8080:80/tcp because NAT is disabled",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nat4 any addr with host port",
|
||||
nat4: true,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nat6 any addr with host port",
|
||||
nat6: true,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nat and addrs and ports",
|
||||
nat4: true,
|
||||
nat6: true,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nat4 and addrs and ports",
|
||||
nat4: true,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat and addrs and ports",
|
||||
ctrIPv6: newIPNet(t, "fd2c:b48c:69fb::2/128").IP,
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
expErrs: []string{
|
||||
"NAT is disabled, omit host address in port mapping 233.252.0.2:8080:80/tcp, or use 0.0.0.0::80 to open port 80 for IPv4-only",
|
||||
"NAT is disabled, omit host address in port mapping [2001:db8::2]:8080:80/tcp, or use [::]::80 to open port 80 for IPv6-only",
|
||||
"host port must not be specified in mapping 233.252.0.2:8080:80/tcp because NAT is disabled",
|
||||
"host port must not be specified in mapping [2001:db8::2]:8080:80/tcp because NAT is disabled",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no nat no ctrIPv6 and addrs and ports",
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "233.252.0.2/24").IP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostIP: newIPNet(t, "2001:db8::2/64").IP, HostPort: 8080, Port: 80},
|
||||
},
|
||||
expErrs: []string{
|
||||
"NAT is disabled, omit host address in port mapping 233.252.0.2:8080:80/tcp, or use 0.0.0.0::80 to open port 80 for IPv4-only",
|
||||
"host port must not be specified in mapping 233.252.0.2:8080:80/tcp because NAT is disabled",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max errs reached",
|
||||
pbs: []types.PortBinding{
|
||||
{Proto: types.TCP, HostPort: 8080, Port: 80},
|
||||
{Proto: types.TCP, HostPort: 8081, Port: 80},
|
||||
{Proto: types.TCP, HostPort: 8082, Port: 80},
|
||||
{Proto: types.TCP, HostPort: 8083, Port: 80},
|
||||
{Proto: types.TCP, HostPort: 8084, Port: 80},
|
||||
{Proto: types.TCP, HostPort: 8085, Port: 80},
|
||||
{Proto: types.TCP, HostPort: 8086, Port: 80},
|
||||
},
|
||||
expErrs: []string{
|
||||
"host port must not be specified in mapping 8080:80/tcp because NAT is disabled",
|
||||
"host port must not be specified in mapping 8081:80/tcp because NAT is disabled",
|
||||
"host port must not be specified in mapping 8082:80/tcp because NAT is disabled",
|
||||
"host port must not be specified in mapping 8083:80/tcp because NAT is disabled",
|
||||
"host port must not be specified in mapping 8084:80/tcp because NAT is disabled",
|
||||
"host port must not be specified in mapping 8085:80/tcp because NAT is disabled",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validatePortBindings(tc.pbs, tc.nat4, tc.nat6, tc.ctrIPv6)
|
||||
if tc.expErrs == nil {
|
||||
assert.Check(t, err)
|
||||
} else {
|
||||
assert.Assert(t, err != nil)
|
||||
for _, e := range tc.expErrs {
|
||||
assert.Check(t, is.ErrorContains(err, e))
|
||||
}
|
||||
numErrs := len(err.(interface{ Unwrap() []error }).Unwrap())
|
||||
assert.Check(t, is.Equal(numErrs, len(tc.expErrs)),
|
||||
fmt.Sprintf("expected %d errors, got %d in %s", len(tc.expErrs), numErrs, err.Error()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmpPortBindings(t *testing.T) {
|
||||
pb := types.PortBinding{
|
||||
Proto: types.TCP,
|
||||
@@ -429,6 +291,7 @@ func TestAddPortMappings(t *testing.T) {
|
||||
hostAddrs []string
|
||||
|
||||
expErr string
|
||||
expLogs []string
|
||||
expPBs []types.PortBinding
|
||||
expProxyRunning bool
|
||||
expReleaseErr string
|
||||
@@ -681,6 +544,22 @@ func TestAddPortMappings(t *testing.T) {
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 80, HostIP: net.IPv6zero},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disable nat6 with ipv6 default binding",
|
||||
epAddrV4: ctrIP4,
|
||||
epAddrV6: ctrIP6,
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22},
|
||||
{Proto: types.TCP, Port: 80},
|
||||
},
|
||||
proxyPath: "/dummy/path/to/proxy",
|
||||
gwMode6: gwModeRouted,
|
||||
defHostIP: net.IPv6loopback,
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero},
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 80, HostIP: net.IPv6zero},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disable nat4",
|
||||
epAddrV4: ctrIP4,
|
||||
@@ -716,6 +595,90 @@ func TestAddPortMappings(t *testing.T) {
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 80, HostIP: net.IPv6zero},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ipv6 mapping to ipv4 container no proxy",
|
||||
epAddrV4: ctrIP4,
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22, HostIP: net.IPv6loopback},
|
||||
},
|
||||
expLogs: []string{"Cannot map from IPv6 to an IPv4-only container because the userland proxy is disabled"},
|
||||
},
|
||||
{
|
||||
name: "ipv6 default mapping to ipv4 container no proxy",
|
||||
epAddrV4: ctrIP4,
|
||||
defHostIP: net.IPv6loopback,
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22},
|
||||
},
|
||||
expLogs: []string{"Cannot map from default host binding address to an IPv4-only container because the userland proxy is disabled"},
|
||||
},
|
||||
{
|
||||
name: "routed mode specific address",
|
||||
epAddrV4: ctrIP4,
|
||||
epAddrV6: ctrIP6,
|
||||
gwMode4: gwModeRouted,
|
||||
gwMode6: gwModeRouted,
|
||||
proxyPath: "/dummy/path/to/proxy",
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22, HostIP: newIPNet(t, "127.0.0.1/8").IP},
|
||||
{Proto: types.TCP, Port: 22, HostIP: net.IPv6loopback},
|
||||
},
|
||||
expLogs: []string{
|
||||
"Using address 0.0.0.0 because NAT is disabled",
|
||||
"Using address [::] because NAT is disabled",
|
||||
},
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero},
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "routed4 nat6 with ipv4 default binding",
|
||||
epAddrV4: ctrIP4,
|
||||
epAddrV6: ctrIP6,
|
||||
gwMode4: gwModeRouted,
|
||||
defHostIP: newIPNet(t, "127.0.0.1/8").IP,
|
||||
proxyPath: "/dummy/path/to/proxy",
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22},
|
||||
},
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "routed4 nat6 with ipv6 default binding",
|
||||
epAddrV4: ctrIP4,
|
||||
epAddrV6: ctrIP6,
|
||||
gwMode4: gwModeRouted,
|
||||
defHostIP: net.IPv6loopback,
|
||||
proxyPath: "/dummy/path/to/proxy",
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22},
|
||||
},
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6loopback, HostPort: firstEphemPort},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "routed with host port",
|
||||
epAddrV4: ctrIP4,
|
||||
epAddrV6: ctrIP6,
|
||||
gwMode4: gwModeRouted,
|
||||
gwMode6: gwModeRouted,
|
||||
cfg: []types.PortBinding{
|
||||
{Proto: types.TCP, Port: 22, HostPort: 2222},
|
||||
},
|
||||
expPBs: []types.PortBinding{
|
||||
{Proto: types.TCP, IP: ctrIP4.IP, Port: 22, HostIP: net.IPv4zero},
|
||||
{Proto: types.TCP, IP: ctrIP6.IP, Port: 22, HostIP: net.IPv6zero},
|
||||
},
|
||||
expLogs: []string{
|
||||
"Host port ignored, because NAT is disabled",
|
||||
"0.0.0.0:2222:172.19.0.2:22/tcp",
|
||||
"[::]:2222:[fdf8:b88e:bb5c:3483::2]:22/tcp",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "same ports for matching mappings with different host addresses",
|
||||
epAddrV4: ctrIP4,
|
||||
@@ -878,12 +841,26 @@ func TestAddPortMappings(t *testing.T) {
|
||||
|
||||
portallocator.Get().ReleaseAll()
|
||||
|
||||
pbs, err := n.addPortMappings(context.Background(), tc.epAddrV4, tc.epAddrV6, tc.cfg, tc.defHostIP)
|
||||
// Capture logs by stashing a new logger in the context.
|
||||
var sb strings.Builder
|
||||
logger := logrus.New()
|
||||
logger.Out = &sb
|
||||
ctx := log.WithLogger(context.Background(), &log.Entry{Logger: logger})
|
||||
t.Cleanup(func() {
|
||||
if t.Failed() {
|
||||
t.Logf("Daemon logs:\n%s", sb.String())
|
||||
}
|
||||
})
|
||||
|
||||
pbs, err := n.addPortMappings(ctx, tc.epAddrV4, tc.epAddrV6, tc.cfg, tc.defHostIP)
|
||||
if tc.expErr != "" {
|
||||
assert.ErrorContains(t, err, tc.expErr)
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
for _, expLog := range tc.expLogs {
|
||||
assert.Check(t, is.Contains(sb.String(), expLog))
|
||||
}
|
||||
assert.Assert(t, is.Len(pbs, len(tc.expPBs)))
|
||||
|
||||
// Check the iptables rules.
|
||||
|
||||
Reference in New Issue
Block a user