Load FW bằng SSH, Thêm tính năng update FW (SSH)

This commit is contained in:
2026-03-08 14:29:35 +07:00
parent 12c65c1948
commit ada3440ebc
8 changed files with 667 additions and 38 deletions

155
docs/FLASH_DOC.md Normal file
View File

@@ -0,0 +1,155 @@
# Tài liệu Flash Firmware — IoT Firmware Loader
## Tổng quan
Ứng dụng tự động hóa quá trình nạp firmware cho thiết bị **OpenWrt Barrier Breaker 14.07** thông qua giao diện web **LuCI**. Thay vì thao tác thủ công trên trình duyệt, ứng dụng thực hiện 3 bước HTTP tự động cho mỗi thiết bị.
---
## Luồng hoạt động
```mermaid
flowchart TD
A[Chọn Firmware .bin] --> B[Scan LAN]
B --> C[Hiển thị danh sách thiết bị]
C --> D{Chọn thiết bị ☑}
D --> E[Nhấn Flash Selected Devices]
E --> F[FlashThread chạy background]
F --> G["ThreadPoolExecutor\n(max_workers = N)"]
G --> H1[Device 1 → flash_device]
G --> H2[Device 2 → flash_device]
G --> H3[Device N → flash_device]
H1 & H2 & H3 --> I[All Done → Thông báo]
```
### Chi tiết `flash_device()` cho mỗi thiết bị
```mermaid
flowchart TD
S1["STEP 1: Login\nGET /cgi-bin/luci\nPOST username=root, password=empty"] --> C1{Thành công?}
C1 -->|Có cookie sysauth + stok| S2
C1 -->|403 Denied| F1["FAIL: Login denied (403)"]
C1 -->|Không có session| F2["FAIL: Login failed — no session"]
S2["STEP 2: Upload Firmware\nPOST /flashops\nField: image=firmware.bin\nkeep=KHÔNG gửi (bỏ tích)"] --> C2{Response?}
C2 -->|Trang Verify + Proceed| S3
C2 -->|HTTP ≠ 200| F3["FAIL: Upload HTTP xxx"]
C2 -->|invalid image| F4["FAIL: Invalid firmware image"]
C2 -->|unsupported| F5["FAIL: Firmware not compatible"]
C2 -->|Vẫn hiện form upload| F6["FAIL: Upload ignored by server"]
S3["STEP 3: Proceed\nPOST step=2, keep=empty\nXác nhận flash"] --> C3{Response?}
C3 -->|200 Flashing...| R["DONE ✅\nThiết bị đang reboot"]
C3 -->|Connection dropped| R
C3 -->|Read timeout| R
```
---
## Bảng Status
### Status trên cột "Status" trong bảng thiết bị
| Icon | Status | Điều kiện hiển thị |
| ---- | ---------------------------------------- | ------------------------------------------------------------- |
| — | `READY` | Sau khi scan, thiết bị chưa được flash |
| ⏳ | `Logging in...` | Đang POST login vào LuCI |
| ⏳ | `Uploading firmware...` | Đang upload file .bin (~30MB) lên thiết bị |
| ⏳ | `Confirming (Proceed)...` | Đã upload xong, đang gửi lệnh xác nhận flash |
| ⏳ | `Rebooting...` | Thiết bị đang reboot sau khi flash |
| ✅ | `DONE` | Flash thành công, thiết bị đang khởi động lại |
| ✅ | `DONE (rebooting)` | Flash thành công nhưng timeout khi chờ response (bình thường) |
| ❌ | `FAIL: Cannot connect` | Không kết nối được tới thiết bị (sai IP, khác mạng) |
| ❌ | `FAIL: Login denied (403)` | Thiết bị từ chối đăng nhập (sai mật khẩu) |
| ❌ | `FAIL: Login failed — no session` | Login không trả về cookie hoặc token |
| ❌ | `FAIL: Upload HTTP xxx` | Server trả mã lỗi HTTP khi upload |
| ❌ | `FAIL: Invalid firmware image` | File firmware không hợp lệ |
| ❌ | `FAIL: Firmware not compatible` | Firmware không tương thích với thiết bị |
| ❌ | `FAIL: Upload ignored by server` | Server nhận file nhưng không xử lý (sai form field) |
| ❌ | `FAIL: Unexpected response after upload` | Không nhận được trang xác nhận Verify |
---
## Chi tiết kỹ thuật HTTP
### Step 1: Login
```
GET http://{IP}/cgi-bin/luci → Lấy trang login, phát hiện field name
POST http://{IP}/cgi-bin/luci → Gửi username=root&password=
```
| Kết quả | Điều kiện |
| -------------- | ------------------------------------------------------------------ |
| **Thành công** | Response chứa `sysauth` cookie VÀ/HOẶC `stok` token trong URL/body |
| **Thất bại** | HTTP 403, hoặc không có cookie/token |
**Field names tự động phát hiện:**
- OpenWrt Barrier Breaker 14.07: `username` / `password`
- OpenWrt mới hơn: `luci_username` / `luci_password`
### Step 2: Upload Firmware
```
POST http://{IP}/cgi-bin/luci/;stok={TOKEN}/admin/system/flashops
Content-Type: multipart/form-data
Fields:
image = [firmware.bin] (file upload)
keep = (KHÔNG gửi) (bỏ tích Keep Settings)
```
| Kết quả | Điều kiện |
| -------------- | ----------------------------------------------------------------------- |
| **Thành công** | Response chứa "Flash Firmware - Verify" + "Proceed" + checksum |
| **Thất bại** | Response chứa "invalid image", "unsupported", hoặc vẫn hiện form upload |
### Step 3: Proceed (Xác nhận)
```
POST http://{IP}/cgi-bin/luci/;stok={TOKEN}/admin/system/flashops
Fields:
step = 2
keep = (empty string)
```
| Kết quả | Điều kiện |
| -------------- | ------------------------------------------------------------------------------- |
| **Thành công** | Response "The system is flashing now" HOẶC connection bị ngắt (thiết bị reboot) |
| **Thất bại** | Hiếm khi xảy ra — nếu đã qua Step 2 thì Step 3 gần như luôn thành công |
---
## Xử lý song song
```
FlashThread (QThread - background)
└── ThreadPoolExecutor (max_workers = N)
├── Thread 1 → flash_device(IP_1)
├── Thread 2 → flash_device(IP_2)
├── ...
└── Thread N → flash_device(IP_N)
```
| Config | Giá trị | Ý nghĩa |
| ------------------------- | --------- | ------------------------------ |
| Concurrent devices = `10` | Mặc định | Flash 10 thiết bị song song |
| Concurrent devices = `0` | Unlimited | Flash tất cả thiết bị cùng lúc |
| Concurrent devices = `1` | Tuần tự | Flash từng thiết bị một |
**Mỗi thiết bị có session HTTP riêng** → không bị lẫn cookie/token giữa các thiết bị.
---
## Files liên quan
| File | Chức năng |
| --------------- | --------------------------------------------- |
| `flasher.py` | Logic flash 3 bước (login → upload → proceed) |
| `main.py` | UI PyQt6, FlashThread, quản lý song song |
| `debug_full.py` | Script debug — chạy 3 bước với log chi tiết |
| `scanner.py` | Scan mạng LAN tìm thiết bị |
""", "Complexity": 3, "Description": "Created flash documentation file with workflow diagrams, status conditions, HTTP details, and parallel processing explanation.", "EmptyFile": false, "IsArtifact": false, "Overwrite": false, "TargetFile": "/Users/nguyennhatminh/Documents/file code/Smatec/iot_fw_loader/FLASH_DOC.md

58
docs/load_fw_ssh_docs.md Normal file
View File

@@ -0,0 +1,58 @@
# Tài liệu Kỹ thuật: Nạp Mới Firmware (LoadFW bằng SSH)
Tính năng **Nạp Mới FW** trong module `ssh_flasher.py` được thiết kế để cấp cứu và cài đặt lại Firmware cho các thiết bị OpenWrt trắng (mới xuất xưởng) hoặc vừa trải qua Hardware Factory Reset.
---
## 🚀 Tính năng Báo Cáo Tiến Độ Lên UI
Tính năng này được thiết kế theo chuẩn hướng sự kiện (Callback). Module liên tục báo cáo dữ liệu thời gian thực lên cột Status của bảng thiết bị:
1. `Checking Telnet port for raw device...` (Nếu thiết bị mới bị khóa SSH)
2. `Setting password via SSH...` / `Password set via Telnet...`
3. `Uploading firmware via SCP...` (Đẩy file vào RAM Disk)
4. `Verifying firmware...`
5. `Syncing filesystem...`
6. `Flashing firmware (sysupgrade)...`
7. `Rebooting...`
---
## 🛠 Flow Hoạt Động Mạch Trắng (Workflows)
Tính năng Flash qua luồng này hoạt động theo một quy trình 5 bước cực kỳ nghiêm ngặt:
### Bước 0: Thiết lập Mật khẩu Đa Kênh qua Cổng 23 (Fallback Mechanism)
- **Vấn đề của OpenWrt:** Router OpenWrt vừa xuất xưởng (chưa có pass) sẽ **CẤM đăng nhập SSH** cho tài khoản `root`. Lúc này, chỉ cổng cục bộ Telnet (Port 23) là mở.
- **Cách Tool vượt qua:**
1. Tool tiên phong chọc thẳng bằng **Giao thức Telnet (Port 23)**.
2. Dùng chuỗi lệnh `passwd` để thiết lập mật khẩu thành `admin123a` 2 lần. Đợi 3s để OpenWrt kịp đánh thức Server Dropbear (Mở cổng 22 SSH).
3. Nếu thiết bị đã có Pass, Tool sẽ mượt mà Rơi xuống nhánh SSH (`old_password` -> `admin123a`).
### Bước 1: Khởi tạo Kết nối SSH
- Sử dụng cờ `AutoAddPolicy` của Python `paramiko` ép kết nối âm thầm chấp nhận mọi Host Certificate mà không bật popup cảnh báo.
### Bước 2: Truyền File bảo mật (SCP vào RAM)
- Thực hiện SCP đẩy BIN firmware (`tim_uImage` hoặc `.bin`) vào phân vùng ảo: `/tmp/<tên_file>.bin`. Đây là khu vực RAM Disk (Ghi siêu tốc, không làm mòn chip nhớ).
### Bước 3 & 4: Xác thực và Đồng bộ (Verify & Sync)
- Gửi lệnh `test -f /tmp/...` để xác nhận file.
- Gọi lệnh `sync` ép File System đẩy transaction xuống vùng nhớ cứng.
### Bước 5: Sysupgrade (Cài Đặt Lõm)
- Chạy lệnh ngầm: `sysupgrade -F -v -n /tmp/<tên_file>`.
- Cờ `-n`: Clean Flash sạch trơn, không giữ Configuration.
- Cờ `-F` (Force): Bỏ qua bộ kiểm tra Image Metadata cho những File thiếu hụt Metadata (uImage).
- **Đứt Kết Nối Đột Ngột** khi script đang đợi (sau khoảng 4s trở đi) sẽ mặc định được coi là tín hiệu Thành Công (Server SSH sập nguồn để Reboot).
---
## 🔎 Trình Gỡ Lỗi (Debugging)
Chạy file `debug_ssh.py` độc lập trên Terminal ở cấp độ TCP Verbose để soi quá trình nạp mới:
`./venv/bin/python debug_ssh.py <IP> <DUONG_DAN_FIRMWARE> <PASS>`

59
docs/scanner_docs.md Normal file
View File

@@ -0,0 +1,59 @@
# Tài liệu Kỹ thuật: Cơ chế Quét IP trên Mạng (Network Scanner)
## 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****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:
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`.**
---
## 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: "Đánh thức" các thiết bị (Ping Sweep)**
- 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ụ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).
- 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)**
- 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)**
- 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`).
- 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.
- 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.
**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:
`[{"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ợ
### `_ping_sweep(network)` \& `_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)`
- **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)`
- **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.
---
## 4. Tóm tắt Ưu & Nhược điểm của thiết kế này
- **Ư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.
- **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.

39
docs/update_fw_docs.md Normal file
View File

@@ -0,0 +1,39 @@
# Tài liệu Kỹ thuật: Cập Nhật Firmware (Update FW)
Quy trình `Update FW` là phương thức tối ưu hóa để nâng cấp Firmware mới vào các thiết bị OpenWrt Live (đã và đang chạy sẵn Firmware của hệ sinh thái MiraV3).
---
## 🔁 Cơ Chế Chuyên Cấp: Update Firmware (Live Nodes)
Điểm khác biệt lớn nhất của luồng Update FW so với Nạp Mới:
Gỡ bỏ hoàn toàn sự cồng kềnh, công cụ MiraV3 hỗ trợ tách bạch quy trình tải Firmware mới thông qua UI `Chế độ Flash > Update FW`.
Module `ssh_flasher.py` tự động kích hoạt cờ tuỳ chọn ngầm `set_passwd = False`:
- **Bỏ Bầu Trời Khởi Động (Skip Telnet):** Hệ thống sẽ ngay lập tức thiết lập kết nối đích thị vào luồng SSH trên Port 22 bằng Security Credentials mặc định `root:admin123a`. Tool bỏ qua hoàn toàn quy trình truy vấn cổng Telnet (23) chậm chạp cũng như các lệnh Set Password thừa thãi.
- **Ràng Buộc Chặt Chẽ:** Được kiểm soát từ tầng UI để chống Flash nhầm. Chỉ duy nhất thiết bị có IP cấu hình cứng `192.168.11.102` mới được âm thầm qua cửa, các IP lạ sẽ bị hệ thống chặn lại và bật MessageBox cảnh báo hỏi kỹ thuật viên xác nhận.
Với cơ chế Skip này, Tốc độ tổng thể tăng tốc cực đáng kể, phục vụ trực tiếp cho quá trình Mass-Update hàng loạt các Nodes đang hoạt động trên hệ thống.
---
## 🚀 Tính năng Xử lý Khủng hoảng (Sysupgrade)
Các thiết bị đang sống khi cập nhật File thường có rủi ro từ chối tệp tin cập nhật do thiếu Metadata:
### Bước xả File: Sysupgrade (Hủy diệt và Tái sinh)
- Chạy lệnh xả Kernel ngầm: `sysupgrade -F -v -n /tmp/<tên_file>`.
- Cờ `-n`: Clean Flash sạch trơn, không giữ Configuration rác của bản cũ.
- Cờ `-F` (Force): Ép hệ điều hành OpenWrt bỏ qua bước kiểm tra Image Metadata tại Local. Tuỳ chọn này sống còn đối với các tệp tin Firmware trần trụi (như build Raw `tim_uImage`) để tránh việc sysupgrade từ chối file, văng lỗi "Image check failed" và ngắt ngang tiến trình.
- Điểm đắt giá của logic: Python (`paramiko`) sẽ phân tích kết quả trả về liên tục trong những giây đầu tiên vòng lặp. Nếu phát sinh lỗi thực thi sai định dạng file ở tầng OS Router thì sẽ báo văng Error Catching ngay lập tức ra bảng Table.
- Ngược lại, việc **Đứt Kết Nối Đột Ngột** khi script đang đợi (sau khoảng 4s trở đi) sẽ mặc định được coi là Thành Công. Vì khi Sysupgrade dội Kernel thành công, hệ thống mạng của Router sẽ tự sập để Reboot.
---
## 🔎 Trình Gỡ Lỗi Nhanh (Debugging Update flow)
Có thể Trigger giả lập tiến trình UI Update FW ngay trên cửa sổ Console Terminal để kiểm tra Handshake TCP lỗi ở đâu (nếu có).
_Lệnh (Lưu ý cờ `--update` để Tắt Telnet Fallback):_
`./venv/bin/python debug_ssh.py <IP_192.168.11.102> <DUONG_DAN_FIRMWARE> <PASS> --update`