Tự động khởi động lại modem cáp quang + Wi-Fi tôi được “tặng” khi đăng ký gói cáp quang FPT.
Thiết bị Modem + Wi-Fi có tên Internet Hub AC1000C (Model: GPON ONT G-97RG6M) hỗ trợ 4 đầu ra LAN, Wi-Fi băng tần kép 2.4GHz (802.11b/g/n) và 5GHz (802.11 a/n/ac) – do Huawei sản xuất nhưng không đề tên.
Dù thiết bị hoạt động khá ổn định nhưng tôi vẫn muốn khởi động lại thiết bị này mỗi tuần cho tươi mới. Vì modem nào cũng vậy, chạy lâu lâu rồi kiểu gì cũng sẽ có vấn đề.
Dù modem cũng hỗ trợ tính năng Reboot Timer nhưng có vấn đề gì đó khiến modem không được ổn định sau khi tôi bật tính năng này.
Cách làm dưới đây về nguyên tắc không hề phụ thuộc bạn đang dùng modem nào bởi chúng chỉ khác nhau về giao diện web và/hoặc cú pháp dòng lệnh (nếu có). Hãy thay đổi tùy theo thiết bị bạn đang có.
Tôi sẽ dùng một máy tính Raspberry Pi của tôi để định kỳ reboot modem từ xa.
Khởi động lại bằng SSH
Modem này hỗ trợ SSH. Để bật SSH, ta đăng nhập vào giao diện web tại địa chỉ http://192.168.1.1 như sau:
Vào mục Security Setup › SSH, bật (enable) Local SSH lên, đừng có bật Remote SSH vì khá mạo hiểm. Cài đặt Username và Password đăng nhập (nên khác với user và password đăng nhập giao diện web).
Bạn có thể thử nghịch ngợm với SSH terminal này bằng lệnh ssh
. Ví dụ ở đây user là icreativ
và IP modem là 192.168.1.1
.
ssh icreativ@192.168.1.1
Lưu ý: Nếu bạn nhận được thông báo sau:
Unable to negotiate with 192.168.1.1 port 22: no matching key exchange method found. Their offer: diffie-hellman-group1-sha1,diffie-hellman-group14-sha1
Nguyên nhân là do SSH client phiên bản mới mặc định không hỗ trợ thuật toán mã hóa Diffie-Hellman là một thuật toán có tính bảo mật không còn cao so với công nghệ thời nay. Hãy thêm đoạn sau vào .ssh/config
rồi thử lại.
Host 192.168.1.1 KexAlgorithms +diffie-hellman-group1-sha1
SSH của modem không hỗ trợ SSH key và phải thao tác vài bước mới đến được shell để gọi lệnh reboot
. Để tự động hóa, ta có thể dùng lệnh expect
như sau:
reboot_modem.exp
#!/usr/bin/expect # reboot-modem.exp # @author: Nguyen Hong Hai set USERNAME "icreativ" set PASSWORD "password" set ROUTERIP "192.168.1.1" # Your username@host spawn ssh $USERNAME@$ROUTERIP expect "password: " # Your SSH password goes here send -- "$PASSWORD\r" expect "#ONT>" # Get into shell send "system/shell\r" expect "#ONT/system/shell>" # Perform the reboot command send "/sbin/reboot\r" expect eof
Chú ý ký tự \r
(tương ứng phím Enter) sau các chuỗi mỗi lần gọi send
.
Chạy script như sau:
expect ./reboot_modem.exp
Hoặc phân quyền executable cho script và chạy thẳng lệnh này luôn:
chmod +x reboot_modem.exp
./reboot_modem.exp
Khởi động lại bằng headless browser
Nếu không muốn dùng đến SSH thì ta vẫn còn một cách khác là thao tác trên giao diện web. Cụ thể chúng ta sẽ giả lập kết quả sau khi nút Reboot dưới đây bị ấn. Tôi định sử dụng Puppeteer.
Tuy nhiên điều khó khăn khi dùng Puppeteer trong trường hợp này là toàn bộ giao diện bị nhúng trong một FRAME và có cơ chế bảo vệ không cho load riêng trang trong FRAME. Do giới hạn của công cụ, tôi không thể dò đến các nút để thao tác được.
Tuy nhiên sau khi xem qua mã nguồn JS, hóa ra quy trình reboot là thực hiện một POST request đến địa chỉ /GponForm/reboot_XForm
kèm theo một biến XWebPageName
mang giá trị reboot
. Tất nhiên điều này chỉ hiệu lực khi ta đã login thành công.
Vì thế script sau sẽ thực hiện hai bước:
- Đăng nhập giao diện web bằng Puppeteer.
- Thực hiện POST request với
XMLHttpRequest
.
Yêu cầu trước hết bạn cần cài đặt Node.js, sau đó cài đặt Puppeteer như sau:
mkdir reboot_modem cd reboot_modem npm i puppeteer-core --save-dev
Mã nguồn chương trình khởi động modem bằng JavaScript. Lưu ý thay USERNAME
và PASSWORD
bằng tài khoản đăng nhập modem qua web.
reboot_modem/index.js
/** * reboot_modem/index.js * @author Nguyen Hong Hai */ const LOGINURL = "http://192.168.1.1/login.html"; const USERNAME = "admin"; const PASSWORD = "password"; const puppeteer = require("puppeteer-core"); (async () => { const browser = await puppeteer.launch({ // Path to the Chromium browser binary on Raspberry Pi executablePath: "/usr/bin/chromium-browser" }); try { const page = await browser.newPage(); await page.goto(LOGINURL, {waitUntil: "networkidle2"}); await page.waitForSelector("#username"); await page.type("#username", USERNAME); await page.type("#password", PASSWORD); await page.click("#btn_login"); await page.waitForSelector("#contentfrm"); await page.evaluate(() => { return new Promise((resolve, reject) => { var oXHR = new XMLHttpRequest(); oXHR.open("POST", "/GponForm/reboot_XForm", true); oXHR.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); oXHR.onreadystatechange = function() { if (oXHR.readyState === 4) { if (oXHR.status === 200) { resolve(); } else { reject(oXHR.statusText); } } } oXHR.send("XWebPageName=reboot"); }); }); } catch (error) { console.error("Error:", error) } finally { await browser.close(); } })();
Chạy script này như sau:
cd reboot_modem node ./index.js
Sau đó việc cuối cùng là tôi đặt (một trong hai) lệnh trên vào crontab
trên thiết bị Raspberry Pi để lên lịch gọi mỗi tuần.