【茶包射手日記】路由器 ssh 環境 curl 閃退問題
| | | 0 | |
這是篇冷門文章,我選了常人不會選的路由器玩法,遇到全球或許只有個位數人口需要面對的 Bug,最後用了在 Vibe Coding 時代才有的方法解決。
想在路由器開發整合應用,我選了一條另類路線,沒用 OpenWRT 跑軟路由,因型號問題沒法改刷魔改韌體,不能用 Docker 領域展開,最後是與官方版韌體鬥智門勇,找可用工具拼湊解決方案。
前幾天將路由器溫度、CPU 及流量數據整合到 Grafana 儀表板時,我發現 BE58 雖然內建 curl 工具,但連外部網站 OK,連 Intranet IP 網站就會閃退,原因不明,吊詭的是沒顯示任何訊息,加了 --verbose 也沒用,怎麼看都像程式有 Bug。原本還想著,在路由器用 cron 設排程定期靠 curl 呼叫 WebAPI 將效能資料上傳 Prometheus Pushgateway,多美妙啊,如意算盤當被摔個粉碎 Orz 後來我的解法是將數據輸出到 syslog,由 ng-syslog 接收 syslog 識別解析再上傳 Prometheus Pushgateway,繞了一大圈,至少問題是解了。
心有不甘,少了 curl 路由器腳本無法直接與外部溝通,當場少掉一條簡潔又高效的整合通道,程式像被毒啞了沒法說話。在舊路由器 AC66U-B1 跑 curl 對照,不論內外部網站都正常,查資料也找到相關討論(會這樣用的人應該很少),加上程式閃退無訊息,加 -v 也沒用,推測 BE58 現行版本內建的 curl 有 Bug 吧。
我先研究了這個議題的背景,AsusWRT 是華碩路由器使用的韌體架構(核心為 Linux),由於路由器的 Flash 儲存空間與 RAM 非常有限,故加入了有「嵌入式 Linux 的瑞士刀」之稱的 BusyBox,以便在 ssh 登入的 Shell 環境提供 ls, cp, mkdir, grep ... 等這些指令。BusyBox 的做法是將數百個指令功能打包在一個僅約數百 KB 到幾 MB 的執行檔,包含大多數標準 Unix 工具的精簡版本,雖然功能不像完整版那麼強大,但足以應付系統管理與腳本執行。系統中看起來像獨立指令的檔案 (例如 /bin/ls),實際上通常只是指向 /bin/busybox 的符號連結。當你執行 ls 時,BusyBox 會根據被呼叫的名稱來決定執行哪項功能,用 ls -l /bin/ls 可以證明。
但 curl 並不屬於 BusyBox,是另外安裝的執行檔,其版本為 7.84.0 (arm-buildroot-linux-gnueabi):

curl 7.84.0 (arm-buildroot-linux-gnueabi) libcurl/7.84.0 OpenSSL/1.1.1t
Release-Date: 2022-06-27
Protocols: file ftp ftps http https mqtt
Features: alt-svc HSTS IPv6 Largefile NTLM SSL threadsafe TLS-SRP UnixSockets
而爬文結果,可能原因是 curl 或其依賴庫(如 OpenSSL、mbedTLS)在編譯時啟用了 -O3 優化,遇到存取地址錯誤會觸發 SIGBUS 或 SIGSEGV,在嵌入式環境的表現便是直接退出且不列印錯誤訊息。另一個可能是 arm-buildroot-linux-gnueabi 通常為 Soft-Foat,若目標機硬體是 Hard-Float 或者連結到 Hard-Float 的動態庫,程式會在進入浮點運算時崩潰。總之,結論是 curl 程式在嵌入環境崩潰閃退是常態,我只用過 PC 跟伺服器等級 Linux 才會大驚小怪。
以我的技能與知識儲備,要修正 curl 如登陸火星,機會渺茫。但在調查過程,我找到一絲希望,BusyBox 沒有 telnet 但有極簡版 nc,原本的 Netcat 是個萬用工具,甚至可以模擬簡單的 HTTP Server,BusyBox 的版本只有開 Socket 傳送跟接收內容的功能,對我這種學過用 TELNET 模擬 HTTP 請求的老人來說,足以成為救命稻草。
BusyBox v1.24.1 (2026-03-03 11:41:07 CST) multi-call binary.
Usage: nc [IPADDR PORT]
Open a pipe to IP:PORT
於是我請 Github Copilot 幫我寫一段 Shell 腳本 (不能用 Bash 語法,AsusWRT 是用 BusyBox 內建的 ash,Almquist Shell) 模擬 curl 的功能(當然,不支援 HTTPS,但能連 HTTP 在我的內網應用已是功德無量),Shell 真是強大又深奧的語言,功能是做出來了,但在我這個麻瓜眼中,根本是一堆去去武器走、阿咯哈姆拉、啊哇咀喀咀啦...
#!/bin/sh
# nc-curl.sh - A simple HTTP client which simulates curl using netcat (nc)
# Usage: ./nc-curl.sh <URL>
# nc-curl.sh http://www.example.com
# nc-curl.sh -X POST -d "a=1&b=2" http://www.example.com/form
# nc-curl.sh -X POST -H "Content-Type: application/json" -d '{"key":"value"}' http://www.example.com/api
METHOD="GET"
DATA=""
HEADERS=""
HAS_CT=0
URL=""
# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
-X) METHOD="$2"; shift 2 ;;
-d) case "$2" in
# @- read data from stdin, use sentinel x to keep trailing newlines
@-) DATA=$(cat; printf x); DATA="${DATA%x}" ;;
*) DATA="$2" ;;
esac
shift 2
;;
-H)
# Check if Content-Type is being set
case "$(printf '%s' "$2" | tr '[:upper:]' '[:lower:]')" in
content-type:*) HAS_CT=1 ;;
esac
HEADERS="${HEADERS}${2}\r\n"
shift 2
;;
*) URL="$1"; shift ;;
esac
done
if [ -z "$URL" ]; then
echo "Usage: $0 [-X METHOD] [-H 'Header: Value'] [-d data] <URL>" >&2
exit 1
fi
case "$URL" in
https://*)
echo "Error: HTTPS is not supported." >&2
exit 1
;;
esac
# Parse URL: http://host[:port]/path
URL_NO_SCHEME="${URL#http://}"
HOST_PORT="${URL_NO_SCHEME%%/*}"
case "$URL_NO_SCHEME" in
*/*) PATH_PART="/${URL_NO_SCHEME#*/}" ;;
*) PATH_PART="/" ;;
esac
case "$HOST_PORT" in
*:*) HOST="${HOST_PORT%%:*}"; PORT="${HOST_PORT##*:}" ;;
*) HOST="$HOST_PORT"; PORT="80" ;;
esac
# Build HTTP request
REQUEST="${METHOD} ${PATH_PART} HTTP/1.0\r\nHost: ${HOST}\r\n${HEADERS}"
if [ -n "$DATA" ]; then
CONTENT_LENGTH=$(printf '%s' "$DATA" | wc -c | tr -d ' ')
[ "$HAS_CT" -eq 0 ] && REQUEST="${REQUEST}Content-Type: application/x-www-form-urlencoded\r\n"
REQUEST="${REQUEST}Content-Length: ${CONTENT_LENGTH}\r\n\r\n${DATA}"
else
REQUEST="${REQUEST}\r\n"
fi
# Send request via nc and print response
printf "%b" "$REQUEST" | nc "$HOST" "$PORT"
使用方式模仿 curl:
./nc-curl.sh http://some-web:3150/test
printf 'a=1&b=2' | ./nc-curl.sh -X POST -d @- http://some-web:3150/form
./nc-curl.sh -X POST -d "a=1&b=2" http://some-web:3150/form
./nc-curl.sh -X POST -H "Content-Type: application/json" -d '{"key":"value"}' http://some-web:3150/api
./nc-curl.sh http://some-web:3150/not-found
實際丟上路由器測試 OK,我成功找到 curl 的替代品,未來排程可直接跟內網服務 API 溝通,有種豁然開朗的舒暢~ (灑花)

