Files
Karl Clinger 40ed108dd5 Volt VMM (Neutron Stardust): source-available under AGPSL v5.0
KVM-based microVMM for the Volt platform:
- Sub-second VM boot times
- Minimal memory footprint
- Landlock LSM + seccomp security
- Virtio device support
- Custom kernel management

Copyright (c) Armored Gates LLC. All rights reserved.
Licensed under AGPSL v5.0
2026-03-21 01:04:35 -05:00

350 lines
7.4 KiB
Go

package unified
import (
"encoding/binary"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"sync"
"time"
)
// IPAM manages IP address allocation for networks
type IPAM struct {
stateDir string
pools map[string]*Pool
mu sync.RWMutex
}
// Pool represents an IP address pool for a network
type Pool struct {
// Network name
Name string `json:"name"`
// Subnet
Subnet *net.IPNet `json:"subnet"`
// Gateway address
Gateway net.IP `json:"gateway"`
// Pool start (first allocatable address)
Start net.IP `json:"start"`
// Pool end (last allocatable address)
End net.IP `json:"end"`
// Static reservations (workloadID -> IP)
Reservations map[string]net.IP `json:"reservations"`
// Active leases
Leases map[string]*Lease `json:"leases"`
// Free IPs (bitmap for fast allocation)
allocated map[uint32]bool
}
// NewIPAM creates a new IPAM instance
func NewIPAM(stateDir string) (*IPAM, error) {
if err := os.MkdirAll(stateDir, 0755); err != nil {
return nil, fmt.Errorf("create IPAM state dir: %w", err)
}
ipam := &IPAM{
stateDir: stateDir,
pools: make(map[string]*Pool),
}
// Load existing state
if err := ipam.loadState(); err != nil {
// Non-fatal, might be first run
_ = err
}
return ipam, nil
}
// AddPool adds a new IP pool for a network
func (i *IPAM) AddPool(name string, subnet *net.IPNet, gateway net.IP, reservations map[string]net.IP) error {
i.mu.Lock()
defer i.mu.Unlock()
// Calculate pool range
start := nextIP(subnet.IP)
if gateway != nil && gateway.Equal(start) {
start = nextIP(start)
}
// Broadcast address is last in subnet
end := lastIP(subnet)
pool := &Pool{
Name: name,
Subnet: subnet,
Gateway: gateway,
Start: start,
End: end,
Reservations: reservations,
Leases: make(map[string]*Lease),
allocated: make(map[uint32]bool),
}
// Mark gateway as allocated
if gateway != nil {
pool.allocated[ipToUint32(gateway)] = true
}
// Mark reservations as allocated
for _, ip := range reservations {
pool.allocated[ipToUint32(ip)] = true
}
i.pools[name] = pool
return i.saveState()
}
// Allocate allocates an IP address for a workload
func (i *IPAM) Allocate(network, workloadID string, mac net.HardwareAddr) (*Lease, error) {
i.mu.Lock()
defer i.mu.Unlock()
pool, ok := i.pools[network]
if !ok {
return nil, fmt.Errorf("network %s not found", network)
}
// Check if workload already has a lease
if lease, ok := pool.Leases[workloadID]; ok {
return lease, nil
}
// Check for static reservation
if ip, ok := pool.Reservations[workloadID]; ok {
lease := &Lease{
IP: ip,
MAC: mac,
WorkloadID: workloadID,
Start: time.Now(),
Expires: time.Now().Add(365 * 24 * time.Hour), // Long lease for static
Static: true,
}
pool.Leases[workloadID] = lease
pool.allocated[ipToUint32(ip)] = true
_ = i.saveState()
return lease, nil
}
// Find free IP in pool
ip, err := pool.findFreeIP()
if err != nil {
return nil, err
}
lease := &Lease{
IP: ip,
MAC: mac,
WorkloadID: workloadID,
Start: time.Now(),
Expires: time.Now().Add(24 * time.Hour), // Default 24h lease
Static: false,
}
pool.Leases[workloadID] = lease
pool.allocated[ipToUint32(ip)] = true
_ = i.saveState()
return lease, nil
}
// Release releases an IP address allocation
func (i *IPAM) Release(network, workloadID string) error {
i.mu.Lock()
defer i.mu.Unlock()
pool, ok := i.pools[network]
if !ok {
return nil // Network doesn't exist, nothing to release
}
lease, ok := pool.Leases[workloadID]
if !ok {
return nil // No lease, nothing to release
}
// Don't release static reservations from allocated map
if !lease.Static {
delete(pool.allocated, ipToUint32(lease.IP))
}
delete(pool.Leases, workloadID)
return i.saveState()
}
// GetLease returns the current lease for a workload
func (i *IPAM) GetLease(network, workloadID string) (*Lease, error) {
i.mu.RLock()
defer i.mu.RUnlock()
pool, ok := i.pools[network]
if !ok {
return nil, fmt.Errorf("network %s not found", network)
}
lease, ok := pool.Leases[workloadID]
if !ok {
return nil, fmt.Errorf("no lease for %s", workloadID)
}
return lease, nil
}
// ListLeases returns all active leases for a network
func (i *IPAM) ListLeases(network string) ([]*Lease, error) {
i.mu.RLock()
defer i.mu.RUnlock()
pool, ok := i.pools[network]
if !ok {
return nil, fmt.Errorf("network %s not found", network)
}
result := make([]*Lease, 0, len(pool.Leases))
for _, lease := range pool.Leases {
result = append(result, lease)
}
return result, nil
}
// Reserve creates a static IP reservation
func (i *IPAM) Reserve(network, workloadID string, ip net.IP) error {
i.mu.Lock()
defer i.mu.Unlock()
pool, ok := i.pools[network]
if !ok {
return fmt.Errorf("network %s not found", network)
}
// Check if IP is in subnet
if !pool.Subnet.Contains(ip) {
return fmt.Errorf("IP %s not in subnet %s", ip, pool.Subnet)
}
// Check if already allocated
if pool.allocated[ipToUint32(ip)] {
return fmt.Errorf("IP %s already allocated", ip)
}
if pool.Reservations == nil {
pool.Reservations = make(map[string]net.IP)
}
pool.Reservations[workloadID] = ip
pool.allocated[ipToUint32(ip)] = true
return i.saveState()
}
// Unreserve removes a static IP reservation
func (i *IPAM) Unreserve(network, workloadID string) error {
i.mu.Lock()
defer i.mu.Unlock()
pool, ok := i.pools[network]
if !ok {
return nil
}
if ip, ok := pool.Reservations[workloadID]; ok {
delete(pool.allocated, ipToUint32(ip))
delete(pool.Reservations, workloadID)
return i.saveState()
}
return nil
}
// findFreeIP finds the next available IP in the pool
func (p *Pool) findFreeIP() (net.IP, error) {
startUint := ipToUint32(p.Start)
endUint := ipToUint32(p.End)
for ip := startUint; ip <= endUint; ip++ {
if !p.allocated[ip] {
return uint32ToIP(ip), nil
}
}
return nil, fmt.Errorf("no free IPs in pool %s", p.Name)
}
// saveState persists IPAM state to disk
func (i *IPAM) saveState() error {
data, err := json.MarshalIndent(i.pools, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(i.stateDir, "pools.json"), data, 0644)
}
// loadState loads IPAM state from disk
func (i *IPAM) loadState() error {
data, err := os.ReadFile(filepath.Join(i.stateDir, "pools.json"))
if err != nil {
return err
}
if err := json.Unmarshal(data, &i.pools); err != nil {
return err
}
// Rebuild allocated maps
for _, pool := range i.pools {
pool.allocated = make(map[uint32]bool)
if pool.Gateway != nil {
pool.allocated[ipToUint32(pool.Gateway)] = true
}
for _, ip := range pool.Reservations {
pool.allocated[ipToUint32(ip)] = true
}
for _, lease := range pool.Leases {
pool.allocated[ipToUint32(lease.IP)] = true
}
}
return nil
}
// Helper functions for IP math
func ipToUint32(ip net.IP) uint32 {
ip = ip.To4()
if ip == nil {
return 0
}
return binary.BigEndian.Uint32(ip)
}
func uint32ToIP(n uint32) net.IP {
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, n)
return ip
}
func nextIP(ip net.IP) net.IP {
return uint32ToIP(ipToUint32(ip) + 1)
}
func lastIP(subnet *net.IPNet) net.IP {
// Get the broadcast address (last IP in subnet)
ip := subnet.IP.To4()
mask := subnet.Mask
broadcast := make(net.IP, 4)
for i := range ip {
broadcast[i] = ip[i] | ^mask[i]
}
// Return one before broadcast (last usable)
return uint32ToIP(ipToUint32(broadcast) - 1)
}