internal/agent/ipalloc/ipalloc.go
Ref: Size: 1.6 KiB History
// Package ipalloc allocates VM IPs within the host's bridge CIDR.
// The network address (first) is reserved, the gateway takes the next, and the
// subnet broadcast (last) is reserved. For the /24 the server assigns today
// those are .0, .1 and .255 respectively.
package ipalloc
import (
"fmt"
"net/netip"
)
func Alloc(cidr string, used []string) (string, error) {
prefix, err := netip.ParsePrefix(cidr)
if err != nil {
return "", err
}
if !prefix.Addr().Is4() {
return "", fmt.Errorf("bridge CIDR must be IPv4, got %s", cidr)
}
inUse := map[string]bool{}
for _, u := range used {
inUse[u] = true
}
network := prefix.Masked().Addr()
broadcast := broadcastAddr(prefix) // subnet-relative, not hardcoded .255
addr := network.Next().Next() // skip network + gateway
for prefix.Contains(addr) {
if addr != broadcast && !inUse[addr.String()] {
return addr.String(), nil
}
addr = addr.Next()
}
return "", fmt.Errorf("no free IP in %s", cidr)
}
// broadcastAddr returns the all-host-bits-set (broadcast) address of an IPv4
// prefix — e.g. 10.0.5.255 for 10.0.5.0/24, 10.0.0.63 for 10.0.0.0/26. The old
// code hardcoded a last octet of 255, which is only the broadcast for /24 or
// shorter; a /25–/30 bridge CIDR would otherwise hand out its true broadcast.
func broadcastAddr(p netip.Prefix) netip.Addr {
b := p.Masked().Addr().As4()
n := uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
host := uint32(0xffffffff) >> uint(p.Bits()) // low (32-bits) host bits set
n |= host
return netip.AddrFrom4([4]byte{byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)})
}