Skip to content

Allow 64-bit --ip-range#48322

Merged
robmry merged 1 commit intomoby:masterfrom
robmry:64bit_iprange_fix
Aug 13, 2024
Merged

Allow 64-bit --ip-range#48322
robmry merged 1 commit intomoby:masterfrom
robmry:64bit_iprange_fix

Conversation

@robmry
Copy link
Contributor

@robmry robmry commented Aug 12, 2024

- What I did

Make it possible to use a --ip-range that ends on a 64-bit boundary.

Without this change ...

# docker network create --ipv6 --subnet fddd::/56 --ip-range fddd::/64 b46
Error response from daemon: failed to allocate gateway (): invalid bit range [0, 18446744073709551615)

# docker network create --ipv6 --subnet fddd::/64 --ip-range fddd::/64 b46
Error response from daemon: failed to allocate gateway (): invalid bit range [0, 18446744073709551615)

This is a workaround, although it should solve the issue in practice. I think the remaining issues are unlikely to be a problem (but added TestIPRangeAt64BitLimit to illustrate a limitation). A proper fix might be to make the Bitmap understand ranges >=64-bits.

- How I did it

When defaultipam.newPoolData is asked for a range of 64-bits or more, it ends up with an overflowed u64 - so, it just subtracts one to get a nearly-big-enough range (for a 64-bit subnet).

When defaultipam.getAddress is called with an ipr (sub-pool range), the range it calls bitmask.SetAnyInRange with is exclusive of end. So, its end param can't be MaxUint64, because that's the max value for the top end of the range and, when checking the range, SetAnyInRange fails.

