JNC Windows
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user