嵌入裝置 Linux 經驗值 + 5。
最後附上路由器排程直飛 Prometheus Pushgateway 的 CPU、溫度、流量數據更新排程腳本範例,告別經 syslog 轉機的笨做法,爽!
#!/bin/sh
# 一次讀取四個核心的 /proc/stat 快照,回傳格式: "total idle"
snapshot_cpu() {
cat /proc/stat | awk '/^cpu[0-3] / {
total = $2+$3+$4+$5+$6+$7+$8
idle = $5
print total, idle
}'
}
snapshot_net() {
# /proc/net/dev 欄位: face rx_bytes ... (9 欄) tx_bytes ...
# 取出 ppp0, wl0.1, wl1.1, wgs1 的 rx_bytes(col2) 及 tx_bytes(col10)
awk '/^ *(ppp0|wl0\.1|wl1\.1|wgs1):/ {
gsub(/:/, " ")
print $1, $2, $10
}' /proc/net/dev
}
# 第一次快照
cpu_snap1=$(snapshot_cpu)
net_snap1=$(snapshot_net)
sleep 1
# 第二次快照
cpu_snap2=$(snapshot_cpu)
net_snap2=$(snapshot_net)
# --- 收集所有輸出至 $DATA ---
DATA=$(
# CPU 使用率
echo "# TYPE router_cpu gauge"
core=0
echo "$cpu_snap1" | while IFS= read -r line1; do
line2=$(echo "$cpu_snap2" | sed -n "$((core + 1))p")
total1=$(echo "$line1" | awk '{print $1}')
idle1=$(echo "$line1" | awk '{print $2}')
total2=$(echo "$line2" | awk '{print $1}')
idle2=$(echo "$line2" | awk '{print $2}')
usage=$(( 100 * ( (total2 - total1) - (idle2 - idle1) ) / (total2 - total1) ))
echo "router_cpu{core=\"cpu$((core + 1))\"} $usage"
core=$((core + 1))
done
# 網路每秒 RX / TX bytes
echo "# TYPE router_network gauge"
# Use awk for arithmetic to avoid 32-bit integer overflow in shell $(( ))
echo "$net_snap1" | while IFS= read -r line1; do
iface=$(echo "$line1" | awk '{print $1}')
line2=$(echo "$net_snap2" | awk -v iface="$iface" '$1 == iface {print}')
echo "$line1 $line2" | awk '{printf "router_network{if=\"%s\",dir=\"rx\"} %.0f\nrouter_network{if=\"%s\",dir=\"tx\"} %.0f\n", $1, $5-$2, $1, $6-$3}'
done
# --- 溫度 ---
temp=$(cat /sys/class/thermal/thermal_zone0/temp)
echo "# TYPE router_temperature gauge"
echo "router_temperature $((temp / 1000))"
)
printf '%s\n' "$DATA" | ./nc-curl.sh -X POST -d @- http://<pushgateway-ip>:9091/metrics/job/router
Comments
Be the first to post a comment