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 authored and Albin Kerouanton committed Dec 27, 2022
1 parent 79a477f commit a2f5fed
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 4 deletions.
38 changes: 34 additions & 4 deletions libnetwork/ipamutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,13 @@ func NewNetworkSplitter(networks []*NetworkToSplit) (*NetworkSplitter, error) {

network.baseIPNet = b
network.mask = net.CIDRMask(network.Size, bits)
network.maxOffset = 1<<uint(network.Size-ones) - 1
// 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) - 1
}

if bits == 32 {
network.ipVersion = IPv4
Expand All @@ -150,7 +156,7 @@ func NewNetworkSplitter(networks []*NetworkToSplit) (*NetworkSplitter, error) {
}

// Copy returns a new NetworkSplitter with the same networks to split but with its internal position reset.
// This method is primarly used to duplicate NetworkSplitter in tests.
// This method is primarily used to duplicate NetworkSplitter in tests.
func (ns *NetworkSplitter) Copy() *NetworkSplitter {
return &NetworkSplitter{
networks: ns.networks,
Expand Down Expand Up @@ -208,9 +214,11 @@ 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}

// Note that network.maxOffset is inclusive, ie. iterations are on the range [0, maxOffset]. Thus, incrementing
// offset before could result into an overflow.
if ns.offset == network.maxOffset {
ns.index++
ns.offset = 0
Expand All @@ -227,9 +235,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
}
}
}
55 changes: 55 additions & 0 deletions libnetwork/ipamutils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,58 @@ 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 over PredefinedLocalScopeDefaultNetworks 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"))

// Previous Get() call incremented index by 1. Next call yields a subnet from the 3rd NetworkToSplit.
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00::/32"))

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"))

PredefinedLocalScopeDefaultNetworks.offset = 1<<56 - 1
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Equal(nw.String(), "fd00:ffff:ffff:ffff:ff00::/72"))

// After the splitter yields the last subnet from the last NetworkToSplit, it should then yield nil.
nw = PredefinedLocalScopeDefaultNetworks.Get()
assert.Check(t, is.Nil(nw))
}

0 comments on commit a2f5fed

Please sign in to comment.