Files
Mira_Firmware_Loader/docs/load_fw_ssh_docs.md

9.3 KiB

Tài liệu Kỹ thuật: Flash Firmware qua SSH

Tài liệu này gộp toàn bộ logic SSH cho cả hai quy trình: Nạp Mới FW (Factory Reset) và Update FW (thiết bị đang chạy). Luồng SSH được tách thành nhiều file với trách nhiệm rõ ràng thay vì gói gọn trong một module duy nhất.


1. Kiến Trúc Tổng Quan — Vai Trò Từng File

core/
├── ssh_utils.py          ← Transport layer dùng chung (không có business logic)
├── ssh_new_flash.py      ← Luồng SSH Nạp Mới FW (Telnet → SSH → sysupgrade)
├── ssh_update_flash.py   ← Luồng SSH Update FW   (SSH trực tiếp → sysupgrade)
├── flash_new_worker.py   ← QThread điều phối Nạp Mới (API LuCI hoặc SSH)
└── flash_update_worker.py← QThread điều phối Update FW (SSH only)

core/ssh_utils.py — Transport Layer Dùng Chung

Chứa các helper function cấp thấp, không mang logic nghiệp vụ, được import bởi cả 2 luồng SSH:

Hàm Mô tả
_create_ssh_client(ip, user, password, timeout) Tạo SSH client với AutoAddPolicy, retry 3 lần. Nếu lỗi AuthenticationException thì raise ngay (không retry vô nghĩa).
_upload_firmware(client, firmware_path, status_cb) Upload file .bin qua SCP lên /tmp/<filename>. Retry 3 lần, timeout SCP là 350 giây. Trả về remote_path.
_verify_firmware(client, remote_path, status_cb) Chạy test -f && ls -lh để xác nhận file tồn tại trên device sau khi upload.
_sync_and_sysupgrade(client, remote_path, status_cb) Chạy sync rồi sysupgrade -F -v -n. Đứt kết nối = thành công. Trả về "DONE" hoặc "FAIL: ...".

core/ssh_new_flash.py — Luồng Nạp Mới FW

Xử lý thiết bị vừa Factory Reset hoặc chưa có mật khẩu. Bao gồm:

  • _SimpleTelnet: Telnet client thủ công bằng raw socket — thay thế telnetlib bị xoá khỏi Python 3.13+.
  • set_device_password(): Đặt mật khẩu thiết bị, thử Telnet port 23 trước, fallback sang SSH nếu Telnet đóng.
  • flash_device_new_ssh(): Hàm flash chính — gọi set_device_password (nếu set_passwd=True) rồi kết nối SSH và gọi pipeline _upload → _verify → _sync_and_sysupgrade từ ssh_utils.

core/ssh_update_flash.py — Luồng Update FW

Xử lý thiết bị đang chạy MiraV3, SSH đã mở sẵn với pass đã biết. Không có Telnet, không có set_passwd:

  • flash_device_update_ssh(): Kết nối SSH (thử password, fallback backup_password nếu auth fail), rồi gọi thẳng pipeline _upload → _verify → _sync_and_sysupgrade từ ssh_utils.

core/flash_new_worker.py — QThread Nạp Mới

NewFlashThread — điều phối flash song song cho chế độ Nạp Mới:

  • Nếu method="ssh" → gọi flash_device_new_ssh() từ ssh_new_flash.
  • Nếu method="api" → gọi flash_device() từ flasher (LuCI HTTP).
  • Nhận đủ credentials từ UI: ssh_user, ssh_password, ssh_backup_password, set_passwd.

core/flash_update_worker.py — QThread Update FW

UpdateFlashThread — điều phối flash song song cho chế độ Update FW:

  • Luôn dùng SSH, credentials hardcode an toàn trong module (root / admin123a, backup admin).
  • Không nhận credentials từ UI — tránh nhập sai gây flash nhầm.

2. Sơ Đồ Luồng — Nạp Mới FW (SSH)

graph TD
    A[NewFlashThread.run] --> B{method = ssh?}
    B -- api --> API[flash_device - LuCI HTTP]
    B -- ssh --> C[flash_device_new_ssh]

    C -->|set_passwd=True| D[set_device_password]
    C -->|set_passwd=False| Jitter[Jitter 0.1s - 1.5s]

    D --> D1[Thử Telnet port 23]
    D1 -- Thành công --> D2[passwd > new_password via Telnet]
    D2 --> D3[Chờ 3s để Dropbear khởi động]
    D1 -- Lỗi / Timeout --> D4[Fallback SSH với old_password rỗng]
    D4 -- Auth Fail --> D5[Thử backup_password]
    D5 -- Auth Fail --> D6[Thử chính new_password - idempotent]
    D6 -- Fail --> Z[DỪNG - Báo FAIL]
    D3 --> F
    D4 -- OK --> F
    D5 -- OK --> F
    D6 -- OK --> F

    Jitter --> F[_create_ssh_client - retry 3 lần]
    F --> G[_upload_firmware via SCP vào /tmp/]
    G -->|retry 3 lần| H[_verify_firmware - test -f]
    H --> I[_sync_and_sysupgrade]
    I --> J[sync rồi sysupgrade -F -v -n]
    J --> K{Kết nối đứt?}
    K -- Có --> L[DONE - Device đang Reboot]
    K -- Không trong 4s --> M[FAIL - Lấy log /tmp/sysup.log]

