siip-available-ips/src/IpSearchService.php

224 lines
7.0 KiB
PHP

<?php
namespace SiipAvailableIps;
class IpSearchService
{
private $apiUrl;
private $apiToken;
private $logger;
// Rangos de IPs administrativas (no recomendadas para clientes)
const ADMIN_IP_RANGES = [
'start' => 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;
}
}