Files
JNC-Scan-Windows/src/gui/widgets/vlan_detection.py
T
joaquin.navajas 7add14d06f JNC Windows
2026-05-11 07:56:47 +02:00

158 lines
5.5 KiB
Python

"""
Widget de detección de VLANs en un trunk (Scapy).
Funciona en Windows (con Npcap) y en Linux.
"""
from PyQt6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QLabel,
QComboBox,
QPushButton,
QGroupBox,
QTableWidget,
QTableWidgetItem,
QHeaderView,
QMessageBox,
)
from PyQt6.QtCore import Qt
from src.core.vlan_scanner import VLANScanner
class VLANDetectionWidget(QWidget):
"""Solo detección pasiva de VLANs (Scapy). Sin bridge ni tcpdump."""
def __init__(self, network_manager):
super().__init__()
self.nm = network_manager
self.vlan_scanner = VLANScanner()
self.scan_thread = None
layout = QVBoxLayout(self)
group = QGroupBox("Detección de VLANs en trunk")
group_layout = QVBoxLayout(group)
controls = QHBoxLayout()
controls.addWidget(QLabel("Interfaz:"))
self.iface_combo = QComboBox()
controls.addWidget(self.iface_combo)
self.scan_btn = QPushButton("Iniciar escaneo (10 s)")
self.scan_btn.clicked.connect(self.start_scan)
controls.addWidget(self.scan_btn)
group_layout.addLayout(controls)
self.scan_table = QTableWidget()
self.scan_table.setColumnCount(2)
self.scan_table.setHorizontalHeaderLabels(["VLAN ID", "Paquetes detectados"])
self.scan_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.scan_table.setMinimumHeight(200)
group_layout.addWidget(self.scan_table)
self.port_type_label = QLabel("Tipo de puerto: —")
self.port_type_label.setStyleSheet("font-weight: bold; color: #aaaaaa;")
group_layout.addWidget(self.port_type_label)
layout.addWidget(group)
info = QLabel(
"Conecta esta interfaz a un puerto trunk y pulsa Iniciar escaneo. "
"En Windows requiere Npcap y ejecutar como Administrador."
)
info.setWordWrap(True)
info.setStyleSheet("color: #888; font-size: 11px;")
layout.addWidget(info)
self.refresh_interfaces()
def refresh_interfaces(self):
self.iface_combo.clear()
for iface in self.nm.list_interfaces():
self.iface_combo.addItem(iface["name"])
def start_scan(self):
if self.scan_thread and self.scan_thread.isRunning():
self.scan_thread.stop()
self.scan_btn.setEnabled(False)
self.scan_btn.setText("Deteniendo...")
return
iface = self.iface_combo.currentText()
if not iface:
QMessageBox.warning(self, "Error", "Selecciona una interfaz.")
return
self.scan_table.setRowCount(0)
self.port_type_label.setText("Tipo de puerto: Escaneando...")
self.port_type_label.setStyleSheet("font-weight: bold; color: #e0e000;")
self.scan_btn.setText("Detener escaneo")
self.scan_btn.setStyleSheet("background-color: #800000; color: white;")
self.scan_thread = self.vlan_scanner.scan_scapy(iface, timeout=10)
self.scan_thread.vlan_detected.connect(self.on_vlan_detected)
self.scan_thread.untagged_detected.connect(self.on_untagged_detected)
self.scan_thread.scan_finished.connect(self.on_scan_finished)
self.scan_thread.error_occurred.connect(self.on_scan_error)
def on_untagged_detected(self, count):
self._update_row("Untagged (Native)", count)
self._update_port_analysis()
def on_vlan_detected(self, vlan_id, count):
self._update_row(str(vlan_id), count)
self._update_port_analysis()
def _update_row(self, label, count):
for row in range(self.scan_table.rowCount()):
if self.scan_table.item(row, 0).text() == label:
self.scan_table.setItem(row, 1, QTableWidgetItem(str(count)))
return
row = self.scan_table.rowCount()
self.scan_table.insertRow(row)
self.scan_table.setItem(row, 0, QTableWidgetItem(label))
self.scan_table.setItem(row, 1, QTableWidgetItem(str(count)))
def _update_port_analysis(self):
has_untagged = False
vlan_count = 0
for row in range(self.scan_table.rowCount()):
label = self.scan_table.item(row, 0).text()
if label == "Untagged (Native)":
has_untagged = True
else:
vlan_count += 1
if vlan_count > 1:
msg = f"Trunk ({vlan_count} VLANs)"
color = "#00e000"
elif vlan_count == 1 and has_untagged:
msg = "Híbrido (tagged + untagged)"
color = "#e0e000"
elif vlan_count == 1:
msg = "Trunk (1 VLAN)"
color = "#00e000"
elif has_untagged:
msg = "Access (solo untagged)"
color = "#00e0e0"
else:
msg = "— / Sin tráfico"
color = "#aaaaaa"
self.port_type_label.setText(f"Tipo de puerto: {msg}")
self.port_type_label.setStyleSheet(f"font-weight: bold; color: {color};")
def on_scan_finished(self):
self.scan_btn.setEnabled(True)
self.scan_btn.setText("Iniciar escaneo (10 s)")
self.scan_btn.setStyleSheet("")
def on_scan_error(self, msg):
self.scan_btn.setEnabled(True)
self.scan_btn.setText("Iniciar escaneo (10 s)")
self.scan_btn.setStyleSheet("")
QMessageBox.critical(self, "Error", msg)
def closeEvent(self, event):
if self.scan_thread and self.scan_thread.isRunning():
self.scan_thread.stop()
event.accept()