class NetworkScannerDialog(QDialog):
"""Network scanner dialog with ping and port scan capabilities."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Network Scanner & Port Discovery")
self.setGeometry(200, 200, 900, 700)
self.setModal(False) # Allow interaction with main window
# Worker threads
self.ping_worker = None
self.port_worker = None
self._setup_ui()
self._setup_connections()
def _setup_ui(self):
"""Setup the user interface."""
layout = QVBoxLayout(self)
# Create tab widget
self.tab_widget = QTabWidget()
layout.addWidget(self.tab_widget)
# Ping tab
self.ping_tab = self._create_ping_tab()
self.tab_widget.addTab(self.ping_tab, "🏓 Ping Tool")
# Port scan tab
self.port_tab = self._create_port_scan_tab()
self.tab_widget.addTab(self.port_tab, "🔍 Port Scanner")
# Close button
close_layout = QHBoxLayout()
close_layout.addStretch()
self.close_button = QPushButton("Close")
close_layout.addWidget(self.close_button)
layout.addLayout(close_layout)
def _create_ping_tab(self) -> QWidget:
"""Create the ping tool tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
# Input section
input_group = QGroupBox("Ping Configuration")
input_layout = QVBoxLayout(input_group)
# Host input
host_layout = QHBoxLayout()
host_layout.addWidget(QLabel("Host/IP:"))
self.ping_host_edit = QLineEdit()
self.ping_host_edit.setPlaceholderText("Enter hostname or IP address (e.g., google.com, 8.8.8.8)")
host_layout.addWidget(self.ping_host_edit)
input_layout.addLayout(host_layout)
# Count input
count_layout = QHBoxLayout()
count_layout.addWidget(QLabel("Count:"))
self.ping_count_spin = QSpinBox()
self.ping_count_spin.setRange(1, 100)
self.ping_count_spin.setValue(4)
count_layout.addWidget(self.ping_count_spin)
count_layout.addStretch()
# Continuous ping checkbox
self.continuous_ping_check = QCheckBox("Continuous Ping")
count_layout.addWidget(self.continuous_ping_check)
input_layout.addLayout(count_layout)
# Control buttons
button_layout = QHBoxLayout()
self.ping_start_button = QPushButton("Start Ping")
self.ping_stop_button = QPushButton("Stop Ping")
self.ping_stop_button.setEnabled(False)
self.ping_clear_button = QPushButton("Clear Results")
button_layout.addWidget(self.ping_start_button)
button_layout.addWidget(self.ping_stop_button)
button_layout.addWidget(self.ping_clear_button)
button_layout.addStretch()
input_layout.addLayout(button_layout)
layout.addWidget(input_group)
# Results section
results_group = QGroupBox("Ping Results")
results_layout = QVBoxLayout(results_group)
# Statistics
stats_layout = QHBoxLayout()
self.ping_stats_label = QLabel("Ready to ping...")
stats_layout.addWidget(self.ping_stats_label)
results_layout.addLayout(stats_layout)
# Results text area
self.ping_results = QTextEdit()
self.ping_results.setReadOnly(True)
self.ping_results.setFont(QFont("Consolas", 9))
results_layout.addWidget(self.ping_results)
layout.addWidget(results_group)
return tab
def _create_port_scan_tab(self) -> QWidget:
"""Create the port scanner tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
# Input section
input_group = QGroupBox("Port Scan Configuration")
input_layout = QVBoxLayout(input_group)
# Host input
host_layout = QHBoxLayout()
host_layout.addWidget(QLabel("Host/IP:"))
self.scan_host_edit = QLineEdit()
self.scan_host_edit.setPlaceholderText("Enter hostname or IP address")
host_layout.addWidget(self.scan_host_edit)
input_layout.addLayout(host_layout)
# Port range
port_layout = QHBoxLayout()
port_layout.addWidget(QLabel("Port Range:"))
self.start_port_spin = QSpinBox()
self.start_port_spin.setRange(1, 65535)
self.start_port_spin.setValue(1)
port_layout.addWidget(self.start_port_spin)
port_layout.addWidget(QLabel("to"))
self.end_port_spin = QSpinBox()
self.end_port_spin.setRange(1, 65535)
self.end_port_spin.setValue(1000)
port_layout.addWidget(self.end_port_spin)
# Quick presets
preset_layout = QHBoxLayout()
preset_layout.addWidget(QLabel("Quick Scan:"))
self.common_ports_button = QPushButton("Common Ports")
self.common_ports_button.setToolTip("Scan common service ports (21,22,23,25,53,80,110,143,443,993,995,3389)")
preset_layout.addWidget(self.common_ports_button)
self.web_ports_button = QPushButton("Web Ports")
self.web_ports_button.setToolTip("Scan web-related ports (80,443,8080,8443,8000,3000,5000,9000)")
preset_layout.addWidget(self.web_ports_button)
self.db_ports_button = QPushButton("Database Ports")
self.db_ports_button.setToolTip("Scan database ports (3306,5432,1433,1521,6379,27017)")
preset_layout.addWidget(self.db_ports_button)
preset_layout.addStretch()
input_layout.addLayout(port_layout)
input_layout.addLayout(preset_layout)
# Timeout setting
timeout_layout = QHBoxLayout()
timeout_layout.addWidget(QLabel("Timeout (sec):"))
self.timeout_spin = QSpinBox()
self.timeout_spin.setRange(1, 10)
self.timeout_spin.setValue(2)
timeout_layout.addWidget(self.timeout_spin)
timeout_layout.addStretch()
input_layout.addLayout(timeout_layout)
# Control buttons
button_layout = QHBoxLayout()
self.scan_start_button = QPushButton("Start Scan")
self.scan_stop_button = QPushButton("Stop Scan")
self.scan_stop_button.setEnabled(False)
self.scan_clear_button = QPushButton("Clear Results")
button_layout.addWidget(self.scan_start_button)
button_layout.addWidget(self.scan_stop_button)
button_layout.addWidget(self.scan_clear_button)
button_layout.addStretch()
input_layout.addLayout(button_layout)
layout.addWidget(input_group)
# Progress bar
self.scan_progress = QProgressBar()
self.scan_progress.setVisible(False)
layout.addWidget(self.scan_progress)
# Results section
results_splitter = QSplitter(Qt.Vertical)
# Open ports table
open_group = QGroupBox("Open Ports")
open_layout = QVBoxLayout(open_group)
self.open_ports_table = QTableWidget()
self.open_ports_table.setColumnCount(3)
self.open_ports_table.setHorizontalHeaderLabels(["Port", "Service", "Status"])
self.open_ports_table.horizontalHeader().setStretchLastSection(True)
open_layout.addWidget(self.open_ports_table)
# Statistics
self.scan_stats_label = QLabel("Ready to scan...")
open_layout.addWidget(self.scan_stats_label)
results_splitter.addWidget(open_group)
# Scan log
log_group = QGroupBox("Scan Log")
log_layout = QVBoxLayout(log_group)
self.scan_log = QTextEdit()
self.scan_log.setReadOnly(True)
self.scan_log.setFont(QFont("Consolas", 8))
self.scan_log.setMaximumHeight(150)
log_layout.addWidget(self.scan_log)
results_splitter.addWidget(log_group)
layout.addWidget(results_splitter)
return tab
def _setup_connections(self):
"""Setup signal connections."""
# Ping tab connections
self.ping_start_button.clicked.connect(self._start_ping)
self.ping_stop_button.clicked.connect(self._stop_ping)
self.ping_clear_button.clicked.connect(self._clear_ping_results)
self.ping_host_edit.returnPressed.connect(self._start_ping)
# Port scan tab connections
self.scan_start_button.clicked.connect(self._start_port_scan)
self.scan_stop_button.clicked.connect(self._stop_port_scan)
self.scan_clear_button.clicked.connect(self._clear_scan_results)
self.scan_host_edit.returnPressed.connect(self._start_port_scan)
# Preset buttons
self.common_ports_button.clicked.connect(self._set_common_ports)
self.web_ports_button.clicked.connect(self._set_web_ports)
self.db_ports_button.clicked.connect(self._set_db_ports)
# Close button
self.close_button.clicked.connect(self.close)
def _start_ping(self):
"""Start ping operation."""
host = self.ping_host_edit.text().strip()
if not host:
QMessageBox.warning(self, "Input Error", "Please enter a host or IP address.")
return
count = self.ping_count_spin.value() if not self.continuous_ping_check.isChecked() else 999999
self.ping_start_button.setEnabled(False)
self.ping_stop_button.setEnabled(True)
self.ping_results.append(f"Starting ping to {host}...\n")
self.ping_worker = PingWorker(host, count)
self.ping_worker.ping_result.connect(self._handle_ping_result)
self.ping_worker.ping_finished.connect(self._ping_finished)
self.ping_worker.start()
# Initialize stats
self.ping_sent = 0
self.ping_received = 0
self.ping_times = []
def _stop_ping(self):
"""Stop ping operation."""
if self.ping_worker:
self.ping_worker.stop()
def _ping_finished(self):
"""Handle ping operation completion."""
self.ping_start_button.setEnabled(True)
self.ping_stop_button.setEnabled(False)
if self.ping_worker:
self.ping_worker.wait()
self.ping_worker = None
# Show final statistics
if self.ping_times:
avg_time = sum(self.ping_times) / len(self.ping_times)
min_time = min(self.ping_times)
max_time = max(self.ping_times)
loss_rate = ((self.ping_sent - self.ping_received) / self.ping_sent) * 100 if self.ping_sent > 0 else 0
self.ping_results.append(f"\n--- Ping Statistics ---")
self.ping_results.append(f"Packets: Sent = {self.ping_sent}, Received = {self.ping_received}, Lost = {self.ping_sent - self.ping_received} ({loss_rate:.1f}% loss)")
self.ping_results.append(f"Round-trip times: Min = {min_time:.1f}ms, Max = {max_time:.1f}ms, Avg = {avg_time:.1f}ms\n")
def _handle_ping_result(self, host: str, success: bool, response_time: float):
"""Handle individual ping result."""
self.ping_sent += 1
if success:
self.ping_received += 1
self.ping_times.append(response_time)
self.ping_results.append(f"Reply from {host}: time={response_time:.1f}ms")
else:
self.ping_results.append(f"Request timeout for {host}")
# Update statistics
loss_rate = ((self.ping_sent - self.ping_received) / self.ping_sent) * 100 if self.ping_sent > 0 else 0
self.ping_stats_label.setText(f"Sent: {self.ping_sent}, Received: {self.ping_received}, Loss: {loss_rate:.1f}%")
# Auto-scroll to bottom
cursor = self.ping_results.textCursor()
cursor.movePosition(cursor.MoveOperation.End)
self.ping_results.setTextCursor(cursor)
def _clear_ping_results(self):
"""Clear ping results."""
self.ping_results.clear()
self.ping_stats_label.setText("Ready to ping...")
def _start_port_scan(self):
"""Start port scan operation."""
host = self.scan_host_edit.text().strip()
if not host:
QMessageBox.warning(self, "Input Error", "Please enter a host or IP address.")
return
start_port = self.start_port_spin.value()
end_port = self.end_port_spin.value()
if start_port > end_port:
QMessageBox.warning(self, "Input Error", "Start port must be less than or equal to end port.")
return
timeout = self.timeout_spin.value()
self.scan_start_button.setEnabled(False)
self.scan_stop_button.setEnabled(True)
self.scan_progress.setVisible(True)
self.scan_progress.setRange(start_port, end_port)
# Clear previous results
self.open_ports_table.setRowCount(0)
self.scan_log.clear()
self.scan_log.append(f"Starting port scan on {host}:{start_port}-{end_port}...")
self.port_worker = PortScanWorker(host, start_port, end_port, timeout)
self.port_worker.port_result.connect(self._handle_port_result)
self.port_worker.scan_progress.connect(self._update_scan_progress)
self.port_worker.scan_finished.connect(self._scan_finished)
self.port_worker.start()
# Initialize stats
self.scanned_ports = 0
self.open_ports_count = 0
self.total_ports = end_port - start_port + 1
def _stop_port_scan(self):
"""Stop port scan operation."""
if self.port_worker:
self.port_worker.stop()
def _scan_finished(self):
"""Handle scan operation completion."""
self.scan_start_button.setEnabled(True)
self.scan_stop_button.setEnabled(False)
self.scan_progress.setVisible(False)
if self.port_worker:
self.port_worker.wait()
self.port_worker = None
self.scan_log.append(f"\nScan completed. Found {self.open_ports_count} open ports out of {self.scanned_ports} scanned.")
def _handle_port_result(self, host: str, port: int, is_open: bool, service: str):
"""Handle individual port scan result."""
self.scanned_ports += 1
if is_open:
self.open_ports_count += 1
# Add to open ports table
row = self.open_ports_table.rowCount()
self.open_ports_table.insertRow(row)
port_item = QTableWidgetItem(str(port))
service_item = QTableWidgetItem(service)
status_item = QTableWidgetItem("Open")
status_item.setBackground(QColor(144, 238, 144)) # Light green
self.open_ports_table.setItem(row, 0, port_item)
self.open_ports_table.setItem(row, 1, service_item)
self.open_ports_table.setItem(row, 2, status_item)
self.scan_log.append(f"Port {port} ({service}) - OPEN")
# Update statistics
self.scan_stats_label.setText(f"Scanned: {self.scanned_ports}/{self.total_ports}, Open: {self.open_ports_count}")
def _update_scan_progress(self, current_port: int):
"""Update scan progress."""
self.scan_progress.setValue(current_port)
def _clear_scan_results(self):
"""Clear scan results."""
self.open_ports_table.setRowCount(0)
self.scan_log.clear()
self.scan_stats_label.setText("Ready to scan...")
def _set_common_ports(self):
"""Set common ports range."""
# Most common service ports
common_ports = [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 993, 995, 3389, 5432, 3306]
self._set_custom_ports(common_ports)
def _set_web_ports(self):
"""Set web-related ports."""
web_ports = [80, 443, 8080, 8443, 8000, 3000, 5000, 9000, 8008, 8888]
self._set_custom_ports(web_ports)
def _set_db_ports(self):
"""Set database ports."""
db_ports = [3306, 5432, 1433, 1521, 6379, 27017, 5984, 9200, 11211]
self._set_custom_ports(db_ports)
def _set_custom_ports(self, ports: List[int]):
"""Set custom port range for scanning specific ports."""
if ports:
min_port = min(ports)
max_port = max(ports)
self.start_port_spin.setValue(min_port)
self.end_port_spin.setValue(max_port)
# Log which ports will be scanned
port_list = ", ".join(map(str, sorted(ports)))
self.scan_log.append(f"Quick scan preset selected. Will scan ports: {port_list}")
def closeEvent(self, event):
"""Handle dialog close event."""
# Stop any running operations
if self.ping_worker:
self.ping_worker.stop()
self.ping_worker.wait()
if self.port_worker:
self.port_worker.stop()
self.port_worker.wait()
event.accept()