Característica: se implementó la verificación por ping opciones para mejorar la precisión de las direcciones IP disponibles.
This commit is contained in:
parent
0be508e749
commit
8549bab24b
55
CHANGELOG.md
55
CHANGELOG.md
@ -7,6 +7,61 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/lang/es/).
|
||||
|
||||
---
|
||||
|
||||
## [1.2.0] - 2025-11-26
|
||||
|
||||
### ✨ Añadido
|
||||
|
||||
#### Verificación por Ping
|
||||
- **Clase `PingService.php`**: Servicio de verificación de IPs mediante ping
|
||||
- Ping paralelo a múltiples IPs simultáneamente (hasta 30 IPs por lote)
|
||||
- Timeout configurable (default: 1 segundo)
|
||||
- Detección automática de disponibilidad del comando ping
|
||||
- Procesamiento en lotes para evitar sobrecarga de red
|
||||
- Fallback graceful si ping no está disponible
|
||||
- **Parámetro de configuración `enablePingVerification`**: Control global de verificación por ping
|
||||
- Modo opcional: usuario decide en cada búsqueda
|
||||
- Checkbox condicional en frontend (solo visible si está habilitado)
|
||||
- Parámetro `verify_ping` en API REST
|
||||
- **Detección de dispositivos no registrados**: Identifica IPs que responden a ping pero no están en UISP
|
||||
- Filtra automáticamente IPs que responden
|
||||
- Advertencias en logs para IPs no registradas
|
||||
- Estadísticas de ping en respuestas
|
||||
|
||||
#### Integración
|
||||
- **Frontend**: Checkbox "Verificar con ping" (condicional según configuración)
|
||||
- Solo visible si `enablePingVerification` está habilitado
|
||||
- Integrado en formulario de búsqueda
|
||||
- Indicador de progreso durante verificación
|
||||
- **API REST**: Soporte para parámetro `verify_ping` en ambos endpoints
|
||||
- `event.ip_request`: Incluye estadísticas de ping en respuesta
|
||||
- `event.ip_check`: Indica si IP específica responde a ping
|
||||
- Campo `ping_verified` en todas las respuestas
|
||||
- Campo `ping_stats` con métricas detalladas
|
||||
|
||||
### 🔄 Mejorado
|
||||
|
||||
- **`IpSearchService::buscarIpsDisponibles()`**: Ahora acepta parámetro `$verifyPing`
|
||||
- Integración transparente con `PingService`
|
||||
- Filtrado automático de IPs que responden
|
||||
- Logging detallado de resultados de ping
|
||||
- **Respuestas de API**: Información enriquecida con datos de ping
|
||||
- Estadísticas: total verificado, respondiendo, no respondiendo
|
||||
- Tiempo de ejecución de verificación
|
||||
- Lista de IPs que responden (posibles conflictos)
|
||||
|
||||
### 🔧 Técnico
|
||||
|
||||
- **Ping paralelo**: Implementación usando procesos en background
|
||||
- Archivos temporales para capturar resultados
|
||||
- Espera asíncrona con timeout
|
||||
- Limpieza automática de archivos temporales
|
||||
- **Performance**: Optimizado para grandes rangos de IPs
|
||||
- Procesamiento en lotes de 30 IPs
|
||||
- Timeout total controlado
|
||||
- Mínimo impacto en tiempo de respuesta
|
||||
|
||||
---
|
||||
|
||||
## [1.1.0] - 2025-11-26
|
||||
|
||||
### ✨ Añadido
|
||||
|
||||
83
README.md
83
README.md
@ -1,6 +1,6 @@
|
||||
# SIIP - Buscador de IP's Disponibles UISP
|
||||
|
||||
[](manifest.json)
|
||||
[](manifest.json)
|
||||
[](https://uisp.com/)
|
||||
[](https://uisp.com/)
|
||||
|
||||
@ -33,6 +33,7 @@ Plugin para UISP CRM (anteriormente UCRM) que permite buscar direcciones IP disp
|
||||
- 🔌 **API REST completa** para integraciones externas
|
||||
- 📋 **Copiar al portapapeles** con un solo clic
|
||||
- 🎯 **Filtrado inteligente** de IPs administrativas vs. IPs para clientes
|
||||
- 🏓 **Verificación por ping** para detectar dispositivos no registrados (opcional)
|
||||
- 📊 **Estadísticas en tiempo real** de IPs disponibles y en uso
|
||||
- 🔐 **Integración nativa** con UISP CRM y UNMS
|
||||
- 🪝 **Soporte para webhooks** y eventos personalizados
|
||||
@ -89,6 +90,7 @@ Después de instalar el plugin, configura los siguientes parámetros:
|
||||
| Parámetro | Descripción | Uso |
|
||||
|-----------|-------------|-----|
|
||||
| **Token de la API UNMS** | Token de UNMS (34 caracteres) | Para búsqueda de IPs en dispositivos de red |
|
||||
| **Habilitar verificación por Ping** | Activa modo de verificación por ping | Permite verificar disponibilidad real de IPs |
|
||||
| **Debug Mode** | Modo de depuración | Habilita logs más detallados |
|
||||
| **Enable debug logs** | Logs verbosos | Información adicional en logs |
|
||||
|
||||
@ -131,12 +133,27 @@ La interfaz incluye:
|
||||
|
||||
```
|
||||
1. Ingresa "5" en el campo de búsqueda
|
||||
2. Haz clic en "Buscar IPs"
|
||||
3. Se mostrarán todas las IPs disponibles en el rango 172.16.5.x
|
||||
4. Haz clic en el botón "Copiar" junto a la IP deseada
|
||||
5. La IP se copia automáticamente al portapapeles
|
||||
2. (Opcional) Marca "Verificar con ping" si está habilitado
|
||||
3. Haz clic en "Buscar IPs"
|
||||
4. Se mostrarán todas las IPs disponibles en el rango 172.16.5.x
|
||||
5. Haz clic en el botón "Copiar" junto a la IP deseada
|
||||
6. La IP se copia automáticamente al portapapeles
|
||||
```
|
||||
|
||||
### Verificación por Ping (Opcional)
|
||||
|
||||
Si está habilitada en la configuración, aparecerá un checkbox "🔍 Verificar con ping" en el formulario.
|
||||
|
||||
**¿Qué hace?**
|
||||
- Verifica que las IPs reportadas como disponibles realmente no respondan a ping
|
||||
- Detecta dispositivos no registrados en UISP (computadoras, impresoras, cámaras, etc.)
|
||||
- Filtra automáticamente IPs que responden (posibles conflictos)
|
||||
|
||||
**¿Cuándo usarlo?**
|
||||
- ✅ Antes de asignar IPs a clientes nuevos (máxima seguridad)
|
||||
- ✅ En redes con dispositivos no gestionados
|
||||
- ❌ Para búsquedas rápidas donde no importa la verificación
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API REST
|
||||
@ -195,10 +212,42 @@ Busca todas las IPs disponibles en un segmento de red específico.
|
||||
**Parámetros**:
|
||||
- `type` (string, requerido): Tipo de evento, debe ser `"event.ip_request"`
|
||||
- `segment` (string, requerido): Tercer octeto del segmento (0-255)
|
||||
- `verify_ping` (boolean, opcional): Si es `true`, verifica IPs con ping antes de reportarlas
|
||||
|
||||
**Ejemplo con verificación por ping**:
|
||||
```json
|
||||
{
|
||||
"type": "event.ip_request",
|
||||
"segment": "5",
|
||||
"verify_ping": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response con ping**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"event": "event.ip_request",
|
||||
"segment": "172.16.5.x",
|
||||
"data": {
|
||||
"available": ["172.16.5.31", "172.16.5.32"],
|
||||
"used": ["172.16.5.1"]
|
||||
},
|
||||
"ping_verified": true,
|
||||
"ping_stats": {
|
||||
"total_checked": 223,
|
||||
"responding": 1,
|
||||
"not_responding": 222,
|
||||
"execution_time": "2.3s"
|
||||
},
|
||||
"ping_responding": ["172.16.5.50"]
|
||||
}
|
||||
```
|
||||
|
||||
**Notas**:
|
||||
- Las IPs administrativas (1-30 y 254) son filtradas automáticamente
|
||||
- Solo se devuelven IPs aptas para asignar a clientes (31-253)
|
||||
- Si `verify_ping` es `true`, las IPs que responden a ping se filtran automáticamente
|
||||
|
||||
---
|
||||
|
||||
@ -386,7 +435,8 @@ siip-available-ips/
|
||||
├── README.md # Este archivo
|
||||
├── src/ # Código fuente
|
||||
│ ├── ApiHandlers.php # Manejadores de API REST
|
||||
│ └── IpSearchService.php # Servicio de búsqueda de IPs
|
||||
│ ├── IpSearchService.php # Servicio de búsqueda de IPs
|
||||
│ └── PingService.php # Servicio de verificación por ping
|
||||
├── data/ # Datos del plugin
|
||||
│ ├── config.json # Configuración (generado automáticamente)
|
||||
│ └── plugin.log # Archivo de logs
|
||||
@ -413,6 +463,13 @@ Clase de servicio para búsqueda de IPs:
|
||||
- `isAdminIp()`: Determina si una IP es administrativa
|
||||
- `getIpType()`: Obtiene información del tipo de IP
|
||||
|
||||
#### `src/PingService.php`
|
||||
Clase de servicio para verificación por ping:
|
||||
- `pingMultipleIps()`: Ping paralelo a múltiples IPs
|
||||
- `pingIp()`: Ping a una sola IP
|
||||
- `processPingResults()`: Procesa resultados de ping
|
||||
- `isAvailable()`: Verifica si ping está disponible
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Filtrado de IPs Administrativas
|
||||
@ -474,9 +531,13 @@ Los logs se guardan en:
|
||||
|
||||
Para ver el historial completo de cambios y versiones, consulta el archivo **[CHANGELOG.md](CHANGELOG.md)**.
|
||||
|
||||
### Versión Actual: 1.1.0 (2025-11-26)
|
||||
### Versión Actual: 1.2.0 (2025-11-26)
|
||||
|
||||
**Cambios destacados**:
|
||||
- 🏓 Verificación por ping para detectar dispositivos no registrados
|
||||
- ⚡ Ping paralelo de hasta 30 IPs simultáneamente
|
||||
- 🎛️ Control opcional vía configuración
|
||||
- 📊 Estadísticas detalladas de verificación
|
||||
- ✨ API REST completa con endpoints `event.ip_request` y `event.ip_check`
|
||||
- 🔌 Soporte para webhooks y eventos personalizados
|
||||
- 🎯 Filtrado automático de IPs administrativas
|
||||
@ -516,14 +577,8 @@ Para ver el historial completo de cambios y versiones, consulta el archivo **[CH
|
||||
|
||||
Este plugin es propiedad de **SIIP Internet**. Todos los derechos reservados.
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Agradecimientos
|
||||
|
||||
- Equipo de UISP/Ubiquiti por la plataforma
|
||||
- Comunidad de desarrolladores de plugins UCRM
|
||||
|
||||
---
|
||||
|
||||
**Versión**: 1.1.0
|
||||
**Versión**: 1.2.0
|
||||
**Última actualización**: 26 de noviembre de 2025
|
||||
|
||||
@ -1 +1 @@
|
||||
{"ipserver":"sistema.siip.mx","apitoken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","unmsApiToken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","debugMode":true,"logging_level":true}
|
||||
{"ipserver":"sistema.siip.mx","apitoken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","unmsApiToken":"393eb3d0-9b46-4a47-b9b4-473e4e24a89c","debugMode":true,"logging_level":true,"enablePingVerification":true}
|
||||
@ -283,3 +283,99 @@ IPs obtenidas exitosamente: 3103 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.13.x - Disponibles: 153, En uso: 101
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":153,"ipsEnUso":101}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"search","segment":"13"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryd3584f1ed3d6519fada0a6a78880083b
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de búsqueda AJAX
|
||||
Configuración cargada: {"ipserver":"sistema.siip.mx","hasUnmsToken":true,"hasApiToken":true}
|
||||
Buscando IPs en segmento: 13 (sin verificación por ping)
|
||||
URL de API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Iniciando conexión a API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Respuesta HTTP: 200
|
||||
Longitud de respuesta: 48906 bytes
|
||||
IPs obtenidas exitosamente: 3105 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.13.x - Disponibles: 153, En uso: 101
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":153,"ipsEnUso":101}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"search","segment":"13"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryc20a33e19ccea953d03471341f409046
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de búsqueda AJAX
|
||||
Configuración cargada: {"ipserver":"sistema.siip.mx","hasUnmsToken":true,"hasApiToken":true}
|
||||
Buscando IPs en segmento: 13 (sin verificación por ping)
|
||||
URL de API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Iniciando conexión a API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Respuesta HTTP: 200
|
||||
Longitud de respuesta: 48906 bytes
|
||||
IPs obtenidas exitosamente: 3105 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.13.x - Disponibles: 153, En uso: 101
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":153,"ipsEnUso":101}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"search","segment":"100"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundary974266b1e07d3862a10f25b4f522b48d
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de búsqueda AJAX
|
||||
Configuración cargada: {"ipserver":"sistema.siip.mx","hasUnmsToken":true,"hasApiToken":true}
|
||||
Buscando IPs en segmento: 100 (sin verificación por ping)
|
||||
URL de API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Iniciando conexión a API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Respuesta HTTP: 200
|
||||
Longitud de respuesta: 48906 bytes
|
||||
IPs obtenidas exitosamente: 3105 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.100.x - Disponibles: 123, En uso: 131
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":123,"ipsEnUso":131}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: GET
|
||||
POST data: []
|
||||
GET data: []
|
||||
Content-Type:
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
Acceso a la interfaz pública de búsqueda de IPs
|
||||
=== NUEVA PETICIÓN ===
|
||||
Método: POST
|
||||
POST data: {"action":"search","segment":"100"}
|
||||
GET data: []
|
||||
Content-Type: multipart/form-data; boundary=----geckoformboundaryd936e6e93d946c44900218614f60b9c7
|
||||
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:145.0) Gecko/20100101 Firefox/145.0
|
||||
>>> Entrando al handler de búsqueda AJAX
|
||||
Configuración cargada: {"ipserver":"sistema.siip.mx","hasUnmsToken":true,"hasApiToken":true}
|
||||
Buscando IPs en segmento: 100 (sin verificación por ping)
|
||||
URL de API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Iniciando conexión a API: https://sistema.siip.mx/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true
|
||||
Respuesta HTTP: 200
|
||||
Longitud de respuesta: 48906 bytes
|
||||
IPs obtenidas exitosamente: 3105 direcciones
|
||||
Búsqueda de IPs en segmento 172.16.100.x - Disponibles: 123, En uso: 131
|
||||
Resultado de búsqueda: {"success":true,"ipsDisponibles":123,"ipsEnUso":131}
|
||||
<<< Finalizando handler de búsqueda AJAX
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"displayName": "SIIP - Buscador de IP's Disponibles UISP",
|
||||
"description": "Este plugin permite buscar IP's disponibles en UISP (UNMS) y asignarlas a los clientes en UCRM. Evitando así la asignación de IP's duplicadas y mejorando la gestión de direcciones IP en la red.",
|
||||
"url": "https://siip.mx",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"ucrmVersionCompliancy": {
|
||||
"min": "1.0.0",
|
||||
"max": null
|
||||
@ -51,6 +51,13 @@
|
||||
"description": "Make the plugin more verbose - Hace el plugin mas verboso.",
|
||||
"type": "checkbox",
|
||||
"required": 0
|
||||
},
|
||||
{
|
||||
"key": "enablePingVerification",
|
||||
"label": "Habilitar verificación por Ping",
|
||||
"description": "Permite verificar la disponibilidad real de las IPs mediante ping. Cuando está habilitado, el usuario puede elegir si desea verificar las IPs con ping antes de mostrarlas como disponibles. Esto ayuda a detectar dispositivos no registrados en UISP.",
|
||||
"required": 0,
|
||||
"type": "checkbox"
|
||||
}
|
||||
],
|
||||
"menu": [
|
||||
|
||||
33
public.php
33
public.php
@ -25,6 +25,7 @@ chdir(__DIR__);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/src/IpSearchService.php';
|
||||
require_once __DIR__ . '/src/PingService.php';
|
||||
require_once __DIR__ . '/src/ApiHandlers.php';
|
||||
|
||||
use Ubnt\UcrmPluginSdk\Service\PluginLogManager;
|
||||
@ -113,7 +114,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
||||
}
|
||||
|
||||
$segmento = $_POST['segment'] ?? '';
|
||||
$log->appendLog("Buscando IPs en segmento: $segmento");
|
||||
$verifyPing = isset($_POST['verify_ping']) && $_POST['verify_ping'] === 'true';
|
||||
|
||||
if ($verifyPing) {
|
||||
$log->appendLog("Buscando IPs en segmento: $segmento (con verificación por ping)");
|
||||
} else {
|
||||
$log->appendLog("Buscando IPs en segmento: $segmento (sin verificación por ping)");
|
||||
}
|
||||
|
||||
// URL de la API de UISP - Usar HTTPS
|
||||
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true";
|
||||
@ -123,7 +130,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
||||
|
||||
// Crear instancia del servicio y buscar IPs
|
||||
$ipService = new IpSearchService($apiUrl, $apiToken, $log);
|
||||
$resultado = $ipService->buscarIpsDisponibles($segmento);
|
||||
$resultado = $ipService->buscarIpsDisponibles($segmento, $verifyPing);
|
||||
|
||||
$log->appendLog('Resultado de búsqueda: ' . json_encode([
|
||||
'success' => $resultado['success'],
|
||||
@ -164,6 +171,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
||||
// Log de acceso público
|
||||
$log->appendLog('Acceso a la interfaz pública de búsqueda de IPs');
|
||||
|
||||
// Cargar configuración para verificar si ping está habilitado
|
||||
$configManager = PluginConfigManager::create();
|
||||
$config = $configManager->loadConfig();
|
||||
$pingEnabled = isset($config['enablePingVerification']) && $config['enablePingVerification'] === '1';
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
@ -617,6 +629,14 @@ $log->appendLog('Acceso a la interfaz pública de búsqueda de IPs');
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($pingEnabled): ?>
|
||||
<div class="input-group" style="flex: 0 0 auto; min-width: auto;">
|
||||
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 0; padding-top: 28px;">
|
||||
<input type="checkbox" id="verifyPing" name="verify_ping" style="width: auto; padding: 0; margin: 0;">
|
||||
<span style="color: var(--text-secondary); font-size: 0.9rem;">🔍 Verificar con ping</span>
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button type="submit" class="btn btn-primary" id="searchBtn">
|
||||
<span>🔍</span>
|
||||
<span>Buscar IPs</span>
|
||||
@ -701,6 +721,15 @@ $log->appendLog('Acceso a la interfaz pública de búsqueda de IPs');
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'search');
|
||||
formData.append('segment', segment);
|
||||
|
||||
// Agregar verificación por ping si está habilitada y marcada
|
||||
<?php if ($pingEnabled): ?>
|
||||
const verifyPingCheckbox = document.getElementById('verifyPing');
|
||||
if (verifyPingCheckbox && verifyPingCheckbox.checked) {
|
||||
formData.append('verify_ping', 'true');
|
||||
console.log('Verificación por ping habilitada');
|
||||
}
|
||||
<?php endif; ?>
|
||||
|
||||
console.log('Enviando petición AJAX...');
|
||||
console.log('Segmento:', segment);
|
||||
|
||||
@ -70,7 +70,13 @@ function handleIpRequest($data, $log) {
|
||||
}
|
||||
|
||||
$segment = $data['segment'];
|
||||
$log->appendLog("API: Buscando IPs en segmento $segment");
|
||||
$verifyPing = isset($data['verify_ping']) && $data['verify_ping'] === true;
|
||||
|
||||
if ($verifyPing) {
|
||||
$log->appendLog("API: Verificación por ping habilitada para segmento $segment");
|
||||
} else {
|
||||
$log->appendLog("API: Buscando IPs en segmento $segment (sin verificación por ping)");
|
||||
}
|
||||
|
||||
// Cargar configuración
|
||||
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
|
||||
@ -90,7 +96,7 @@ function handleIpRequest($data, $log) {
|
||||
$apiToken = $config['unmsApiToken'];
|
||||
|
||||
$ipService = new \SiipAvailableIps\IpSearchService($apiUrl, $apiToken, $log);
|
||||
$resultado = $ipService->buscarIpsDisponibles($segment);
|
||||
$resultado = $ipService->buscarIpsDisponibles($segment, $verifyPing);
|
||||
|
||||
// Formatear respuesta para API
|
||||
if ($resultado['success']) {
|
||||
@ -102,7 +108,7 @@ function handleIpRequest($data, $log) {
|
||||
|
||||
$log->appendLog("API: Filtrando IPs administrativas. Total disponibles: " . count($resultado['data']) . ", Aptas para clientes: " . count($clientAvailableIps));
|
||||
|
||||
echo json_encode([
|
||||
$response = [
|
||||
'success' => true,
|
||||
'event' => 'event.ip_request',
|
||||
'segment' => $resultado['segment'],
|
||||
@ -115,13 +121,29 @@ function handleIpRequest($data, $log) {
|
||||
'used' => count($resultado['used']),
|
||||
'admin_filtered' => count($resultado['data']) - count($clientAvailableIps)
|
||||
],
|
||||
'ping_verified' => $resultado['ping_verified'],
|
||||
'message' => sprintf(
|
||||
'Se encontraron %d IPs aptas para clientes en el segmento %s (%d IPs administrativas filtradas)',
|
||||
count($clientAvailableIps),
|
||||
$resultado['segment'],
|
||||
count($resultado['data']) - count($clientAvailableIps)
|
||||
)
|
||||
]);
|
||||
];
|
||||
|
||||
// Agregar estadísticas de ping si se verificó
|
||||
if ($resultado['ping_verified'] && isset($resultado['ping_stats'])) {
|
||||
$response['ping_stats'] = $resultado['ping_stats'];
|
||||
|
||||
// Filtrar IPs que responden de las IPs de cliente
|
||||
if (isset($resultado['ping_responding'])) {
|
||||
$clientPingResponding = array_filter($resultado['ping_responding'], function($ip) {
|
||||
return !\SiipAvailableIps\IpSearchService::isAdminIp($ip);
|
||||
});
|
||||
$response['ping_responding'] = array_values($clientPingResponding);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
@ -177,8 +199,11 @@ function handleIpCheck($data, $log) {
|
||||
$apiUrl = "https://{$config['ipserver']}/nms/api/v2.1/devices/ips?suspended=false&management=true&includeObsolete=true";
|
||||
$apiToken = $config['unmsApiToken'];
|
||||
|
||||
// Verificar si se solicita ping
|
||||
$verifyPing = isset($data['verify_ping']) && $data['verify_ping'] === true;
|
||||
|
||||
$ipService = new \SiipAvailableIps\IpSearchService($apiUrl, $apiToken, $log);
|
||||
$resultado = $ipService->buscarIpsDisponibles($segment);
|
||||
$resultado = $ipService->buscarIpsDisponibles($segment, $verifyPing);
|
||||
|
||||
if ($resultado['success']) {
|
||||
$isAvailable = in_array($ipToCheck, $resultado['data']);
|
||||
@ -197,15 +222,29 @@ function handleIpCheck($data, $log) {
|
||||
: 'used';
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
$response = [
|
||||
'success' => true,
|
||||
'event' => 'event.ip_check',
|
||||
'ip' => $ipToCheck,
|
||||
'status' => $status,
|
||||
'available' => $isAvailable,
|
||||
'used' => $isUsed,
|
||||
'ip_type' => $ipType
|
||||
]);
|
||||
'ip_type' => $ipType,
|
||||
'ping_verified' => $resultado['ping_verified']
|
||||
];
|
||||
|
||||
// Si se verificó con ping, agregar información
|
||||
if ($resultado['ping_verified'] && isset($resultado['ping_responding'])) {
|
||||
$pingResponds = in_array($ipToCheck, $resultado['ping_responding']);
|
||||
$response['ping_responds'] = $pingResponds;
|
||||
|
||||
// Si la IP responde a ping pero está marcada como disponible, es una advertencia
|
||||
if ($pingResponds && $isAvailable) {
|
||||
$response['warning'] = 'IP responde a ping pero no está registrada en UISP';
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
|
||||
@ -70,9 +70,10 @@ class IpSearchService
|
||||
* 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'
|
||||
* @param bool $verifyPing Si es true, verifica las IPs con ping antes de reportarlas como disponibles
|
||||
* @return array Array con 'success', 'data' (IPs disponibles), 'used' (IPs en uso), 'message', 'ping_verified', 'ping_stats'
|
||||
*/
|
||||
public function buscarIpsDisponibles($segmento)
|
||||
public function buscarIpsDisponibles($segmento, $verifyPing = false)
|
||||
{
|
||||
try {
|
||||
// Validar el segmento
|
||||
@ -119,18 +120,81 @@ class IpSearchService
|
||||
// Calcular IPs disponibles
|
||||
$ipsDisponibles = array_values(array_diff($todasLasIps, $segmentIps));
|
||||
|
||||
// Verificar con ping si está habilitado
|
||||
$pingVerified = false;
|
||||
$pingStats = null;
|
||||
$pingResponding = [];
|
||||
|
||||
if ($verifyPing) {
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog('Verificación por ping habilitada, iniciando verificación...');
|
||||
}
|
||||
|
||||
// Crear instancia de PingService
|
||||
$pingService = new PingService($this->logger, 1, 30);
|
||||
|
||||
// Verificar si ping está disponible
|
||||
if (!$pingService->isAvailable()) {
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog('ADVERTENCIA: Comando ping no disponible, se omitirá verificación');
|
||||
}
|
||||
} else {
|
||||
// Hacer ping a las IPs "disponibles"
|
||||
$startTime = microtime(true);
|
||||
$pingResults = $pingService->pingMultipleIps($ipsDisponibles);
|
||||
$executionTime = microtime(true) - $startTime;
|
||||
|
||||
// Procesar resultados
|
||||
$processed = $pingService->processPingResults($pingResults);
|
||||
$pingResponding = $processed['responding'];
|
||||
|
||||
// Filtrar IPs que responden (están en uso pero no registradas en UISP)
|
||||
if (!empty($pingResponding)) {
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog(sprintf(
|
||||
'ADVERTENCIA: %d IPs responden a ping pero no están en UISP: %s',
|
||||
count($pingResponding),
|
||||
implode(', ', $pingResponding)
|
||||
));
|
||||
}
|
||||
|
||||
// Remover IPs que responden de la lista de disponibles
|
||||
$ipsDisponibles = array_values(array_diff($ipsDisponibles, $pingResponding));
|
||||
}
|
||||
|
||||
$pingVerified = true;
|
||||
$pingStats = [
|
||||
'total_checked' => $processed['stats']['total_checked'],
|
||||
'responding' => $processed['stats']['responding'],
|
||||
'not_responding' => $processed['stats']['not_responding'],
|
||||
'execution_time' => round($executionTime, 2) . 's'
|
||||
];
|
||||
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog(sprintf(
|
||||
'Verificación por ping completada: %d IPs verificadas, %d responden, %d no responden (%.2fs)',
|
||||
$pingStats['total_checked'],
|
||||
$pingStats['responding'],
|
||||
$pingStats['not_responding'],
|
||||
$executionTime
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog(
|
||||
sprintf(
|
||||
'Búsqueda de IPs en segmento 172.16.%s.x - Disponibles: %d, En uso: %d',
|
||||
'Búsqueda de IPs en segmento 172.16.%s.x - Disponibles: %d, En uso: %d%s',
|
||||
$segmento,
|
||||
count($ipsDisponibles),
|
||||
count($segmentIps)
|
||||
count($segmentIps),
|
||||
$pingVerified ? ' (verificado con ping)' : ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
$result = [
|
||||
'success' => true,
|
||||
'message' => sprintf(
|
||||
'Se encontraron %d IPs disponibles en el segmento 172.16.%s.x',
|
||||
@ -139,8 +203,17 @@ class IpSearchService
|
||||
),
|
||||
'data' => $ipsDisponibles,
|
||||
'used' => $segmentIps,
|
||||
'segment' => "172.16.$segmento.x"
|
||||
'segment' => "172.16.$segmento.x",
|
||||
'ping_verified' => $pingVerified
|
||||
];
|
||||
|
||||
// Agregar información de ping si se verificó
|
||||
if ($pingVerified && $pingStats) {
|
||||
$result['ping_stats'] = $pingStats;
|
||||
$result['ping_responding'] = $pingResponding;
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if ($this->logger) {
|
||||
|
||||
279
src/PingService.php
Normal file
279
src/PingService.php
Normal file
@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace SiipAvailableIps;
|
||||
|
||||
/**
|
||||
* Servicio de verificación de IPs mediante ping
|
||||
* Implementa ping paralelo para verificar disponibilidad real de IPs
|
||||
*/
|
||||
class PingService
|
||||
{
|
||||
private $logger;
|
||||
private $timeout;
|
||||
private $batchSize;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object|null $logger Logger para registrar operaciones
|
||||
* @param int $timeout Timeout en segundos para cada ping (default: 1)
|
||||
* @param int $batchSize Número de IPs a procesar en paralelo (default: 30)
|
||||
*/
|
||||
public function __construct($logger = null, $timeout = 1, $batchSize = 30)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->timeout = $timeout;
|
||||
$this->batchSize = $batchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si el comando ping está disponible en el sistema
|
||||
*
|
||||
* @return bool True si ping está disponible
|
||||
*/
|
||||
public function isAvailable()
|
||||
{
|
||||
$output = [];
|
||||
$returnCode = 0;
|
||||
|
||||
// Intentar ejecutar ping
|
||||
exec('which ping 2>&1', $output, $returnCode);
|
||||
|
||||
return $returnCode === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza ping a una sola IP
|
||||
*
|
||||
* @param string $ip Dirección IP a verificar
|
||||
* @param int|null $timeout Timeout en segundos (usa el default si es null)
|
||||
* @return array ['ip' => string, 'responding' => bool, 'time' => float|null]
|
||||
*/
|
||||
public function pingIp($ip, $timeout = null)
|
||||
{
|
||||
if ($timeout === null) {
|
||||
$timeout = $this->timeout;
|
||||
}
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
// Comando ping: -c 1 (1 paquete), -W timeout (espera en segundos)
|
||||
// 2>&1 redirige stderr a stdout
|
||||
$command = sprintf('ping -c 1 -W %d %s 2>&1', $timeout, escapeshellarg($ip));
|
||||
|
||||
$output = [];
|
||||
$returnCode = 0;
|
||||
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
$executionTime = microtime(true) - $startTime;
|
||||
|
||||
// returnCode 0 = ping exitoso (IP responde)
|
||||
// returnCode != 0 = ping falló (IP no responde o error)
|
||||
$responding = ($returnCode === 0);
|
||||
|
||||
if ($this->logger) {
|
||||
$status = $responding ? 'RESPONDE' : 'NO RESPONDE';
|
||||
$this->logger->appendLog(sprintf(
|
||||
'Ping a %s: %s (%.2fs)',
|
||||
$ip,
|
||||
$status,
|
||||
$executionTime
|
||||
));
|
||||
}
|
||||
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'responding' => $responding,
|
||||
'time' => $executionTime
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza ping a múltiples IPs en paralelo
|
||||
*
|
||||
* @param array $ips Array de direcciones IP a verificar
|
||||
* @param int|null $timeout Timeout en segundos (usa el default si es null)
|
||||
* @return array Array con resultados: ['ip' => string, 'responding' => bool, 'time' => float]
|
||||
*/
|
||||
public function pingMultipleIps($ips, $timeout = null)
|
||||
{
|
||||
if (empty($ips)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($timeout === null) {
|
||||
$timeout = $this->timeout;
|
||||
}
|
||||
|
||||
if (!$this->isAvailable()) {
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog('ADVERTENCIA: Comando ping no disponible en el sistema');
|
||||
}
|
||||
|
||||
// Retornar todas las IPs como no respondiendo
|
||||
return array_map(function($ip) {
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'responding' => false,
|
||||
'time' => null,
|
||||
'error' => 'Ping no disponible'
|
||||
];
|
||||
}, $ips);
|
||||
}
|
||||
|
||||
$totalIps = count($ips);
|
||||
$results = [];
|
||||
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog(sprintf(
|
||||
'Iniciando verificación por ping de %d IPs (lotes de %d)',
|
||||
$totalIps,
|
||||
$this->batchSize
|
||||
));
|
||||
}
|
||||
|
||||
// Procesar en lotes para evitar sobrecarga
|
||||
$batches = array_chunk($ips, $this->batchSize);
|
||||
|
||||
foreach ($batches as $batchIndex => $batch) {
|
||||
if ($this->logger) {
|
||||
$this->logger->appendLog(sprintf(
|
||||
'Procesando lote %d/%d (%d IPs)',
|
||||
$batchIndex + 1,
|
||||
count($batches),
|
||||
count($batch)
|
||||
));
|
||||
}
|
||||
|
||||
$batchResults = $this->pingBatch($batch, $timeout);
|
||||
$results = array_merge($results, $batchResults);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza ping a un lote de IPs en paralelo
|
||||
*
|
||||
* @param array $ips Array de IPs del lote
|
||||
* @param int $timeout Timeout en segundos
|
||||
* @return array Resultados del lote
|
||||
*/
|
||||
private function pingBatch($ips, $timeout)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$tempFiles = [];
|
||||
$processes = [];
|
||||
|
||||
// Iniciar procesos de ping en paralelo
|
||||
foreach ($ips as $ip) {
|
||||
// Crear archivo temporal para guardar resultado
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'ping_');
|
||||
$tempFiles[$ip] = $tempFile;
|
||||
|
||||
// Comando ping con salida a archivo temporal
|
||||
$command = sprintf(
|
||||
'ping -c 1 -W %d %s > %s 2>&1 ; echo $? >> %s &',
|
||||
$timeout,
|
||||
escapeshellarg($ip),
|
||||
escapeshellarg($tempFile),
|
||||
escapeshellarg($tempFile)
|
||||
);
|
||||
|
||||
// Ejecutar en background
|
||||
exec($command);
|
||||
$processes[$ip] = true;
|
||||
}
|
||||
|
||||
// Esperar a que todos los procesos terminen
|
||||
// Timeout total = timeout de ping + 1 segundo de margen
|
||||
$maxWait = $timeout + 1;
|
||||
$waited = 0;
|
||||
$sleepInterval = 0.1; // 100ms
|
||||
|
||||
while ($waited < $maxWait && !empty($processes)) {
|
||||
usleep($sleepInterval * 1000000); // Convertir a microsegundos
|
||||
$waited += $sleepInterval;
|
||||
|
||||
// Verificar si los archivos tienen resultados
|
||||
foreach ($processes as $ip => $pending) {
|
||||
if (file_exists($tempFiles[$ip]) && filesize($tempFiles[$ip]) > 0) {
|
||||
unset($processes[$ip]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Procesar resultados
|
||||
$results = [];
|
||||
foreach ($ips as $ip) {
|
||||
$tempFile = $tempFiles[$ip];
|
||||
$responding = false;
|
||||
|
||||
if (file_exists($tempFile)) {
|
||||
$content = file_get_contents($tempFile);
|
||||
$lines = explode("\n", trim($content));
|
||||
|
||||
// El último valor es el código de retorno
|
||||
$returnCode = end($lines);
|
||||
$responding = (trim($returnCode) === '0');
|
||||
|
||||
// Limpiar archivo temporal
|
||||
@unlink($tempFile);
|
||||
}
|
||||
|
||||
$results[] = [
|
||||
'ip' => $ip,
|
||||
'responding' => $responding,
|
||||
'time' => null // No calculamos tiempo individual en modo paralelo
|
||||
];
|
||||
}
|
||||
|
||||
$executionTime = microtime(true) - $startTime;
|
||||
|
||||
if ($this->logger) {
|
||||
$respondingCount = count(array_filter($results, function($r) {
|
||||
return $r['responding'];
|
||||
}));
|
||||
|
||||
$this->logger->appendLog(sprintf(
|
||||
'Lote completado: %d/%d IPs responden (%.2fs)',
|
||||
$respondingCount,
|
||||
count($ips),
|
||||
$executionTime
|
||||
));
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa resultados de ping y separa IPs que responden vs. no responden
|
||||
*
|
||||
* @param array $results Resultados de pingMultipleIps()
|
||||
* @return array ['responding' => [...], 'not_responding' => [...], 'stats' => [...]]
|
||||
*/
|
||||
public function processPingResults($results)
|
||||
{
|
||||
$responding = [];
|
||||
$notResponding = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
if ($result['responding']) {
|
||||
$responding[] = $result['ip'];
|
||||
} else {
|
||||
$notResponding[] = $result['ip'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'responding' => $responding,
|
||||
'not_responding' => $notResponding,
|
||||
'stats' => [
|
||||
'total_checked' => count($results),
|
||||
'responding' => count($responding),
|
||||
'not_responding' => count($notResponding)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user