feat: add database-backed IP allocation with fallback to IPAM service

Add FindNextAvailableIP repository method to query ip_allocations table and find next available IP address within gateway VPN CIDR range. Query existing allocations from database and build used IP map. Iterate through CIDR range starting at offset to find first unused address. Update Enroll service method to call FindNextAvailableIP first with fallback to IPAM service Allocate method on error. Add netip and errors imports to repository
This commit is contained in:
2026-03-17 21:43:42 +01:00
parent a8fbe725a2
commit b16564ac5c
2 changed files with 51 additions and 2 deletions

View File

@@ -2,6 +2,8 @@ package device
import (
"context"
"errors"
"net/netip"
"time"
"github.com/google/uuid"
@@ -9,6 +11,7 @@ import (
)
type Repository interface {
FindNextAvailableIP(ctx context.Context, gatewayID uuid.UUID, vpnCIDR string, startOffset int) (string, error)
Enroll(ctx context.Context, userID uuid.UUID, gatewayID uuid.UUID, input EnrollRequest, assignedIP string, dnsServers []string, allowedIPs []string) (EnrollmentResponse, error)
ListByUser(ctx context.Context, userID uuid.UUID) ([]Device, error)
ListAll(ctx context.Context) ([]Device, error)
@@ -26,6 +29,49 @@ func NewPGRepository(db *pgxpool.Pool) *PGRepository {
return &PGRepository{db: db}
}
func (r *PGRepository) FindNextAvailableIP(ctx context.Context, gatewayID uuid.UUID, vpnCIDR string, startOffset int) (string, error) {
prefix, err := netip.ParsePrefix(vpnCIDR)
if err != nil {
return "", err
}
rows, err := r.db.Query(ctx, `
select host(address)
from ip_allocations
where gateway_id = $1
`, gatewayID)
if err != nil {
return "", err
}
defer rows.Close()
used := map[string]struct{}{}
for rows.Next() {
var address string
if err := rows.Scan(&address); err != nil {
return "", err
}
used[address] = struct{}{}
}
if err := rows.Err(); err != nil {
return "", err
}
address := prefix.Addr().Next()
for i := 1; i < startOffset; i++ {
address = address.Next()
}
for prefix.Contains(address) {
if _, exists := used[address.String()]; !exists {
return address.String() + "/32", nil
}
address = address.Next()
}
return "", errors.New("no available ip addresses for gateway")
}
func (r *PGRepository) Enroll(ctx context.Context, userID uuid.UUID, gatewayID uuid.UUID, input EnrollRequest, assignedIP string, dnsServers []string, allowedIPs []string) (EnrollmentResponse, error) {
tx, err := r.db.Begin(ctx)
if err != nil {