refactor code, ẩn thông tin ssh
This commit is contained in:
228
core/ssh_new_flash.py
Normal file
228
core/ssh_new_flash.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
Luồng SSH cho chế độ "Nạp Mới FW" (Factory Reset / Raw device).
|
||||
|
||||
Đặc điểm:
|
||||
- Thiết bị vừa reset cứng → Telnet port 23 mở, SSH chưa có pass.
|
||||
- Bước 0 (tuỳ chọn): đặt password qua Telnet → SSH (set_passwd=True).
|
||||
- Bước 1–4: kết nối SSH, upload SCP, verify, sync + sysupgrade.
|
||||
|
||||
Public API:
|
||||
set_device_password(ip, user, old_password, new_password, status_cb)
|
||||
flash_device_new_ssh(ip, firmware_path, user, password,
|
||||
backup_password, set_passwd, status_cb)
|
||||
"""
|
||||
|
||||
import socket
|
||||
import time
|
||||
import random
|
||||
|
||||
import paramiko
|
||||
|
||||
from core.ssh_utils import (
|
||||
_create_ssh_client,
|
||||
_upload_firmware,
|
||||
_verify_firmware,
|
||||
_sync_and_sysupgrade,
|
||||
)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────
|
||||
# Telnet client thủ công (telnetlib bị xoá Python 3.13+)
|
||||
# ──────────────────────────────────────────────────
|
||||
|
||||
class _SimpleTelnet:
|
||||
"""Minimal Telnet client dùng raw socket."""
|
||||
|
||||
def __init__(self, host, port=23, timeout=10):
|
||||
self._sock = socket.create_connection((host, port), timeout=timeout)
|
||||
|
||||
def read_very_eager(self):
|
||||
try:
|
||||
self._sock.setblocking(False)
|
||||
data = b""
|
||||
while True:
|
||||
try:
|
||||
chunk = self._sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
data += chunk
|
||||
except (BlockingIOError, OSError):
|
||||
break
|
||||
self._sock.setblocking(True)
|
||||
return data
|
||||
except Exception:
|
||||
return b""
|
||||
|
||||
def write(self, data: bytes):
|
||||
self._sock.sendall(data)
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────
|
||||
# Đặt mật khẩu thiết bị (Telnet → SSH fallback)
|
||||
# ──────────────────────────────────────────────────
|
||||
|
||||
def set_device_password(ip, user="root", old_password="",
|
||||
new_password="admin123a", status_cb=None):
|
||||
"""
|
||||
Đặt mật khẩu thiết bị OpenWrt.
|
||||
|
||||
Thứ tự thử:
|
||||
1. Telnet port 23 — thiết bị vừa reset (chưa có pass, SSH chưa mở)
|
||||
2. SSH — thiết bị cũ có SSH nhưng cần đổi pass
|
||||
|
||||
Returns:
|
||||
"DONE" — thành công
|
||||
"FAIL: …" — thất bại sau tất cả các retry
|
||||
"""
|
||||
# Jitter để không hammering khi chạy song song nhiều thiết bị
|
||||
time.sleep(random.uniform(0.1, 1.5))
|
||||
|
||||
# ── 1. Thử Telnet ──────────────────────────────────────────────
|
||||
if status_cb:
|
||||
status_cb("Checking Telnet port for raw device...")
|
||||
try:
|
||||
tn = _SimpleTelnet(ip, timeout=5)
|
||||
if status_cb:
|
||||
status_cb("Telnet connected! Setting password...")
|
||||
|
||||
time.sleep(1)
|
||||
tn.read_very_eager() # Flush banner OpenWrt
|
||||
|
||||
tn.write(b"passwd\n"); time.sleep(1)
|
||||
tn.write(new_password.encode("ascii") + b"\n"); time.sleep(1)
|
||||
tn.write(new_password.encode("ascii") + b"\n"); time.sleep(1)
|
||||
tn.write(b"exit\n"); time.sleep(0.5)
|
||||
tn.close()
|
||||
|
||||
if status_cb:
|
||||
status_cb("Password set via Telnet. Waiting for SSH to start...")
|
||||
time.sleep(3) # Chờ Dropbear (SSH daemon) khởi động
|
||||
return "DONE"
|
||||
except Exception:
|
||||
# Telnet không truy cập được → thử SSH bên dưới
|
||||
pass
|
||||
|
||||
# ── 2. Fallback SSH ─────────────────────────────────────────────
|
||||
if status_cb:
|
||||
status_cb("Connecting SSH for password update...")
|
||||
|
||||
last_err = None
|
||||
for attempt in range(3):
|
||||
try:
|
||||
client = _create_ssh_client(ip, user, old_password, timeout=10)
|
||||
if status_cb:
|
||||
status_cb("Setting password via SSH...")
|
||||
|
||||
shell = client.invoke_shell()
|
||||
time.sleep(1)
|
||||
if shell.recv_ready():
|
||||
shell.recv(65535)
|
||||
|
||||
shell.send("passwd\n"); time.sleep(2)
|
||||
shell.send(f"{new_password}\n"); time.sleep(1)
|
||||
shell.send(f"{new_password}\n"); time.sleep(2)
|
||||
if shell.recv_ready():
|
||||
shell.recv(65535)
|
||||
shell.send("exit\n"); time.sleep(0.5)
|
||||
shell.close()
|
||||
client.close()
|
||||
|
||||
if status_cb:
|
||||
status_cb("Password set ✓")
|
||||
return "DONE"
|
||||
|
||||
except paramiko.AuthenticationException as e:
|
||||
return f"FAIL: Cannot connect SSH (Old pass incorrect?) — {e}"
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(2)
|
||||
|
||||
return f"FAIL: Cannot connect SSH after 3 attempts — {last_err}"
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────
|
||||
# Flash firmware — Nạp Mới (Factory Reset)
|
||||
# ──────────────────────────────────────────────────
|
||||
|
||||
def flash_device_new_ssh(ip, firmware_path, user="root", password="admin123a",
|
||||
backup_password="", set_passwd=False, status_cb=None):
|
||||
"""
|
||||
Flash firmware lên thiết bị OpenWrt vừa reset / chưa có mật khẩu.
|
||||
|
||||
Luồng:
|
||||
0. (Tuỳ chọn) Đặt mật khẩu qua Telnet / SSH
|
||||
1. Kết nối SSH
|
||||
2. Upload firmware qua SCP lên /tmp/
|
||||
3. Verify file tồn tại
|
||||
4. sync + sysupgrade
|
||||
|
||||
Args:
|
||||
ip : địa chỉ IP thiết bị
|
||||
firmware_path : đường dẫn file .bin trên máy tính
|
||||
user : SSH username (mặc định "root")
|
||||
password : mật khẩu SSH (hoặc mật khẩu mới sẽ đặt)
|
||||
backup_password : mật khẩu dự phòng nếu password chính không vào được
|
||||
set_passwd : True = chạy bước đặt mật khẩu trước khi flash
|
||||
status_cb : callback(str) để cập nhật trạng thái lên UI
|
||||
|
||||
Returns:
|
||||
"DONE" — flash thành công
|
||||
"FAIL: …" — thất bại, kèm lý do
|
||||
"""
|
||||
|
||||
# ── STEP 0: Đặt mật khẩu (tuỳ chọn) ──────────────────────────
|
||||
if set_passwd:
|
||||
result = set_device_password(ip, user, "", password, status_cb)
|
||||
|
||||
if result.startswith("FAIL"):
|
||||
# Thử với backup_password nếu có
|
||||
if backup_password:
|
||||
result = set_device_password(ip, user, backup_password, password, status_cb)
|
||||
|
||||
# Thử xem password đã được đặt chưa (idempotent)
|
||||
if result.startswith("FAIL"):
|
||||
result = set_device_password(ip, user, password, password, status_cb)
|
||||
|
||||
if result.startswith("FAIL"):
|
||||
return result
|
||||
else:
|
||||
time.sleep(random.uniform(0.1, 1.5)) # Jitter khi không set_passwd
|
||||
|
||||
# ── STEP 1: Kết nối SSH ─────────────────────────────────────────
|
||||
if status_cb:
|
||||
status_cb("Connecting SSH...")
|
||||
|
||||
try:
|
||||
client = _create_ssh_client(ip, user, password)
|
||||
except paramiko.AuthenticationException:
|
||||
return "FAIL: SSH authentication failed"
|
||||
except paramiko.SSHException as e:
|
||||
return f"FAIL: SSH error — {e}"
|
||||
except Exception as e:
|
||||
return f"FAIL: Cannot connect — {e}"
|
||||
|
||||
# ── STEP 2–4: Upload → Verify → sysupgrade ─────────────────────
|
||||
try:
|
||||
remote_path = _upload_firmware(client, firmware_path, status_cb)
|
||||
_verify_firmware(client, remote_path, status_cb)
|
||||
return _sync_and_sysupgrade(client, remote_path, status_cb)
|
||||
|
||||
except RuntimeError as e:
|
||||
return f"FAIL: {e}"
|
||||
except Exception as e:
|
||||
return f"FAIL: {e}"
|
||||
finally:
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user