mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Allow configured address with no configured subnet
Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
@@ -540,8 +540,8 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n
|
|||||||
errs = normalizeEndpointIPAMConfig(errs, ipamConfig)
|
errs = normalizeEndpointIPAMConfig(errs, ipamConfig)
|
||||||
|
|
||||||
if nw != nil {
|
if nw != nil {
|
||||||
_, _, v4Configs, v6Configs := nw.IpamConfig()
|
v4Info, v6Info := nw.IpamInfo()
|
||||||
errs = validateIPAMConfigIsInRange(errs, ipamConfig, v4Configs, v6Configs)
|
errs = validateIPAMConfigIsInRange(errs, ipamConfig, v4Info, v6Info)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sysctls, ok := epConfig.DriverOpts[netlabel.EndpointSysctls]; ok {
|
if sysctls, ok := epConfig.DriverOpts[netlabel.EndpointSysctls]; ok {
|
||||||
@@ -598,36 +598,28 @@ func normalizeEndpointIPAMConfig(errs []error, cfg *networktypes.EndpointIPAMCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validateIPAMConfigIsInRange checks whether static IP addresses are valid in a specific network.
|
// validateIPAMConfigIsInRange checks whether static IP addresses are valid in a specific network.
|
||||||
func validateIPAMConfigIsInRange(errs []error, cfg *networktypes.EndpointIPAMConfig, v4Subnets, v6Subnets []*libnetwork.IpamConf) []error {
|
func validateIPAMConfigIsInRange(errs []error, cfg *networktypes.EndpointIPAMConfig, v4Info, v6Info []*libnetwork.IpamInfo) []error {
|
||||||
if err := validateEndpointIPAddress(cfg.IPv4Address, v4Subnets); err != nil {
|
if err := validateEndpointIPAddress(cfg.IPv4Address, v4Info); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
if err := validateEndpointIPAddress(cfg.IPv6Address, v6Subnets); err != nil {
|
if err := validateEndpointIPAddress(cfg.IPv6Address, v6Info); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateEndpointIPAddress(epAddr netip.Addr, ipamSubnets []*libnetwork.IpamConf) error {
|
func validateEndpointIPAddress(epAddr netip.Addr, ipamInfo []*libnetwork.IpamInfo) error {
|
||||||
if !epAddr.IsValid() {
|
if !epAddr.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var staticSubnet bool
|
for _, subnet := range ipamInfo {
|
||||||
for _, subnet := range ipamSubnets {
|
if subnet.Pool.Contains(epAddr.AsSlice()) {
|
||||||
if subnet.IsStatic() {
|
|
||||||
staticSubnet = true
|
|
||||||
if subnet.Contains(epAddr) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if staticSubnet {
|
return fmt.Errorf("no configured subnet contains IP address %s", epAddr)
|
||||||
return fmt.Errorf("no configured subnet or ip-range contain the IP address %s", epAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New("user specified IP address is supported only when connecting to networks with user configured subnets")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanOperationalData resets the operational data from the passed endpoint settings
|
// cleanOperationalData resets the operational data from the passed endpoint settings
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package daemon
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
networktypes "github.com/moby/moby/api/types/network"
|
networktypes "github.com/moby/moby/api/types/network"
|
||||||
"github.com/moby/moby/v2/daemon/container"
|
"github.com/moby/moby/v2/daemon/container"
|
||||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||||
|
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
@@ -57,12 +59,12 @@ func buildNetwork(t *testing.T, config map[string]any) *libnetwork.Network {
|
|||||||
return nw
|
return nw
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
func TestEndpointIPAMInfoWithOutOfRangeAddrs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ipamConfig *networktypes.EndpointIPAMConfig
|
ipamConfig *networktypes.EndpointIPAMConfig
|
||||||
v4Subnets []*libnetwork.IpamConf
|
v4Pool string
|
||||||
v6Subnets []*libnetwork.IpamConf
|
v6Pool string
|
||||||
expectedErrors []string
|
expectedErrors []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -72,12 +74,8 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
|||||||
IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"),
|
IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"),
|
||||||
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254"), netip.MustParseAddr("fe80::42:a8ff:fe33:6230")},
|
LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254"), netip.MustParseAddr("fe80::42:a8ff:fe33:6230")},
|
||||||
},
|
},
|
||||||
v4Subnets: []*libnetwork.IpamConf{
|
v4Pool: "192.168.100.0/24",
|
||||||
{PreferredPool: "192.168.100.0/24"},
|
v6Pool: "2a01:d2:af:420b:25c1:1816:bb33::/112",
|
||||||
},
|
|
||||||
v6Subnets: []*libnetwork.IpamConf{
|
|
||||||
{PreferredPool: "2a01:d2:af:420b:25c1:1816:bb33::/112"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "static addresses out of range",
|
name: "static addresses out of range",
|
||||||
@@ -85,39 +83,28 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
|||||||
IPv4Address: netip.MustParseAddr("192.168.100.10"),
|
IPv4Address: netip.MustParseAddr("192.168.100.10"),
|
||||||
IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"),
|
IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"),
|
||||||
},
|
},
|
||||||
v4Subnets: []*libnetwork.IpamConf{
|
v4Pool: "192.168.255.0/24",
|
||||||
{PreferredPool: "192.168.255.0/24"},
|
v6Pool: "2001:db8::/112",
|
||||||
},
|
|
||||||
v6Subnets: []*libnetwork.IpamConf{
|
|
||||||
{PreferredPool: "2001:db8::/112"},
|
|
||||||
},
|
|
||||||
expectedErrors: []string{
|
expectedErrors: []string{
|
||||||
"no configured subnet or ip-range contain the IP address 192.168.100.10",
|
"no configured subnet contains IP address 192.168.100.10",
|
||||||
"no configured subnet or ip-range contain the IP address 2a01:d2:af:420b:25c1:1816:bb33:855c",
|
"no configured subnet contains IP address 2a01:d2:af:420b:25c1:1816:bb33:855c",
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "static addresses with dynamic network subnets",
|
|
||||||
ipamConfig: &networktypes.EndpointIPAMConfig{
|
|
||||||
IPv4Address: netip.MustParseAddr("192.168.100.10"),
|
|
||||||
IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"),
|
|
||||||
},
|
|
||||||
v4Subnets: []*libnetwork.IpamConf{
|
|
||||||
{},
|
|
||||||
},
|
|
||||||
v6Subnets: []*libnetwork.IpamConf{
|
|
||||||
{},
|
|
||||||
},
|
|
||||||
expectedErrors: []string{
|
|
||||||
"user specified IP address is supported only when connecting to networks with user configured subnets",
|
|
||||||
"user specified IP address is supported only when connecting to networks with user configured subnets",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
errs := validateIPAMConfigIsInRange(nil, tc.ipamConfig, tc.v4Subnets, tc.v6Subnets)
|
_, v4Pool, err := net.ParseCIDR(tc.v4Pool)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
v4Info := []*libnetwork.IpamInfo{
|
||||||
|
{IPAMData: driverapi.IPAMData{Pool: v4Pool}},
|
||||||
|
}
|
||||||
|
_, v6Pool, err := net.ParseCIDR(tc.v6Pool)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
v6Info := []*libnetwork.IpamInfo{
|
||||||
|
{IPAMData: driverapi.IPAMData{Pool: v6Pool}},
|
||||||
|
}
|
||||||
|
errs := validateIPAMConfigIsInRange(nil, tc.ipamConfig, v4Info, v6Info)
|
||||||
if tc.expectedErrors == nil {
|
if tc.expectedErrors == nil {
|
||||||
assert.NilError(t, errors.Join(errs...))
|
assert.NilError(t, errors.Join(errs...))
|
||||||
return
|
return
|
||||||
@@ -125,7 +112,7 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
|||||||
|
|
||||||
assert.Check(t, len(errs) == len(tc.expectedErrors), "errs: %+v", errs)
|
assert.Check(t, len(errs) == len(tc.expectedErrors), "errs: %+v", errs)
|
||||||
|
|
||||||
err := errors.Join(errs...)
|
err = errors.Join(errs...)
|
||||||
for _, expected := range tc.expectedErrors {
|
for _, expected := range tc.expectedErrors {
|
||||||
assert.Check(t, is.ErrorContains(err, expected))
|
assert.Check(t, is.ErrorContains(err, expected))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1357,10 +1357,10 @@ func (s *DockerNetworkSuite) TestDockerNetworkUnsupportedRequiredIP(c *testing.T
|
|||||||
|
|
||||||
out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
|
out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
|
||||||
assert.Assert(c, err != nil, "out: %s", out)
|
assert.Assert(c, err != nil, "out: %s", out)
|
||||||
assert.Assert(c, is.Contains(out, "user specified IP address is supported only when connecting to networks with user configured subnets"))
|
assert.Assert(c, is.Contains(out, "no configured subnet contains IP address 172.28.99.88"))
|
||||||
out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
|
out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
|
||||||
assert.Assert(c, err != nil, "out: %s", out)
|
assert.Assert(c, err != nil, "out: %s", out)
|
||||||
assert.Assert(c, is.Contains(out, "user specified IP address is supported only when connecting to networks with user configured subnets"))
|
assert.Assert(c, is.Contains(out, "no configured subnet contains IP address 2001:db8:1234::9988"))
|
||||||
cli.DockerCmd(c, "network", "rm", "n0")
|
cli.DockerCmd(c, "network", "rm", "n0")
|
||||||
assertNwNotAvailable(c, "n0")
|
assertNwNotAvailable(c, "n0")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2058,3 +2058,35 @@ func TestDNSNamesForNonSwarmScopedNetworks(t *testing.T) {
|
|||||||
container.WithAutoRemove)
|
container.WithAutoRemove)
|
||||||
assert.Equal(t, res.ExitCode, 0, "exit code: %d, expected 0; stdout:\n%s", res.ExitCode, res.Stdout)
|
assert.Equal(t, res.ExitCode, 0, "exit code: %d, expected 0; stdout:\n%s", res.ExitCode, res.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that when a network is created with no --subnet, a container can be
|
||||||
|
// started with a --ip in the subnet allocated from the default pools.
|
||||||
|
//
|
||||||
|
// Regression test for https://github.com/moby/moby/issues/51569
|
||||||
|
func TestSetIPWithNoConfiguredSubnet(t *testing.T) {
|
||||||
|
ctx := setupTest(t)
|
||||||
|
c := testEnv.APIClient()
|
||||||
|
|
||||||
|
const bridgeName = "subnet-from-pools"
|
||||||
|
network.CreateNoError(ctx, t, c, bridgeName, network.WithIPv6())
|
||||||
|
defer network.RemoveNoError(ctx, t, c, bridgeName)
|
||||||
|
|
||||||
|
insp := network.InspectNoError(ctx, t, c, bridgeName, client.NetworkInspectOptions{})
|
||||||
|
assert.Assert(t, is.Len(insp.Network.IPAM.Config, 2))
|
||||||
|
ip4 := insp.Network.IPAM.Config[0].Subnet.Addr().Next().Next().String()
|
||||||
|
ip6 := insp.Network.IPAM.Config[1].Subnet.Addr().Next().Next().String()
|
||||||
|
if insp.Network.IPAM.Config[0].Subnet.Addr().Is6() {
|
||||||
|
ip4, ip6 = ip6, ip4
|
||||||
|
}
|
||||||
|
|
||||||
|
res := container.RunAttach(ctx, t, c,
|
||||||
|
container.WithCmd("ip", "addr", "show", "eth0"),
|
||||||
|
container.WithNetworkMode(bridgeName),
|
||||||
|
container.WithIPv4(bridgeName, ip4),
|
||||||
|
container.WithIPv6(bridgeName, ip6),
|
||||||
|
)
|
||||||
|
if assert.Check(t, is.Equal(res.ExitCode, 0)) {
|
||||||
|
assert.Check(t, is.Contains(res.Stdout.String(), ip4))
|
||||||
|
assert.Check(t, is.Contains(res.Stdout.String(), ip6))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user