1, 'end' => 30, 'broadcast' => 254 ]; public function __construct($apiUrl, $apiToken, $logger = null) { $this->apiUrl = $apiUrl; $this->apiToken = $apiToken; $this->logger = $logger; } /** * Determina si una IP es administrativa (no recomendada para clientes) * * @param string $ip Dirección IP completa (ej: 172.16.13.5) * @return bool True si es IP administrativa */ public static function isAdminIp($ip) { $parts = explode('.', $ip); if (count($parts) !== 4) { return false; } $lastOctet = (int)$parts[3]; return ($lastOctet >= self::ADMIN_IP_RANGES['start'] && $lastOctet <= self::ADMIN_IP_RANGES['end']) || $lastOctet === self::ADMIN_IP_RANGES['broadcast']; } /** * Obtiene el tipo de IP (administrativa o cliente) * * @param string $ip Dirección IP completa * @return array ['type' => 'admin'|'client', 'label' => string, 'recommended' => bool] */ public static function getIpType($ip) { if (self::isAdminIp($ip)) { return [ 'type' => 'admin', 'label' => 'Administración', 'recommended' => false, 'description' => 'No recomendada para clientes' ]; } return [ 'type' => 'client', 'label' => 'Apta para cliente', 'recommended' => true, 'description' => 'Recomendada para asignar a clientes' ]; } /** * Busca IPs disponibles en un segmento de red específico * * @param string $segmento El tercer octeto del segmento (ej: "5" para 172.16.5.x) * @return array Array con 'success', 'data' (IPs disponibles), 'used' (IPs en uso), 'message' */ public function buscarIpsDisponibles($segmento) { try { // Validar el segmento if (!is_numeric($segmento) || $segmento < 0 || $segmento > 255) { return [ 'success' => false, 'message' => 'El segmento debe ser un número entre 0 y 255', 'data' => [] ]; } // Obtener IPs en uso desde la API $ipsEnUso = $this->obtenerIpsEnUso(); if ($ipsEnUso === false) { return [ 'success' => false, 'message' => 'Error al conectar con la API de UISP', 'data' => [] ]; } // Filtrar IPs del segmento solicitado $segmentoPrefix = "172.16.$segmento."; $segmentIps = []; foreach ($ipsEnUso as $ip) { if (strpos($ip, $segmentoPrefix) === 0) { $segmentIps[] = $ip; } } // Ordenar IPs del segmento usort($segmentIps, function($a, $b) { return intval(explode('.', $a)[3]) - intval(explode('.', $b)[3]); }); // Generar todas las IPs posibles del segmento (1-254) $todasLasIps = []; for ($i = 1; $i <= 254; $i++) { $todasLasIps[] = "172.16.$segmento.$i"; } // Calcular IPs disponibles $ipsDisponibles = array_values(array_diff($todasLasIps, $segmentIps)); if ($this->logger) { $this->logger->appendLog( sprintf( 'Búsqueda de IPs en segmento 172.16.%s.x - Disponibles: %d, En uso: %d', $segmento, count($ipsDisponibles), count($segmentIps) ) ); } return [ 'success' => true, 'message' => sprintf( 'Se encontraron %d IPs disponibles en el segmento 172.16.%s.x', count($ipsDisponibles), $segmento ), 'data' => $ipsDisponibles, 'used' => $segmentIps, 'segment' => "172.16.$segmento.x" ]; } catch (\Exception $e) { if ($this->logger) { $this->logger->appendLog('Error en búsqueda de IPs: ' . $e->getMessage()); } return [ 'success' => false, 'message' => 'Error interno: ' . $e->getMessage(), 'data' => [] ]; } } /** * Obtiene todas las IPs en uso desde la API de UISP * * @return array|false Array de IPs o false en caso de error */ private function obtenerIpsEnUso() { if ($this->logger) { $this->logger->appendLog('Iniciando conexión a API: ' . $this->apiUrl); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'accept: application/json', "x-auth-token: {$this->apiToken}" ]); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Para entornos de desarrollo curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); $result = curl_exec($ch); $curlError = curl_error($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($result === false) { if ($this->logger) { $this->logger->appendLog('Error cURL: ' . $curlError); $this->logger->appendLog('Código HTTP: ' . $httpCode); } curl_close($ch); return false; } curl_close($ch); if ($this->logger) { $this->logger->appendLog("Respuesta HTTP: $httpCode"); $this->logger->appendLog('Longitud de respuesta: ' . strlen($result) . ' bytes'); } if ($httpCode !== 200) { if ($this->logger) { $this->logger->appendLog("Error HTTP $httpCode. Respuesta: " . substr($result, 0, 500)); } return false; } $ipsEnUso = json_decode($result, true); if (!is_array($ipsEnUso)) { if ($this->logger) { $this->logger->appendLog('Error: La respuesta no es un array válido. Respuesta: ' . substr($result, 0, 500)); } return false; } if ($this->logger) { $this->logger->appendLog('IPs obtenidas exitosamente: ' . count($ipsEnUso) . ' direcciones'); } return $ipsEnUso; } }