Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56b688766e | |||
| 69b620f832 | |||
| a7c41a7235 | |||
| 594d13c0cc |
216
README.md
216
README.md
@@ -1,31 +1,41 @@
|
|||||||
# ⚡ IoT Firmware Loader (MiraV3)
|
# ⚡ Mira Firmware Loader
|
||||||
|
|
||||||
Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng loạt** cho các thiết bị OpenWrt (qua giao diện LuCI) trong mạng LAN.
|
Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng loạt** cho các thiết bị OpenWrt trong mạng LAN.
|
||||||
|
|
||||||
> **Tech stack:** Python 3.9+ · PyQt6 · Scapy · Requests · PyInstaller
|
> **Tech stack:** Python 3.9+ · PyQt6 · Paramiko/SCP · Scapy · Requests · PyInstaller
|
||||||
|
> **Phiên bản:** `1.1.2`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Cấu trúc dự án
|
## 📁 Cấu trúc dự án
|
||||||
|
|
||||||
```text
|
```text
|
||||||
iot_fw_loader/
|
Mira_Firmware_Loader/
|
||||||
├── main.py # UI chính (PyQt6)
|
├── main.py # UI chính (PyQt6)
|
||||||
├── core/ # Các thành phần cốt lõi và xử lý đa luồng
|
├── version.txt # Số phiên bản ứng dụng
|
||||||
│ ├── workers.py # Quản lý luồng dùng chung (ScanThread, FlashThread)
|
├── requirements.txt # Danh sách dependencies
|
||||||
│ ├── scanner.py # Quét thiết bị mạng đa lớp (Ping sweep + ARP + Scapy)
|
├── core/
|
||||||
│ ├── flasher.py # Flash firmware và tự động hóa qua OpenWrt LuCI bằng API
|
│ ├── scanner.py # Quét thiết bị mạng đa lớp (Ping sweep + ARP + Scapy)
|
||||||
│ └── ssh_flasher.py # Load/Update firmware qua đường dẫn SSH
|
│ ├── workers.py # ScanThread — chạy scanner trong background thread
|
||||||
├── ui/ # Các component thiết kế giao diện
|
│ ├── api_flash.py # Flash firmware qua LuCI HTTP API
|
||||||
│ ├── components.py # Custom Qt Widgets (CollapsibleGroupBox, etc.)
|
│ ├── ssh_utils.py # SSH/SCP transport helpers dùng chung
|
||||||
│ └── styles.py # Các cấu hình Stylesheet
|
│ ├── ssh_new_flash.py # Luồng SSH cho chế độ Nạp Mới (Telnet → set passwd → SSH)
|
||||||
├── utils/ # Các hàm helper tiện ích
|
│ ├── ssh_update_flash.py # Luồng SSH cho chế độ Update (SSH trực tiếp)
|
||||||
│ ├── network.py # Các hàm xử lý IP, Hostname
|
│ ├── flash_new_worker.py # NewFlashThread — QThread điều phối Nạp Mới FW
|
||||||
│ └── system.py # Các hàm lấy thông tin máy và resources
|
│ └── flash_update_worker.py # UpdateFlashThread — QThread điều phối Update FW
|
||||||
├── run.sh # Script khởi chạy (macOS/Linux)
|
├── ui/
|
||||||
├── run.bat # Script khởi chạy (Windows)
|
│ ├── components.py # Custom Qt Widgets (CollapsibleGroupBox)
|
||||||
├── build_windows.bat # Script đóng gói thành file .exe độc lập (Windows)
|
│ └── styles.py # Stylesheet toàn ứng dụng
|
||||||
└── venv/ # Python virtual environment (tự tạo)
|
├── utils/
|
||||||
|
│ ├── network.py # Helper IP / network (get_local_ip, get_default_network)
|
||||||
|
│ └── system.py # Lấy thông tin máy, resource path cho PyInstaller
|
||||||
|
├── docs/
|
||||||
|
│ ├── api_flash_docs.md # Tài liệu kỹ thuật LuCI API flash
|
||||||
|
│ ├── load_fw_ssh_docs.md # Tài liệu kỹ thuật SSH flash (cả 2 luồng)
|
||||||
|
│ └── scanner_docs.md # Tài liệu kỹ thuật scanner
|
||||||
|
├── run.sh # Script khởi chạy (macOS/Linux)
|
||||||
|
├── run.bat # Script khởi chạy (Windows)
|
||||||
|
└── build_windows.bat # Script đóng gói thành .exe (Windows, PyInstaller)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -34,11 +44,11 @@ iot_fw_loader/
|
|||||||
|
|
||||||
### Yêu cầu
|
### Yêu cầu
|
||||||
|
|
||||||
- Python 3.9+
|
- Python **3.9+**
|
||||||
- Các thư viện: `PyQt6`, `scapy`, `requests`, `pyinstaller` (để build).
|
- Thư viện: `PyQt6`, `scapy`, `requests`, `paramiko`, `scp`, `pyinstaller` (để build)
|
||||||
- _Trên Windows:_ Cần cài đặt [Npcap](https://npcap.com/) để `scapy` có thể quét ARP ở chế độ sâu (không bắt buộc, có fallback dự phòng).
|
- _Windows:_ Cài [Npcap](https://npcap.com/) để Scapy có thể gửi ARP broadcast (không bắt buộc — có fallback)
|
||||||
|
|
||||||
### Khởi chạy nhanh (Môi trường Dev)
|
### Khởi chạy nhanh
|
||||||
|
|
||||||
**macOS / Linux:**
|
**macOS / Linux:**
|
||||||
|
|
||||||
@@ -54,48 +64,56 @@ run.bat
|
|||||||
|
|
||||||
> Script tự tạo `venv` và cài dependencies nếu chưa có.
|
> Script tự tạo `venv` và cài dependencies nếu chưa có.
|
||||||
|
|
||||||
### 📦 Build ra file chạy độc lập (.exe) cho Windows
|
### 📦 Build file chạy độc lập (.exe) cho Windows
|
||||||
|
|
||||||
Chạy script sau để tự động đóng gói ứng dụng thành 1 file `.exe` duy nhất (không cần cài Python trên máy đích):
|
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
build_windows.bat
|
build_windows.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
File nhận được sẽ nằm ở: `dist\IoT_Firmware_Loader.exe`.
|
Output: `dist\Mira_Firmware_Loader.exe` — không cần cài Python trên máy đích.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🏗 Kiến trúc hệ thống
|
## 🏗 Kiến trúc hệ thống
|
||||||
|
|
||||||
```text
|
```
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────┐
|
||||||
│ main.py (UI) │
|
│ main.py (UI) │
|
||||||
│ ┌───────────┐ ┌───────────┐ ┌────────────────────────────┐ │
|
│ │
|
||||||
│ │ Machine │ │ Firmware │ │ Network Scan (QThread) │ │
|
│ Machine Info │ Firmware Selector │ Network Scan │
|
||||||
│ │ Info │ │ Selector │ │ Tham số: Network (CIDR) │ │
|
│ ───────────────────────────────────────────────────── │
|
||||||
│ └───────────┘ └───────────┘ └────────┬───────────────────┘ │
|
│ Device Table [ ] IP │ MAC │ Status │
|
||||||
│ ┌────────────────────────────────────┼───────────────────┐ │
|
│ ───────────────────────────────────────────────────── │
|
||||||
│ │ Device Table │ │ │
|
│ Flash Controls │
|
||||||
│ │ [ ] IP │ Name │ MAC │ Status │ │ │
|
│ Flash Mode: [ New Flash | Update Firmware ] │
|
||||||
│ │ (Hỗ trợ lọc ẩn Gateway & Self) │ │ │
|
│ Method: [ API (LuCI) | SSH (paramiko) ] │
|
||||||
│ └────────────────────────────────────┼───────────────────┘ │
|
│ Concurrent devices: [SpinBox] │
|
||||||
│ ┌────────────────────────────────────┼───────────────────┐ │
|
│ [ ⚡ FLASH SELECTED DEVICES ] │
|
||||||
│ │ Flash Controls + Progress Bar │ │ │
|
└────────────────────────┬─────────────────────────────────┘
|
||||||
│ │ (ThreadPoolExecutor) │ │ │
|
│
|
||||||
│ └────────────────────────────────────┼───────────────────┘ │
|
┌──────────────┼──────────────┐
|
||||||
└───────────────────────────────────────┼─────────────────────┘
|
│ │
|
||||||
│
|
┌──────▼──────┐ ┌────────▼────────────────┐
|
||||||
┌───────────────────┼───────────────────┐
|
│ scanner.py │ │ Flash Workers │
|
||||||
│ │
|
│ │ │ │
|
||||||
┌─────▼─────┐ ┌─────▼─────┐
|
│ 1. Ping │ │ NewFlashThread │
|
||||||
│ scanner.py│ │ flasher.py│
|
│ Sweep │ │ ├─ method=api │
|
||||||
│ │ │ │
|
│ 2. arp -a │ │ │ └── api_flash.py │
|
||||||
│ 1. Ping │ │ LuCI HTTP │
|
│ 3. Scapy │ │ └─ method=ssh │
|
||||||
│ Sweep │ │ /cgi-bin/ │
|
│ ARP │ │ └── ssh_new_flash │
|
||||||
│ 2. arp -a │ │ luci │
|
└─────────────┘ │ │
|
||||||
│ 3. Scapy │ │ │
|
│ UpdateFlashThread │
|
||||||
└───────────┘ └───────────┘
|
│ └─ ssh_update_flash.py │
|
||||||
|
└─────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────▼──────────┐
|
||||||
|
│ ssh_utils.py │
|
||||||
|
│ (Transport Layer) │
|
||||||
|
│ _create_ssh_client │
|
||||||
|
│ _upload_firmware │
|
||||||
|
│ _verify_firmware │
|
||||||
|
│ _sync_and_sysupgr │
|
||||||
|
└─────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -104,44 +122,78 @@ File nhận được sẽ nằm ở: `dist\IoT_Firmware_Loader.exe`.
|
|||||||
|
|
||||||
### 1. Quét mạng (Network Scan)
|
### 1. Quét mạng (Network Scan)
|
||||||
|
|
||||||
Module `scanner.py` sử dụng chiến lược 3 lớp để đảm bảo phát hiện đa dạng các thiết bị mạng mà vẫn duy trì khả năng tránh spam/lag cho OS:
|
`scanner.py` dùng chiến lược 3 lớp, đảm bảo phát hiện đầy đủ mà không cần quyền Root:
|
||||||
|
|
||||||
1. **Ping Sweep:** Gửi gói tin Ping song song (tối đa 50 threads) để đánh thức thiết bị và điền IP/MAC vào bảng ARP Cache của hệ điều hành.
|
| Giai đoạn | Mô tả |
|
||||||
2. **ARP Table Fallback:** Đọc bảng ARP nội bộ của OS (`arp -a`) bằng Regex. Hoạt động đa nền tảng (Windows/macOS/Linux) mà không cần quyền Admin/Root.
|
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
3. **Scapy ARP (Tính năng nâng cao):** Gửi các gói tin ARP Broadcast trực tiếp để đảm bảo bao phủ gap nếu có. Yêu cầu quyền Root trên Linux/macOS hoặc Npcap trên Windows.
|
| **Ping Sweep** | Gửi ping đồng thời tới toàn bộ host trong dải `/24` (tất cả cùng lúc, không batching) để đánh thức thiết bị và điền ARP cache |
|
||||||
_Ngoài ra, công cụ tự động dò Hostname (`socket.gethostbyaddr`) song song để lấy tên thiết bị._
|
| **ARP Table** | Đọc `arp -a` bằng regex, hỗ trợ cả định dạng Windows (`cc-2d-...`) và macOS/Linux (`aa:bb:...`) |
|
||||||
|
| **Scapy ARP** | Chạy **song song** với ARP Table — gửi ARP broadcast để lấp khoảng trống. Yêu cầu Npcap (Windows) hoặc root (Linux); tự động bỏ qua nếu không khả dụng |
|
||||||
|
|
||||||
### 2. Giao diện thiết bị (Device Table)
|
Kết quả được merge theo IP và sort tăng dần trước khi trả về UI.
|
||||||
|
|
||||||
- Thiết bị được thu thập sẽ hiện ra trên UI dạng bảng, với tính năng tuỳ chọn hiển thị (checkbox ẩn Gateway mạng hiện tại và chính máy tính đang quét phần mềm).
|
### 2. Bảng thiết bị (Device Table)
|
||||||
- Trạng thái các luồng UI được tách rời bằng QThread + QPropertyAnimation cho hộp Collapsible nhằm tự động cuộn khi ẩn hiện nội dung, không làm treo ứng dụng.
|
|
||||||
|
|
||||||
### 3. Nạp Firmware (OpenWrt Flasher)
|
- Hiển thị cột: checkbox, IP, MAC, Status
|
||||||
|
- Mặc định ẩn gateway và IP máy tính đang chạy (có thể bật "Show all")
|
||||||
|
- Thiết bị đã flash trong session được đánh dấu "Already Flashed" và tự bỏ tick
|
||||||
|
|
||||||
Từ phiên bản hiện tại, phương thức ESP32 OTA không còn được áp dụng, thay vào đó module `flasher.py` tự động hóa quá trình update của router **OpenWrt (Barrier Breaker 14.07)**:
|
### 3. Flash Firmware
|
||||||
|
|
||||||
- **Bước 1:** Gửi `POST` chứa thông tin đăng nhập (username, password mặc định là root/trống, hoặc luci_username tuỳ thuộc firmware) để lấy session cookie `stok`.
|
Có 2 chế độ và 2 method, tổng cộng 3 luồng thực thi khác nhau:
|
||||||
- **Bước 2:** Đẩy file firmware `*.bin` dạng `multipart/form-data` lên `/cgi-bin/luci/;stok=.../admin/system/flashops`.
|
|
||||||
- **Bước 3:** Gửi lệnh tiếp tục (Kèm tuỳ chọn giữ cấu hình `keep=on` nếu có).
|
#### Chế độ `New Flash` — dùng cho thiết bị vừa reset cứng
|
||||||
- **Bước 4:** Thiết bị xác nhận và tự khởi động lại. Cập nhật Status trên Application.
|
|
||||||
- 🛠 Hỗ trợ `ThreadPoolExecutor` để Flash đồng thời nhiều thiết bị, với lựa chọn số luồng đồng thời tuỳ chỉnh.
|
| Method | Luồng | Mô tả |
|
||||||
|
| -------------- | ------------------ | ------------------------------------------------------------------------------------------- |
|
||||||
|
| **API (LuCI)** | `api_flash.py` | Đăng nhập LuCI → upload firmware → Proceed. Hỗ trợ Barrier Breaker 14.07 và OpenWrt mới hơn |
|
||||||
|
| **SSH** | `ssh_new_flash.py` | Kết nối Telnet → đặt password mới → SSH → SCP upload → sysupgrade |
|
||||||
|
|
||||||
|
**Luồng SSH – New Flash chi tiết:**
|
||||||
|
|
||||||
|
1. Telnet port 23 (thiết bị vừa reset, chưa có pass)
|
||||||
|
2. Đặt password `admin123a` qua lệnh `passwd`
|
||||||
|
3. SSH vào với password vừa đặt
|
||||||
|
4. SCP upload firmware lên `/tmp/`
|
||||||
|
5. Verify file tồn tại
|
||||||
|
6. `sync && sysupgrade -F -v -n` → thiết bị reboot (connection drop = DONE)
|
||||||
|
|
||||||
|
#### Chế độ `Update Firmware` — dùng cho thiết bị đang chạy OpenWrt
|
||||||
|
|
||||||
|
Luồng `ssh_update_flash.py`, SSH trực tiếp (không qua Telnet):
|
||||||
|
|
||||||
|
1. SSH kết nối với `root` / `admin123a` (fallback: `admin`)
|
||||||
|
2. SCP upload firmware lên `/tmp/`
|
||||||
|
3. Verify file tồn tại
|
||||||
|
4. `sync && sysupgrade -F -v -n` → thiết bị reboot
|
||||||
|
|
||||||
|
> ⚠️ Update Mode hiển thị cảnh báo nếu IP thiết bị khác `192.168.11.102` và yêu cầu xác nhận.
|
||||||
|
|
||||||
|
### 4. Xử lý song song
|
||||||
|
|
||||||
|
| Tham số | Mô tả |
|
||||||
|
| ---------------------- | ------------------------------------------------------------------------ |
|
||||||
|
| **Concurrent devices** | Số thiết bị flash đồng thời (`ThreadPoolExecutor`). `0` = không giới hạn |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ Cấu hình
|
## ⚙️ Cấu hình mặc định
|
||||||
|
|
||||||
| Tham số | Mô tả |
|
| Tham số | Giá trị mặc định | Mô tả |
|
||||||
| ---------------------- | ------------------------------------------------------------------- |
|
| ---------------------- | ------------------------------------ | --------------------------------- |
|
||||||
| **Network** | Dải mạng quét (vd: `192.168.1.0/24`). Tự động tính từ local IP app. |
|
| **Network** | Tự suy ra từ local IP (`x.x.x.0/24`) | Dải mạng để quét |
|
||||||
| **Show all** | Tuỳ chọn cho phép liệt kê cả Gateway IP máy chủ. |
|
| **Flash Mode** | `New Flash` | Nạp mới hoặc Update |
|
||||||
| **Concurrent devices** | Số luồng để đĩa flash song song. `0` = Không giới hạn. |
|
| **Method** | `API (LuCI)` | Phương thức flash cho New Flash |
|
||||||
| **Firmware filter** | Mặc định hiển thị và hỗ trợ `.bin`, `.hex`, `.uf2`. |
|
| **SSH User** | `root` | Hardcoded, không hiển thị trên UI |
|
||||||
|
| **SSH Password** | `admin123a` | Hardcoded, không hiển thị trên UI |
|
||||||
|
| **Concurrent devices** | `10` | Số luồng flash song song |
|
||||||
|
| **Show all** | Tắt | Ẩn gateway và IP máy host |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔒 Lưu ý bảo mật & Quyền
|
## 🔒 Lưu ý bảo mật & Quyền
|
||||||
|
|
||||||
- **Quyền Scanner:** Ở chế độ quét Scapy (Sâu), cần khởi chạy bằng quyền Admin (Windows) hoặc Sudo (macOS/Linux), tuy nhiên App có thể chạy kể cả không có quyền Admin nhờ chiến lược Ping Sweep + `arp -a`.
|
- **Scapy (chế độ sâu):** Cần Npcap (Windows) hoặc `sudo` (macOS/Linux). App vẫn hoạt động mà không cần quyền Admin nhờ fallback Ping Sweep + `arp -a`.
|
||||||
- **Ẩn Console (Window):** Khi gọi subproces (`ping`, `arp`), ứng dụng có tích hợp thuộc tính platform `CREATE_NO_WINDOW` để ngăn chặn các terminal đen giật nháy trên màn hình Windows.
|
- **CREATE_NO_WINDOW:** Khi gọi subprocess (`ping`, `arp`), ứng dụng dùng flag `CREATE_NO_WINDOW` trên Windows để ngăn cửa sổ console hiện ra.
|
||||||
- **Bảo mật mạng:** Quá trình tải firmware lên thiết bị thông qua HTTP thuần, chỉ nên sử dụng ở mạng LAN nội bộ, tránh những mạng mở hoặc public để tránh lộ file firmware.
|
- **HTTP thuần:** Firmware được upload qua HTTP (không HTTPS). Chỉ dùng trong mạng LAN nội bộ, không nên dùng trên mạng mở.
|
||||||
- **LuCI Login:** API này nhắm thẳng vào quá trình đăng nhập qua form, nên sẽ tự động handle các router đang dùng OpenWrt Barrier Breaker mà không cần phải can thiệp tay.
|
- **Credentials cố định:** SSH credentials (`root`/`admin123a`) được hardcode trong `flash_update_worker.py` và truyền từ `main.py`, không hiển thị trên UI.
|
||||||
|
|||||||
251
core/scanner.py
251
core/scanner.py
@@ -9,57 +9,42 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|||||||
_NO_WINDOW = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
|
_NO_WINDOW = subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0
|
||||||
|
|
||||||
|
|
||||||
def _scan_with_scapy(network):
|
|
||||||
"""Scan using scapy (requires root/sudo, and Npcap on Windows)."""
|
|
||||||
from scapy.all import ARP, Ether, srp
|
|
||||||
|
|
||||||
arp = ARP(pdst=str(network))
|
|
||||||
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
|
|
||||||
|
|
||||||
packet = ether / arp
|
|
||||||
|
|
||||||
result = srp(packet, timeout=3, verbose=0)[0]
|
|
||||||
|
|
||||||
devices = []
|
|
||||||
|
|
||||||
for sent, received in result:
|
|
||||||
devices.append({
|
|
||||||
"ip": received.psrc,
|
|
||||||
"mac": received.hwsrc
|
|
||||||
})
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def _ping_one(ip, is_win):
|
def _ping_one(ip, is_win):
|
||||||
"""Ping a single IP to populate ARP table."""
|
"""Ping a single IP to populate ARP table."""
|
||||||
try:
|
try:
|
||||||
if is_win:
|
if is_win:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["ping", "-n", "1", "-w", "600", str(ip)],
|
["ping", "-n", "1", "-w", "300", str(ip)], # 300ms (was 600ms)
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
timeout=3,
|
timeout=2,
|
||||||
creationflags=_NO_WINDOW
|
creationflags=_NO_WINDOW
|
||||||
)
|
)
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
subprocess.run(
|
||||||
|
["ping", "-c", "1", "-W", "500", str(ip)], # 500ms — macOS: -W unit là ms
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
timeout=2
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["ping", "-c", "1", "-W", "1", str(ip)],
|
["ping", "-c", "1", "-W", "1", str(ip)], # 1s — Linux: -W unit là giây
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
timeout=3
|
timeout=2
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _ping_sweep(network, progress_cb=None):
|
def _ping_sweep(network, progress_cb=None):
|
||||||
"""Ping all IPs in network concurrently to populate ARP table.
|
"""Ping tất cả host trong network đồng thời để điền ARP cache.
|
||||||
Calls progress_cb(done, total) after each ping completes if provided.
|
Gọi progress_cb(done, total) sau mỗi ping nếu được cung cấp.
|
||||||
"""
|
"""
|
||||||
net = ipaddress.ip_network(network, strict=False)
|
net = ipaddress.ip_network(network, strict=False)
|
||||||
|
|
||||||
# Only ping sweep for /24 or smaller to avoid flooding
|
# Chỉ ping sweep cho /24 hoặc nhỏ hơn
|
||||||
if net.num_addresses > 256:
|
if net.num_addresses > 256:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -74,151 +59,97 @@ def _ping_sweep(network, progress_cb=None):
|
|||||||
if progress_cb:
|
if progress_cb:
|
||||||
progress_cb(done_count[0], total)
|
progress_cb(done_count[0], total)
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=100) as executor:
|
# Tất cả host cùng lúc — loại bỏ overhead batching (was max_workers=100)
|
||||||
|
with ThreadPoolExecutor(max_workers=len(hosts)) as executor:
|
||||||
futures = [executor.submit(_ping_and_track, ip) for ip in hosts]
|
futures = [executor.submit(_ping_and_track, ip) for ip in hosts]
|
||||||
for f in as_completed(futures):
|
for f in as_completed(futures):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _scan_with_arp_table(network):
|
|
||||||
"""Fallback: read ARP table using system 'arp -a' (no root needed).
|
|
||||||
Supports both macOS and Windows output formats.
|
|
||||||
"""
|
|
||||||
# Ping sweep first to populate ARP table with active devices
|
|
||||||
_ping_sweep(network)
|
|
||||||
|
|
||||||
# Brief pause to let the OS finalize ARP cache entries
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
["arp", "-a"], text=True, creationflags=_NO_WINDOW
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
|
|
||||||
devices = []
|
|
||||||
net = ipaddress.ip_network(network, strict=False)
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
|
||||||
# Windows format:
|
|
||||||
# 192.168.4.1 cc-2d-21-a5-85-b0 dynamic
|
|
||||||
pattern = re.compile(
|
|
||||||
r"(\d+\.\d+\.\d+\.\d+)\s+"
|
|
||||||
r"([0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-"
|
|
||||||
r"[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2})\s+"
|
|
||||||
r"(dynamic|static)",
|
|
||||||
re.IGNORECASE
|
|
||||||
)
|
|
||||||
|
|
||||||
for line in output.splitlines():
|
|
||||||
m = pattern.search(line)
|
|
||||||
if m:
|
|
||||||
ip_str = m.group(1)
|
|
||||||
# Convert Windows MAC format (cc-2d-21-a5-85-b0) to standard (cc:2d:21:a5:85:b0)
|
|
||||||
mac = m.group(2).replace("-", ":")
|
|
||||||
if mac.upper() != "FF:FF:FF:FF:FF:FF":
|
|
||||||
try:
|
|
||||||
if ipaddress.ip_address(ip_str) in net:
|
|
||||||
devices.append({"ip": ip_str, "mac": mac})
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# macOS/Linux format:
|
|
||||||
# ? (192.168.1.1) at aa:bb:cc:dd:ee:ff on en0
|
|
||||||
pattern = re.compile(
|
|
||||||
r"\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-fA-F:]+)"
|
|
||||||
)
|
|
||||||
|
|
||||||
for line in output.splitlines():
|
|
||||||
m = pattern.search(line)
|
|
||||||
if m:
|
|
||||||
ip_str, mac = m.group(1), m.group(2)
|
|
||||||
if mac.lower() != "(incomplete)" and mac != "ff:ff:ff:ff:ff:ff":
|
|
||||||
try:
|
|
||||||
if ipaddress.ip_address(ip_str) in net:
|
|
||||||
devices.append({"ip": ip_str, "mac": mac})
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return devices
|
|
||||||
|
|
||||||
|
|
||||||
def scan_network(network, progress_cb=None, stage_cb=None):
|
def scan_network(network, progress_cb=None, stage_cb=None):
|
||||||
"""Scan network: ping sweep first, then merge scapy ARP + arp table."""
|
"""Scan network: ping sweep → ARP table + Scapy song song."""
|
||||||
# Phase 1: Ping sweep — wake up devices and populate ARP cache
|
# Phase 1: Ping sweep
|
||||||
if stage_cb:
|
if stage_cb:
|
||||||
stage_cb("ping")
|
stage_cb("ping")
|
||||||
_ping_sweep(network, progress_cb)
|
_ping_sweep(network, progress_cb)
|
||||||
time.sleep(1)
|
time.sleep(0.3) # Giảm từ 1s xuống 0.3s
|
||||||
|
|
||||||
# Collect results from both methods and merge by IP
|
# Phase 2 + 3: ARP table và Scapy chạy song song
|
||||||
seen = {} # ip -> device dict
|
|
||||||
|
|
||||||
# Phase 2: ARP table (populated by ping sweep above)
|
|
||||||
if stage_cb:
|
if stage_cb:
|
||||||
stage_cb("arp")
|
stage_cb("arp")
|
||||||
try:
|
|
||||||
output = subprocess.check_output(
|
|
||||||
["arp", "-a"], text=True, creationflags=_NO_WINDOW
|
|
||||||
)
|
|
||||||
net = ipaddress.ip_network(network, strict=False)
|
|
||||||
if sys.platform == "win32":
|
|
||||||
pattern = re.compile(
|
|
||||||
r"(\d+\.\d+\.\d+\.\d+)\s+"
|
|
||||||
r"([0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-"
|
|
||||||
r"[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2})\s+"
|
|
||||||
r"(dynamic|static)",
|
|
||||||
re.IGNORECASE
|
|
||||||
)
|
|
||||||
for line in output.splitlines():
|
|
||||||
m = pattern.search(line)
|
|
||||||
if m:
|
|
||||||
ip_str = m.group(1)
|
|
||||||
mac = m.group(2).replace("-", ":")
|
|
||||||
if mac.upper() != "FF:FF:FF:FF:FF:FF":
|
|
||||||
try:
|
|
||||||
if ipaddress.ip_address(ip_str) in net:
|
|
||||||
seen[ip_str] = {"ip": ip_str, "mac": mac}
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
pattern = re.compile(
|
|
||||||
r"\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-fA-F:]+)"
|
|
||||||
)
|
|
||||||
for line in output.splitlines():
|
|
||||||
m = pattern.search(line)
|
|
||||||
if m:
|
|
||||||
ip_str, mac = m.group(1), m.group(2)
|
|
||||||
if mac.lower() not in ("(incomplete)", "ff:ff:ff:ff:ff:ff"):
|
|
||||||
try:
|
|
||||||
if ipaddress.ip_address(ip_str) in net:
|
|
||||||
seen[ip_str] = {"ip": ip_str, "mac": mac}
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Phase 3: scapy ARP scan (if Npcap available) — fills in any gaps
|
def _collect_arp():
|
||||||
if stage_cb:
|
result = {}
|
||||||
stage_cb("scapy")
|
|
||||||
try:
|
|
||||||
import io, os
|
|
||||||
_stderr = sys.stderr
|
|
||||||
sys.stderr = io.StringIO()
|
|
||||||
try:
|
try:
|
||||||
from scapy.all import ARP, Ether, srp
|
output = subprocess.check_output(
|
||||||
finally:
|
["arp", "-a"], text=True, creationflags=_NO_WINDOW
|
||||||
sys.stderr = _stderr
|
)
|
||||||
|
net = ipaddress.ip_network(network, strict=False)
|
||||||
|
if sys.platform == "win32":
|
||||||
|
pattern = re.compile(
|
||||||
|
r"(\d+\.\d+\.\d+\.\d+)\s+"
|
||||||
|
r"([0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-"
|
||||||
|
r"[0-9a-fA-F]{2}-[0-9a-fA-F]{2}-[0-9a-fA-F]{2})\s+"
|
||||||
|
r"(dynamic|static)",
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
for line in output.splitlines():
|
||||||
|
m = pattern.search(line)
|
||||||
|
if m:
|
||||||
|
ip_str = m.group(1)
|
||||||
|
mac = m.group(2).replace("-", ":")
|
||||||
|
if mac.upper() != "FF:FF:FF:FF:FF:FF":
|
||||||
|
try:
|
||||||
|
if ipaddress.ip_address(ip_str) in net:
|
||||||
|
result[ip_str] = {"ip": ip_str, "mac": mac}
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pattern = re.compile(
|
||||||
|
r"\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-fA-F:]+)"
|
||||||
|
)
|
||||||
|
for line in output.splitlines():
|
||||||
|
m = pattern.search(line)
|
||||||
|
if m:
|
||||||
|
ip_str, mac = m.group(1), m.group(2)
|
||||||
|
if mac.lower() not in ("(incomplete)", "ff:ff:ff:ff:ff:ff"):
|
||||||
|
try:
|
||||||
|
if ipaddress.ip_address(ip_str) in net:
|
||||||
|
result[ip_str] = {"ip": ip_str, "mac": mac}
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
arp = ARP(pdst=str(network))
|
def _collect_scapy():
|
||||||
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
|
result = {}
|
||||||
result = srp(ether / arp, timeout=2, verbose=0)[0]
|
try:
|
||||||
for sent, received in result:
|
import io
|
||||||
ip = received.psrc
|
_stderr = sys.stderr
|
||||||
|
sys.stderr = io.StringIO()
|
||||||
|
try:
|
||||||
|
from scapy.all import ARP, Ether, srp
|
||||||
|
finally:
|
||||||
|
sys.stderr = _stderr
|
||||||
|
|
||||||
|
arp = ARP(pdst=str(network))
|
||||||
|
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
|
||||||
|
raw = srp(ether / arp, timeout=1, verbose=0)[0] # Giảm từ 2s xuống 1s
|
||||||
|
for _, received in raw:
|
||||||
|
result[received.psrc] = {"ip": received.psrc, "mac": received.hwsrc}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Chạy ARP read và Scapy đồng thời
|
||||||
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||||
|
f_arp = executor.submit(_collect_arp)
|
||||||
|
f_scapy = executor.submit(_collect_scapy)
|
||||||
|
seen = f_arp.result()
|
||||||
|
# Scapy bổ sung những IP chưa có trong ARP table
|
||||||
|
for ip, dev in f_scapy.result().items():
|
||||||
if ip not in seen:
|
if ip not in seen:
|
||||||
seen[ip] = {"ip": ip, "mac": received.hwsrc}
|
seen[ip] = dev
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return sorted(seen.values(), key=lambda d: ipaddress.ip_address(d["ip"]))
|
return sorted(seen.values(), key=lambda d: ipaddress.ip_address(d["ip"]))
|
||||||
|
|||||||
@@ -1,59 +1,101 @@
|
|||||||
# Tài liệu Kỹ thuật: Cơ chế Quét IP trên Mạng (Network Scanner)
|
# Tài liệu Kỹ thuật: Cơ chế Quét IP trên Mạng (Network Scanner)
|
||||||
|
|
||||||
## 1. Tổng quan
|
## 1. Tổng quan
|
||||||
Thành phần quét IP (IP Scanner) trong file `scanner.py` được thiết kế để dò tìm và liệt kê tất cả các thiết bị đang hoạt động trên một dải mạng (ví dụ: `192.168.1.0/24`). Nó theo dõi và trả về danh sách các đối tượng chứa địa chỉ **IP** và **MAC Address** của từng thiết bị.
|
|
||||||
|
|
||||||
Để đảm bảo tỷ lệ phát hiện cao nhất trên các hệ điều hành khác nhau (Windows, macOS, Linux), script sử dụng kết hợp hai phương pháp dò tìm:
|
Thành phần quét IP trong `scanner.py` dò tìm và liệt kê tất cả thiết bị đang hoạt động trên một dải mạng (ví dụ: `192.168.1.0/24`), trả về danh sách chứa **IP** và **MAC Address** của từng thiết bị.
|
||||||
1. **Quét mồi bằng Ping (Ping Sweep) kết hợp đọc bảng ARP tĩnh của hệ điều hành.**
|
|
||||||
2. **Quét ARP trực tiếp ở Tầng 2 (Layer 2) bằng thư viện `scapy`.**
|
Để đảm bảo tỷ lệ phát hiện cao trên mọi hệ điều hành (Windows, macOS, Linux), scanner kết hợp 3 giai đoạn:
|
||||||
|
|
||||||
|
1. **Ping Sweep** — đánh thức thiết bị, điền ARP cache
|
||||||
|
2. **Đọc bảng ARP hệ điều hành** (`arp -a`) — không cần quyền Admin
|
||||||
|
3. **Scapy ARP broadcast** — bổ sung thiết bị chặn ICMP
|
||||||
|
|
||||||
|
Bước 2 và 3 chạy **song song** để giảm tổng thời gian scan.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Luồng hoạt động chính (Hàm `scan_network`)
|
## 2. Luồng hoạt động chính (Hàm `scan_network`)
|
||||||
|
|
||||||
Đây là hàm được gọi trực tiếp khi muốn quét một mạng. Trình tự rà quét diễn ra qua các bước sau:
|
**Bước 1 — Ping Sweep**
|
||||||
|
|
||||||
**Bước 1: "Đánh thức" các thiết bị (Ping Sweep)**
|
- Gọi `_ping_sweep(network)`: gửi ICMP Echo Request đồng thời tới **toàn bộ host** trong dải mạng.
|
||||||
- Hệ thống gọi hàm `_ping_sweep(network)` để gửi gói Ping (ICMP Echo Request) đồng loạt tới tất cả các IP có thể có trong mạng.
|
- Mỗi thiết bị phản hồi sẽ khiến hệ điều hành **tự ghi MAC vào ARP Cache**.
|
||||||
- Mục đích của bước này là buộc các thiết bị phản hồi, từ đó hệ điều hành của máy đang chạy lệnh sẽ **tự động ghi nhận địa chỉ MAC** của các thiết bị đó vào bộ nhớ đệm ARP (ARP Cache).
|
- Sau khi sweep xong, chờ `0.3s` để OS kịp finalize ARP cache (giảm từ 1s trước đây).
|
||||||
- Hệ thống tạm dừng 1 giây để đảm bảo hệ điều hành kịp lưu thông tin vào ARP Cache.
|
|
||||||
|
|
||||||
**Bước 2: Lấy dữ liệu từ bảng ARP của Hệ điều hành (Method 1)**
|
**Bước 2 + 3 — ARP Table & Scapy (song song)**
|
||||||
- Thực thi lệnh hệ thống `arp -a` để đọc ARP Cache.
|
|
||||||
- Kết quả được phân tích cú pháp (Regex) để trích xuất IP và MAC tương ứng, tương thích linh hoạt với cả đầu ra của Windows lẫn macOS/Linux.
|
|
||||||
- Các thiết bị đọc được lưu vào danh sách nháp (biến `seen`).
|
|
||||||
|
|
||||||
**Bước 3: Quét sâu với Scapy (Method 2 - Dự phòng/Bổ sung)**
|
- Hai hàm `_collect_arp()` và `_collect_scapy()` được submit vào `ThreadPoolExecutor(max_workers=2)` và chạy đồng thời:
|
||||||
- Script gọi thêm thư viện `scapy` để phát một thông điệp "ARP Who-has" tới địa chỉ MAC Broadcast (`ff:ff:ff:ff:ff:ff`).
|
- `_collect_arp()`: đọc `arp -a`, parse regex lấy IP + MAC.
|
||||||
- Phương pháp này giúp phát hiện ra các thiết bị chặn Ping (chặn ICMP) ở Bước 1 nhưng buộc phải phản hồi gói ARP ở tầng Data Link.
|
- `_collect_scapy()`: gửi ARP broadcast, nhận phản hồi trực tiếp từ thiết bị.
|
||||||
- Những thiết bị mới tìm thấy (nếu chưa có trong danh sách `seen` ở Bước 2) sẽ được bổ sung vào danh sách.
|
- Kết quả merge theo IP: ARP table làm nền, Scapy bổ sung IP còn thiếu.
|
||||||
|
|
||||||
**Bước 4: Trả về kết quả**
|
**Bước 4 — Trả về kết quả**
|
||||||
- Danh sách các thiết bị cuối cùng được sắp xếp từ nhỏ đến lớn dựa trên địa chỉ IP và trả về dưới dạng:
|
|
||||||
|
- Danh sách sort tăng dần theo IP:
|
||||||
`[{"ip": "192.168.1.2", "mac": "aa:bb:cc:dd:ee:ff"}, ...]`
|
`[{"ip": "192.168.1.2", "mac": "aa:bb:cc:dd:ee:ff"}, ...]`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Phân tích chi tiết các hàm hỗ trợ
|
## 3. Phân tích chi tiết các hàm
|
||||||
|
|
||||||
### `_ping_sweep(network)` \& `_ping_one(ip, is_win)`
|
### `_ping_one(ip, is_win)`
|
||||||
- **Nhiệm vụ:** Quét Ping hàng loạt (Ping Sweep).
|
|
||||||
- **Cách thức hoạt động:** Sử dụng `ThreadPoolExecutor` để chạy **tối đa 100 luồng (threads) song song**. Điều này giúp việc gửi Ping hàng loạt diễn ra cực kì nhanh chóng tính bằng giây thay vì phải đợi ping từng IP một.
|
|
||||||
- **Biện pháp an toàn:** Script có cơ chế tự bảo vệ chặn flood mạng: nó sẽ **từ chối chạy Ping Sweep** nếu dải mạng (subnet) lớn hơn 256 địa chỉ IP (tức là chỉ chạy cho dải mạng từ `/24` trở xuống).
|
|
||||||
|
|
||||||
### `_scan_with_arp_table(network)`
|
Ping một IP đơn lẻ với timeout tối ưu theo nền tảng:
|
||||||
- **Nhiệm vụ:** Hàm chạy độc lập để quét tìm thiết bị mà **không cần thông qua đặc quyền Root/Administrator** (fallback method).
|
|
||||||
- **Hỗ trợ Đa nền tảng:**
|
|
||||||
- **Trên Windows (`win32`):** Chuẩn hóa định dạng chuẩn MAC từ gạch ngang sang dấu hai chấm (vd: từ `cc-2d-21...` sang `cc:2d:21...`). Nó dựa vào Regex nhận diện các dòng chuẩn xuất ra có chữ `dynamic` hoặc `static`.
|
|
||||||
- **Trên macOS / Linux:** Dùng Regex đọc định dạng chuẩn riêng biệt ví dụ: `? (192.168.1.1) at aa:bb:cc:dd:ee:ff on en0`. Loại bỏ những địa chỉ lỗi bị đánh dấu là `(incomplete)`.
|
|
||||||
|
|
||||||
### `_scan_with_scapy(network)`
|
| OS | Lệnh | Timeout wait | Timeout process |
|
||||||
- **Nhiệm vụ:** Công cụ quét cực mạnh ở Tầng 2 (Layer 2) của mô hình mạng OSI.
|
| ------- | ------------------ | ----------------- | --------------- |
|
||||||
- **Đặc thù:** Đòi hỏi người dùng phải cấp quyền Sudo/Root trên Linux/macOS hoặc phải cài đặt thư viện phần mềm **Npcap/WinPcap** trên Windows thì mới có thể sử dụng. Gửi gói `srp` bằng `scapy` với timeout là 3 giây để lấy về thông tin phần cứng đích thực.
|
| Windows | `ping -n 1 -w 300` | 300ms | 2s |
|
||||||
|
| macOS | `ping -c 1 -W 500` | 500ms (đơn vị ms) | 2s |
|
||||||
|
| Linux | `ping -c 1 -W 1` | 1s (đơn vị giây) | 2s |
|
||||||
|
|
||||||
|
> macOS và Linux dùng cùng flag `-W` nhưng đơn vị khác nhau — được xử lý tách biệt theo `sys.platform`.
|
||||||
|
|
||||||
|
### `_ping_sweep(network, progress_cb)`
|
||||||
|
|
||||||
|
- Tạo `ThreadPoolExecutor(max_workers=len(hosts))` — **toàn bộ host ping đồng thời**, không batching.
|
||||||
|
- Giới hạn an toàn: chỉ chạy với subnet `/24` trở xuống (`num_addresses <= 256`).
|
||||||
|
- Gọi `progress_cb(done, total)` sau mỗi ping để UI cập nhật thanh tiến độ.
|
||||||
|
|
||||||
|
### `_collect_arp()` (nội bộ trong `scan_network`)
|
||||||
|
|
||||||
|
Đọc và parse output `arp -a`, hỗ trợ đa nền tảng:
|
||||||
|
|
||||||
|
- **Windows:** Regex nhận dạng dạng `cc-2d-21-a5-85-b0 dynamic`, chuẩn hóa MAC sang `cc:2d:21:a5:85:b0`.
|
||||||
|
- **macOS/Linux:** Regex nhận dạng dạng `(192.168.1.1) at aa:bb:cc:dd:ee:ff`, bỏ qua entry `(incomplete)`.
|
||||||
|
|
||||||
|
### `_collect_scapy()` (nội bộ trong `scan_network`)
|
||||||
|
|
||||||
|
- Gửi ARP `Who-has` broadcast (`Ether/ARP` qua `srp`) với **timeout 1s** (giảm từ 2s).
|
||||||
|
- Stderr bị redirect tạm thời khi import scapy để tránh spam log ra console.
|
||||||
|
- Tự động bỏ qua nếu scapy không khả dụng (không có Npcap / không có quyền root).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Tóm tắt Ưu & Nhược điểm của thiết kế này
|
## 4. So sánh hiệu năng (trước và sau tối ưu)
|
||||||
|
|
||||||
- **Ưu điểm:** Khả năng dò tìm diện rộng rất cao vì sự kết hợp song song của hai phương pháp. Nếu thiết bị không có quyền Root/Sudo để chạy `scapy`, nó vẫn có thể tìm được ít nhất ~90% thiết bị trong mạng nhờ tính năng Ping Sweeping mạnh mẽ. Tính tương thích chéo OS (Windows/Mac/Linux) được xử lý rất tốt và gọn gàng qua Regex.
|
| Thay đổi | Trước | Sau |
|
||||||
- **Nhược điểm:** Tốn thêm 1 giây (hàm `time.sleep(1)`) và thêm vài giây timeout tổng cộng, do cần chờ để làm đầy bộ nhớ đệm ARP trước khi quét sâu. Nếu thiết bị đích cố tình tắt phản hồi ARP, thì vẫn có khả năng bị lọt dán mạng.
|
| --------------------------- | --------------------- | ------------------------------------ |
|
||||||
|
| Ping workers | 100 (batching ~3 đợt) | `len(hosts)` (~254, tất cả cùng lúc) |
|
||||||
|
| Ping timeout — Windows | 600ms | 300ms |
|
||||||
|
| Ping timeout — macOS | 1ms (sai đơn vị) | 500ms |
|
||||||
|
| Sleep sau ping sweep | 1.0s | 0.3s |
|
||||||
|
| ARP + Scapy | Tuần tự | **Song song** |
|
||||||
|
| Scapy timeout | 2s | 1s |
|
||||||
|
| **Tổng thời gian scan /24** | ~5–7s | **~1.5–2s** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Ưu & Nhược điểm
|
||||||
|
|
||||||
|
**Ưu điểm:**
|
||||||
|
|
||||||
|
- Tỷ lệ phát hiện thiết bị cao nhờ kết hợp 3 lớp.
|
||||||
|
- Không cần quyền Admin/Root — ping sweep + ARP table vẫn tìm được ~90% thiết bị.
|
||||||
|
- Tương thích đa nền tảng (Windows/macOS/Linux) qua xử lý riêng từng OS.
|
||||||
|
- ARP table và Scapy chạy song song → không cộng dồn thời gian chờ.
|
||||||
|
|
||||||
|
**Nhược điểm:**
|
||||||
|
|
||||||
|
- Vẫn cần `sleep(0.3s)` để OS kịp ghi ARP cache sau ping sweep.
|
||||||
|
- Thiết bị tắt hoàn toàn cả ICMP lẫn ARP sẽ không bị phát hiện.
|
||||||
|
- Spawn ~254 process `ping` đồng thời trên Windows có overhead cao hơn Unix do Windows tạo process chậm hơn.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.1.2
|
1.1.3
|
||||||
|
|||||||
Reference in New Issue
Block a user