3. Sơ Đồ Luồng — Update FW (SSH)

graph TD
    A[UpdateFlashThread.run] --> B[flash_device_update_ssh]
    B --> C[_create_ssh_client với password chính]
    C -- Auth OK --> F
    C -- AuthenticationException --> D[Thử backup_password]
    D -- Auth OK --> F
    D -- Fail --> Z[DỪNG - Báo FAIL]

    F[_upload_firmware via SCP vào /tmp/] -->|retry 3 lần| G[_verify_firmware - test -f]
    G --> H[_sync_and_sysupgrade]
    H --> I[sync rồi sysupgrade -F -v -n]
    I --> J{Kết nối đứt?}
    J -- Có --> K[DONE - Device đang Reboot]
    J -- Không trong 4s --> L[FAIL - Lấy log /tmp/sysup.log]

4. Phân Tích Logic Cốt Lõi

4.1 Cơ Chế Dự Phòng Mật Khẩu Đa Tầng (Nạp Mới)

Thuật toán set_device_password có 4 lớp dự phòng để chiếm được quyền Root trên thiết bị dù ở trạng thái nào:

  1. Telnet (Port 23): OpenWrt ngay sau Factory Reset mở Telnet không cần mật khẩu nhưng chặn SSH. Script luôn thử cổng này đầu tiên. Mọi exception (Refused, Timeout, Broken Pipe) đều kích hoạt fallback.
  2. SSH mật khẩu rỗng "": Nếu Telnet đóng, thử SSH với pass rỗng — trường hợp thiết bị đang ở trạng thái semi-configured.
  3. SSH Backup Password: Nếu pass rỗng bị AuthenticationException, thiết bị đang kẹt một pass cũ — thử backup_password.
  4. SSH Target Password (Idempotent): Thử lại với chính new_password để chặn trường hợp device đã đổi pass thành công từ lần flash trước nhưng chưa được flash firmware.

4.2 Cơ Chế Dự Phòng Mật Khẩu (Update FW)

Đơn giản hơn: thử password chính → nếu AuthenticationException → thử backup_password. Không có Telnet, không có set_passwd. Nếu cả hai đều fail thì trả về FAIL ngay.

4.3 Xử Lý Đa Luồng Chống Nghẽn

paramiko dễ báo lỗi [Errno 64] Host is down hoặc [Errno 32] Broken Pipe khi hàng chục thread cùng hit network stack cùng một lúc. Hệ thống chống nghẽn bằng 2 cơ chế:

  • Jitter ngẫu nhiên: time.sleep(random.uniform(0.1, 1.5)) phân tán chùm request theo thời gian, tránh tự gây DDoS nội bộ.
  • Retry Hooks: _create_ssh_client retry 3 lần (nghỉ 1s giữa mỗi lần). _upload_firmware retry 3 lần (nghỉ 1.5s). Triệt 99% lỗi TCP transient.

4.4 RAM Disk SCP

Firmware được upload thẳng vào /tmp/<tên_file>.bin — phân vùng RAM ảo của OpenWrt. Lợi ích:

  • Tốc độ ghi nhanh nhất (RAM vs Flash).
  • Không gây hao mòn FlashNOR của router.
  • File biến mất sau reboot, không để lại rác.

4.5 Thủ Thuật sysupgrade -F -v -n

Cờ Tác dụng
-F (Force) Bỏ qua kiểm tra Image Metadata — bắt buộc với file build Raw / tim_uImage để tránh lỗi "Image metadata not present".
-v (Verbose) Log chi tiết vào /tmp/sysup.log để debug khi cần.
-n (No-keep) Clean Flash — không giữ cấu hình cũ, tránh xung đột config giữa các phiên bản.

Đứt kết nối = Thành công: sysupgrade phá kernel hiện tại rồi reboot nạp firmware mới → SSH session bị đứt là kết quả tất yếu và mong muốn. Script bẫy exception này và trả về "DONE". Ngược lại, nếu sysupgrade fail sớm (< 4 giây, còn giữ kết nối), script đọc /tmp/sysup.log trả về error code đầy đủ.


5. Trình Gỡ Lỗi Nhanh

Cả hai luồng đều gọi status_cb(msg) tại mỗi bước — toàn bộ tiến độ như "Connecting SSH", "Uploading firmware", "Syncing filesystem" hiển thị trực tiếp tại cột Status trên giao diện chính.

Để debug chi tiết hơn, chạy app qua terminal:

./run.sh

Mọi exception đều bị bắt và trả về chuỗi "FAIL: <lý do>" hiển thị lên UI — không có silent failure.