(When fixed-cidr-v6 behaves more like fixed-cidr, it will ask for a 64-bit range if that's what fixed-cidr-v6 needs.)

Work around that limitation of the Bitmap, by matching the off-by-one when allocating a bit from a range.

The additional check for ipr == base avoids the issue in some cases, by ignoring the ipr/sub-pool range if ipr is the same as the pool itself (not really a sub-pool). But, it still fails when ipr!=base. For example:

docker network create --ipv6 --subnet fddd::/56 --ip-range fddd::/64 b46

So, also subtract one from end if it's going to hit the max value allowed by the Bitmap.

- How to verify it

Without this fix, to see the problem, try:

docker network create --ipv6 --subnet fddd::/64 --ip-range fddd::/64 b46

New tests, output ...

=== RUN   Test64BitIPRange
=== RUN   Test64BitIPRange/64-bit-subnet/no-range/no-gateway
=== RUN   Test64BitIPRange/64-bit-subnet/no-range/with-gateway
=== RUN   Test64BitIPRange/64-bit-subnet/64-bit-range/no-gateway
=== RUN   Test64BitIPRange/64-bit-subnet/64-bit-range/with-gateway
=== RUN   Test64BitIPRange/56-bit-subnet/no-range/no-gateway
=== RUN   Test64BitIPRange/56-bit-subnet/no-range/with-gateway
=== RUN   Test64BitIPRange/56-bit-subnet/64-bit-range/no-gateway
=== RUN   Test64BitIPRange/56-bit-subnet/64-bit-range/with-gateway
--- PASS: Test64BitIPRange (0.82s)
    --- PASS: Test64BitIPRange/64-bit-subnet/no-range/no-gateway (0.09s)
    --- PASS: Test64BitIPRange/64-bit-subnet/no-range/with-gateway (0.10s)
    --- PASS: Test64BitIPRange/64-bit-subnet/64-bit-range/no-gateway (0.10s)
    --- PASS: Test64BitIPRange/64-bit-subnet/64-bit-range/with-gateway (0.11s)
    --- PASS: Test64BitIPRange/56-bit-subnet/no-range/no-gateway (0.10s)
    --- PASS: Test64BitIPRange/56-bit-subnet/no-range/with-gateway (0.11s)
    --- PASS: Test64BitIPRange/56-bit-subnet/64-bit-range/no-gateway (0.11s)
    --- PASS: Test64BitIPRange/56-bit-subnet/64-bit-range/with-gateway (0.09s)
=== RUN   TestIPRangeAt64BitLimit
=== RUN   TestIPRangeAt64BitLimit/ipRange_before_end_of_64-bit_subnet
=== RUN   TestIPRangeAt64BitLimit/ipRange_at_end_of_64-bit_subnet
    bridge_test.go:196: XFAIL: Container startup failed with error: Error response from daemon: no available IPv6 addresses on this network's address pools: testnet (6e561e06c66648b1baaa5cb20fd474ee79c98907466ef77e0853537741ebb20c)
=== RUN   TestIPRangeAt64BitLimit/ipRange_at_64-bit_boundary_inside_56-bit_subnet
    bridge_test.go:196: XFAIL: Container startup failed with error: Error response from daemon: no available IPv6 addresses on this network's address pools: testnet (4ee6fa98e8d1feeb2a92e1ffa7f1e422e42174dcce33546fd29d03b60061aad8)
--- PASS: TestIPRangeAt64BitLimit (0.53s)
    --- PASS: TestIPRangeAt64BitLimit/ipRange_before_end_of_64-bit_subnet (0.29s)
    --- SKIP: TestIPRangeAt64BitLimit/ipRange_at_end_of_64-bit_subnet (0.12s)
    --- SKIP: TestIPRangeAt64BitLimit/ipRange_at_64-bit_boundary_inside_56-bit_subnet (0.11s)

- Description for the changelog

n/a - workaround, replaced by https://github.com/moby/moby/pull/49223

@robmry robmry added this to the 28.0.0 milestone Aug 12, 2024
@robmry robmry self-assigned this Aug 12, 2024
@robmry robmry requested a review from akerouanton August 12, 2024 13:56
When defaultipam.newPoolData is asked for a pool of 64-bits
or more, it ends up with an overflowed u64 - so, it just
subtracts one to get a nearly-big-enough range (for a 64-bit
subnet).

When defaultipam.getAddress is called with an ipr (sub-pool
range), the range it calls bitmask.SetAnyInRange with is
exclusive of end. So, its end param can't be MaxUint64,
because that's the max value for the top end of the range
and, when checking the range, SetAnyInRange fails.

When fixed-cidr-v6 behaves more like fixed-cidr, it will ask
for a 64-bit range if that's what fixed-cidr-v6 needs. So,
it hits the bug when allocating an address for, for example:

  docker network create --ipv6 --subnet fddd::/64 --ip-range fddd::/64 b46

The additional check for "ipr == base" avoids the issue in
this case, by ignoring the ipr/sub-pool range if ipr is the
same as the pool itself (not really a sub-pool).

But, it still fails when ipr!=base. For example:

  docker network create --ipv6 --subnet fddd::/56 --ip-range fddd::/64 b46

So, also subtract one from 'end' if it's going to hit the max
value allowed by the Bitmap.

Signed-off-by: Rob Murray <rob.murray@docker.com>
@robmry robmry force-pushed the 64bit_iprange_fix branch from b86622d to 496b457 Compare August 12, 2024 14:37
ctx := setupTest(t)
c := testEnv.APIClient()

type kv struct{ k, v string }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use a map[string]string instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make the order of the tests predictable.

Comment on lines +124 to +126
for _, sn := range subnets {
for _, ipr := range ipRanges {
for _, gw := range gateways {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need to capture these vars in the loop? (not sure if we'd run these with --parallel)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so - the vars aren't used inside the Run().

ctx := testutil.StartSpan(ctx, t)
const netName = "test64br"
network.CreateNoError(ctx, t, c, netName, network.WithIPv6(), ipamSetter)
defer network.RemoveNoError(ctx, t, c, netName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious why a defer here as it's the last statement in the test, or is that mostly to guard against things possibly being added in future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just habit, but I did think about starting/inspecting a container ... maybe we'll want to do that at some point.

Comment on lines 218 to 221
if pool.Addr().Is6() && numAddresses == 0 {
// FIXME(robmry) - see the comment in getAddress
numAddresses--
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OHMAN yeah, this is confusing numAddresses == 0 as the condition .. so let's decrement :confused_travolta:

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bitseq: invalid bit range on IPv6

3 participants