a73x

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