#!/usr/bin/env bash # ============================================ # IP Assignment # ============================================ function ip::assigned() { grep -h "^Address" "$(ctx::clients)"/*.conf 2>/dev/null \ | awk '{print $3}' \ | cut -d'/' -f1 } function ip::is_assigned() { local candidate="$1" ip::assigned | grep -q "^${candidate}$" } # ip::next_for_subnet # Finds the next unassigned host IP within a CIDR. # Replaces ip::next_for_type for the subnet-aware allocation path. function ip::next_for_subnet() { local cidr="${1:-}" if [[ -z "$cidr" ]]; then log::error "No subnet CIDR provided for IP allocation" return 1 fi local prefix prefix=$(subnet::prefix "$cidr") local candidate for i in $(subnet::host_range "$cidr"); do candidate="${prefix}.${i}" ip::is_assigned "$candidate" || { echo "$candidate"; return 0; } done log::error "No available IPs in subnet ${cidr}" return 1 } # ============================================ # Validation # ============================================ function ip::is_valid() { local ip="${1:-}" [[ -z "$ip" ]] && return 1 # Strip CIDR mask if present local addr="${ip%%/*}" # Structural check — 4 octets, optional /mask [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$ ]] || return 1 # Octet range check — each must be 0-255 local IFS='.' local -a octets read -ra octets <<< "$addr" for octet in "${octets[@]}"; do (( octet >= 0 && octet <= 255 )) || return 1 done return 0 } function ip::is_cidr() { [[ "$1" == *"/"* ]] } # ip::is_valid_for_subnet # Convenience wrapper — validates an IP against a specific subnet. # Delegates to subnet::ip_valid_for which handles all the checks. function ip::is_valid_for_subnet() { local cidr="${1:-}" ip="${2:-}" subnet::ip_valid_for "$cidr" "$ip" } # ip::require_valid_for_subnet # Errors and returns 1 if the IP is not valid for the subnet. # Used when a manual --ip override is provided. function ip::require_valid_for_subnet() { local cidr="${1:-}" ip="${2:-}" subnet::require_ip_valid_for "$cidr" "$ip" } function ip::validate() { local ip="$1" if ! ip::is_valid "$ip"; then log::error "Invalid IP or CIDR: ${ip}" return 1 fi return 0 } function ip::require_valid() { local ip="$1" ip::validate "$ip" || exit 1 }