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)
|
||||
|
||||
if nw != nil {
|
||||
_, _, v4Configs, v6Configs := nw.IpamConfig()
|
||||
errs = validateIPAMConfigIsInRange(errs, ipamConfig, v4Configs, v6Configs)
|
||||
v4Info, v6Info := nw.IpamInfo()
|
||||
errs = validateIPAMConfigIsInRange(errs, ipamConfig, v4Info, v6Info)
|
||||
}
|
||||
|
||||
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.
|
||||
func validateIPAMConfigIsInRange(errs []error, cfg *networktypes.EndpointIPAMConfig, v4Subnets, v6Subnets []*libnetwork.IpamConf) []error {
|
||||
if err := validateEndpointIPAddress(cfg.IPv4Address, v4Subnets); err != nil {
|
||||
func validateIPAMConfigIsInRange(errs []error, cfg *networktypes.EndpointIPAMConfig, v4Info, v6Info []*libnetwork.IpamInfo) []error {
|
||||
if err := validateEndpointIPAddress(cfg.IPv4Address, v4Info); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := validateEndpointIPAddress(cfg.IPv6Address, v6Subnets); err != nil {
|
||||
if err := validateEndpointIPAddress(cfg.IPv6Address, v6Info); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateEndpointIPAddress(epAddr netip.Addr, ipamSubnets []*libnetwork.IpamConf) error {
|
||||
func validateEndpointIPAddress(epAddr netip.Addr, ipamInfo []*libnetwork.IpamInfo) error {
|
||||
if !epAddr.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var staticSubnet bool
|
||||
for _, subnet := range ipamSubnets {
|
||||
if subnet.IsStatic() {
|
||||
staticSubnet = true
|
||||
if subnet.Contains(epAddr) {
|
||||
for _, subnet := range ipamInfo {
|
||||
if subnet.Pool.Contains(epAddr.AsSlice()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if staticSubnet {
|
||||
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")
|
||||
return fmt.Errorf("no configured subnet contains IP address %s", epAddr)
|
||||
}
|
||||
|
||||
// cleanOperationalData resets the operational data from the passed endpoint settings
|
||||
|
||||
@@ -3,6 +3,7 @@ package daemon
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
networktypes "github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/v2/daemon/container"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork"
|
||||
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@@ -57,12 +59,12 @@ func buildNetwork(t *testing.T, config map[string]any) *libnetwork.Network {
|
||||
return nw
|
||||
}
|
||||
|
||||
func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
||||
func TestEndpointIPAMInfoWithOutOfRangeAddrs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ipamConfig *networktypes.EndpointIPAMConfig
|
||||
v4Subnets []*libnetwork.IpamConf
|
||||
v6Subnets []*libnetwork.IpamConf
|
||||
v4Pool string
|
||||
v6Pool string
|
||||
expectedErrors []string
|
||||
}{
|
||||
{
|
||||
@@ -72,12 +74,8 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
||||
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")},
|
||||
},
|
||||
v4Subnets: []*libnetwork.IpamConf{
|
||||
{PreferredPool: "192.168.100.0/24"},
|
||||
},
|
||||
v6Subnets: []*libnetwork.IpamConf{
|
||||
{PreferredPool: "2a01:d2:af:420b:25c1:1816:bb33::/112"},
|
||||
},
|
||||
v4Pool: "192.168.100.0/24",
|
||||
v6Pool: "2a01:d2:af:420b:25c1:1816:bb33::/112",
|
||||
},
|
||||
{
|
||||
name: "static addresses out of range",
|
||||
@@ -85,39 +83,28 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
||||
IPv4Address: netip.MustParseAddr("192.168.100.10"),
|
||||
IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"),
|
||||
},
|
||||
v4Subnets: []*libnetwork.IpamConf{
|
||||
{PreferredPool: "192.168.255.0/24"},
|
||||
},
|
||||
v6Subnets: []*libnetwork.IpamConf{
|
||||
{PreferredPool: "2001:db8::/112"},
|
||||
},
|
||||
v4Pool: "192.168.255.0/24",
|
||||
v6Pool: "2001:db8::/112",
|
||||
expectedErrors: []string{
|
||||
"no configured subnet or ip-range contain the IP address 192.168.100.10",
|
||||
"no configured subnet or ip-range contain the 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",
|
||||
"no configured subnet contains IP address 192.168.100.10",
|
||||
"no configured subnet contains IP address 2a01:d2:af:420b:25c1:1816:bb33:855c",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
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 {
|
||||
assert.NilError(t, errors.Join(errs...))
|
||||
return
|
||||
@@ -125,7 +112,7 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
|
||||
|
||||
assert.Check(t, len(errs) == len(tc.expectedErrors), "errs: %+v", errs)
|
||||
|
||||
err := errors.Join(errs...)
|
||||
err = errors.Join(errs...)
|
||||
for _, expected := range tc.expectedErrors {
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
assertNwNotAvailable(c, "n0")
|
||||
}
|
||||
|
||||
@@ -2058,3 +2058,35 @@ func TestDNSNamesForNonSwarmScopedNetworks(t *testing.T) {
|
||||
container.WithAutoRemove)
|
||||
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