While the networkRange function calculates lastIP, it uses
net.IPv4zero or net.IPv6zero as a starting value from which it
builds the final last IP. The issue is that these are global
variables defined in the standard library, and modifying them affects
all future users of those variables across the program.
This leads to a not-so-rare race condition when creating multiple
libvirt networks. These networks are being created concurrently and they
both race modifying that global variable. Their lastIP DHCP
configuration gets mixed up as result. The manner in which the
configuration gets mixed up is unpredictable, but it could lead to a
corrupt configuration that cannot be applied.
The solution is to copy the zero IP addresses to a new variable rather
than use them directly. Instead for simplicity I just instantiate an
empty slice with the same length as the net IP, which would be filled
with zeroes anyway.
This commit also solves another unrelated bug in getNetworkIPConfig
where the ^ operator was used as if it's a power operator, while in
reality it's a bitwise-xor that leads to slightly incorrect results.
This makes the validation only slightly wrong and is overly strict (e.g.
it will not allow you to create /28 IPv4 network even though such
network has far more available addresses than the condition blocks)
A similar race condition can be simply reproduced and visualized with
this short go code:
package main
import (
"fmt"
"net"
"sync"
)
func getNetMaskWithMax16Bits(m net.IPMask) net.IPMask {
ones, bits := m.Size()
if bits-ones > 16 {
if bits == 128 {
return net.CIDRMask(128-16, 128)
}
return net.CIDRMask(32-16, 32)
}
return m
}
func networkRange(network *net.IPNet) (net.IP, net.IP) {
netIP := network.IP.To4()
lastIP := net.IPv4zero.To4()
if netIP == nil {
netIP = network.IP.To16()
lastIP = net.IPv6zero.To16()
}
firstIP := netIP.Mask(network.Mask)
intMask := getNetMaskWithMax16Bits(network.Mask)
for i := 0; i < len(lastIP); i++ {
lastIP[i] = netIP[i] | ^intMask[i]
}
return firstIP, lastIP
}
func update(wg *sync.WaitGroup, cidr string, id int) {
address := cidr
_, ipNet, _ := net.ParseCIDR(address)
start, end := networkRange(ipNet)
start[len(start)-1]++
start[len(start)-1]++
end[len(end)-1]--
fmt.Printf("Start %d: %s\n", id, start.String())
fmt.Printf("End %d: %s\n", id, end.String())
wg.Done()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go update(&wg, "192.168.145.0/24", 0)
go update(&wg, "192.168.127.0/24", 1)
wg.Wait()
}
While the
networkRange
function calculateslastIP
, it usesnet.IPv4zero
ornet.IPv6zero
as a starting value from which it builds the final last IP. The issue is that these are global variables defined in the standard library, and modifying them affects all future users of those variables across the program.This leads to a not-so-rare race condition when creating multiple libvirt networks. These networks are being created concurrently and they both race modifying that global variable. Their
lastIP
DHCP configuration gets mixed up as result. The manner in which the configuration gets mixed up is unpredictable, but it could lead to a corrupt configuration that cannot be applied.The solution is to copy the zero IP addresses to a new variable rather than use them directly. Instead for simplicity I just instantiate an empty slice with the same length as the net IP, which would be filled with zeroes anyway.
This commit also solves another unrelated bug in
getNetworkIPConfig
where the^
operator was used as if it's a power operator, while in reality it's a bitwise-xor that leads to slightly incorrect results. This makes the validation only slightly wrong and is overly strict (e.g. it will not allow you to create/28
IPv4 network even though such network has far more available addresses than the condition blocks)A similar race condition can be simply reproduced and visualized with this short go code:
Then run:
To see it happen for short moments.
Alternatively, create this
main.tf
file:And run:
Until the bug reproduces with the following error:
Please make sure you read the contributor documentation before opening a Pull Request.