Skip to content

Commit

Permalink
Fixes #42801: Properly shift bytes of IPv6 subnets
Browse files Browse the repository at this point in the history
Prior to this change, IPv6 subnets generation was broken because of a
bitwise shift overflowing uint64.

To solve this issue, addIntToIP has been changed to take an addend and
computes two ordinals applied to respectively the lower 64 bits and the
upper 64 bits of IPv6 addresses.

Nothing change for IPv4 (ie. upperOrdinal is always 0).

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
  • Loading branch information
akerouanton committed Jun 11, 2022
1 parent bd81d7b commit 1f47715
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 4 deletions.
39 changes: 35 additions & 4 deletions libnetwork/ipamutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,14 @@ func NewNetworkSplitter(networks []*NetworkToSplit) (*NetworkSplitter, error) {

network.baseIPNet = b
network.mask = net.CIDRMask(network.Size, bits)
network.maxOffset = 1 << uint(network.Size-ones)

// When there's more than 2^64 subnets that could be generated from a network, just cap maxOffset to 2^64 as
// this is already a number that couldn't be realistically reached.
if network.Size-ones > 64 {
network.maxOffset = 1<<64 - 1
} else {
network.maxOffset = 1 << uint(network.Size-ones)
}

if bits == 32 {
network.ipVersion = IPv4
Expand Down Expand Up @@ -200,10 +207,12 @@ func (ns *NetworkSplitter) Get() *net.IPNet {
s := uint(bits - network.Size)

ip := copyIP(base.IP)
addIntToIP(ip, uint(ns.offset<<s))
addIntToIP(ip, ns.offset, s)
newNet := &net.IPNet{IP: ip, Mask: mask}

ns.offset = ns.offset + 1
// Note that network.maxOffset is exclusive, ie. iterations are on the range [0, maxOffset). Thus, incrementing
// offset first and then checking if it's equal to network.maxOffset is okay.
ns.offset++
if ns.offset == network.maxOffset && ns.index < len(ns.networks) {
ns.index = ns.index + 1
ns.offset = 0
Expand All @@ -218,9 +227,31 @@ func copyIP(from net.IP) net.IP {
return ip
}

func addIntToIP(array net.IP, ordinal uint) {
// addIntToIP takes an uint64 addend to add to the IP address and s, the number of bits of the host identifier space.
// For instance a /96 give 32 bits to encode host identifiers, so s = 32.
func addIntToIP(array net.IP, addend uint64, s uint) {
// lowerOrdinal will overflow when the number of bits addend takes plus the position shift exceeds 63, thus it has
// to be broken down into two parts.
var lowerOrdinal uint64
var upperOrdinal uint64
if s >= 64 {
lowerOrdinal = 0
upperOrdinal = addend << (s - 64)
} else {
// To compute lowerOrdinal, keep only the n last bits of addend where n is the maximum number of bits that
// won't overflow the ordinal once shifted to the left by s. That is, n = 64 - s.
lowerOrdinal = addend & (1<<(64-s) - 1) << s
// upperOrdinal takes the remainder of lowerOrdinal (ie. the higher bits discarded from lowerOrdinal because
// they would have overflown).
upperOrdinal = addend >> (64 - s)
}

ordinal := lowerOrdinal
for i := len(array) - 1; i >= 0; i-- {
array[i] |= (byte)(ordinal & 0xff)
ordinal >>= 8
if i == 8 {
ordinal = upperOrdinal
}
}
}
47 changes: 47 additions & 0 deletions libnetwork/ipamutils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,50 @@ func TestInitAddressPools(t *testing.T) {
assert.Check(t, is.Equal(nets[383].String(), "172.90.127.0/24"))
assert.Check(t, is.Equal(nets[511].String(), "172.90.255.0/24"))
}

func TestInitPoolWithIPv6(t *testing.T) {
err := ConfigLocalScopeDefaultNetworks([]*NetworkToSplit{
{Base: "2001:db8:1:1f00::/64", Size: 96},
{Base: "fd00::/8", Size: 96},
{Base: "fd00::/16", Size: 32},
{Base: "fd00:0:0:fff0::/63", Size: 65},
{Base: "fd00::/16", Size: 72},
})
assert.NilError(t, err)

// Iterating overPredefinedLocalScopeDefaultNetworks would be way too long,
// so better change the offset of the NetworkSplitter and see what
// Get() returns for the last offset of the NetworkSplitter.
PredefinedLocalScopeDefaultNetworks.offset = (1 << 32) - 1
nw := PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "2001:db8:1:1f00:ffff:ffff::/96"))

PredefinedLocalScopeDefaultNetworks.index = 1
PredefinedLocalScopeDefaultNetworks.offset = (1 << 64) - 1
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:0:ffff:ffff:ffff:ffff::/96"))

PredefinedLocalScopeDefaultNetworks.offset = (1 << 64) - 1
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:0:ffff:ffff:ffff:ffff::/96"))

PredefinedLocalScopeDefaultNetworks.index = 2
PredefinedLocalScopeDefaultNetworks.offset = (1 << 16) - 3
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:fffd::/32"))

PredefinedLocalScopeDefaultNetworks.index = 3
PredefinedLocalScopeDefaultNetworks.offset = 1
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:0:0:fff0:8000::/65"))

PredefinedLocalScopeDefaultNetworks.index = 3
PredefinedLocalScopeDefaultNetworks.offset = 2
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:0:0:fff1::/65"))

PredefinedLocalScopeDefaultNetworks.index = 4
PredefinedLocalScopeDefaultNetworks.offset = 2000
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:0:0:7:d000::/72"))
}

0 comments on commit 1f47715

Please sign in to comment.