fix bug scan ip

This commit is contained in:
2026-03-10 17:54:56 +07:00
parent 466dadf1c9
commit f5ee209726
3 changed files with 79 additions and 154 deletions

View File

@@ -2,37 +2,40 @@
## 1. Tổng quan
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****MAC Address** của từng thiết bị.
Thành phần quét IP trong `scanner.py` dò tìm và liệt kê tất cả thiết bị **đang thực sự online** trên một dải mạng (ví dụ: `192.168.1.0/24`), trả về danh sách chứa **IP** của từng thiết bị.
Để đả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:
Scanner chỉ sử dụng **ICMP Ping** — thiết bị phải phản hồi ping thì mới được liệt kê. Cơ chế này đảm bảo kết quả chính xác, tránh hiện tượng hiển thị thiết bị đã ngắt kết nối do ARP cache cũ trên router/OS.
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.
> **Lý do loại bỏ ARP và Scapy:** Bảng ARP của hệ điều hành và router giữ cache entry trong nhiều phút, khiến thiết bị đã rút vẫn xuất hiện trong kết quả scan. Ping trực tiếp là phương pháp duy nhất xác nhận thiết bị thực sự đang hoạt động tại thời điểm scan.
---
## 2. Luồng hoạt động chính (Hàm `scan_network`)
```
scan_network(network)
├── stage_cb("ping") ← Thông báo UI bắt đầu quét
├── _ping_sweep(network) ← Ping đồng thời toàn bộ host
│ │
│ ├── _ping_one(ip) × N ← Mỗi host 1 thread
│ │
│ └── return [alive IPs]
└── return [{"ip": ..., "mac": "N/A"}, ...] ← Sorted by IP
```
**Bước 1 — 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.
- Mỗi thiết bị phản hồi sẽ khiến hệ điều hành **tự ghi MAC vào ARP Cache**.
- Sau khi sweep xong, chờ `0.3s` để OS kịp finalize ARP cache (giảm từ 1s trước đây).
- Chỉ các IP có `returncode == 0` (phản hồi thành công) mới được đưa vào kết quả.
**Bước 2 + 3 — ARP Table & Scapy (song song)**
- Hai hàm `_collect_arp()``_collect_scapy()` được submit vào `ThreadPoolExecutor(max_workers=2)` và chạy đồng thời:
- `_collect_arp()`: đọc `arp -a`, parse regex lấy IP + MAC.
- `_collect_scapy()`: gửi ARP broadcast, nhận phản hồi trực tiếp từ thiết bị.
- 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 2 — Trả về kết quả**
- 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": "N/A"}, ...]`
- Trường `mac` luôn là `"N/A"` — giữ nguyên cấu trúc dict để tương thích với UI và các module khác.
---
@@ -40,48 +43,45 @@ Bước 2 và 3 chạy **song song** để giảm tổng thời gian scan.
### `_ping_one(ip, is_win)`
Ping một IP đơn lẻ với timeout tối ưu theo nền tảng:
Ping một IP đơn lẻ, trả về `True` nếu host phản hồi, `False` nếu không.
| OS | Lệnh | Timeout wait | Timeout process |
| ------- | ------------------ | ----------------- | --------------- |
| 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 |
Timeout tối ưu theo nền tảng:
> 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`.
| OS | Lệnh | Timeout wait | Timeout process |
| ------- | ------------------ | ------------ | --------------- |
| Windows | `ping -n 1 -w 300` | 300ms | 2s |
| macOS | `ping -c 1 -W 300` | 300ms | 2s |
| Linux | `ping -c 1 -W 1` | 1s | 2s |
> macOS và Linux dùng cùng flag `-W` nhưng đơn vị khác nhau (macOS: ms, Linux: giây) — được xử lý tách biệt theo `sys.platform`.
Windows sử dụng `CREATE_NO_WINDOW` flag để tránh mở cửa sổ console cho mỗi subprocess.
### `_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`).
- Trả về danh sách IP (string) đã phản hồi thành công.
- 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`)
### `scan_network(network, progress_cb, stage_cb)`
Đọ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).
- Entry point chính.
- `stage_cb("ping")` thông báo UI giai đoạn hiện tại.
- Trả về `list[dict]` với format `{"ip": str, "mac": "N/A"}`, sorted theo IP tăng dần.
---
## 4. So sánh hiệu năng (trước và sau tối ưu)
## 4. Hiệu năng
| Thay đổi | Trước | Sau |
| --------------------------- | --------------------- | ------------------------------------ |
| 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** | ~57s | **~1.52s** |
| Thông số | Giá trị |
| --------------------------- | ------------------------------------ |
| Ping workers | `len(hosts)` (~254, tất cả cùng lúc) |
| Ping timeout — Windows | 300ms |
| Ping timeout — macOS | 300ms |
| Ping timeout — Linux | 1s |
| Process timeout | 2s |
| **Tổng thời gian scan /24** | **~12s** |
---
@@ -89,13 +89,14 @@ Ping một IP đơn lẻ với timeout tối ưu theo nền tảng:
**Ư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ờ.
- **Kết quả chính xác** — chỉ thiết bị thực sự online mới xuất hiện, không bị ảnh hưởng bởi ARP cache cũ.
- Không cần quyền Admin/Root.
- Không phụ thuộc thư viện bên ngoài (không cần Scapy, Npcap).
- Tương thích đa nền tảng (Windows/macOS/Linux).
- Nhanh (~12s cho /24) nhờ ping toàn bộ host đồng thời.
**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.
- Không có thông tin MAC address.
- Thiết bị chặn ICMP (tắt ping) sẽ không bị phát hiện.
- Spawn ~254 process `ping` đồng thời trên Windows có overhead cao hơn Unix.