fix(detect): never bind USB gadget iface as proxy egress
设备把自己当 USB 网卡挂给上游主机时会出现 usb0(驱动 configfs-gadget.g1, IP 192.168.68.1)。该链路只通向 PC,被当成出口后所有出站都 context deadline exceeded,客户端看到 socks connect failed rep=0x01。 误选原因:score_iface_as_cellular 给 usb0 与真出口 wwan0 都打 175 分。 usb0 靠名称命中 patterns 里的 usb(+80)与非默认路由(+40);wwan0 的 bam-dmux 驱动不在白名单里,白丢 +100。detect_cellular_iface 用 -gt 比较, list_ifaces 按名排序让 usb0 先入选,平分下无法被顶替。 改动: - lib.sh 新增 iface_is_usb_gadget(),按驱动名与 gadget 总线路径识别, 并并入 is_virtual_or_skip_iface(usb0 评分 175 -> 0) - 驱动白名单补 bam-dmux/bam_dmux/qcom-ipa/ipa_wan;名称权重 80 -> 40, 确保驱动证据始终压过名称猜测 - 新增 detect_cellular_iface_via_mm(),把 ModemManager bearer 的 interface: 作为第 0 步权威来源 - detect_cellular_iface / resolve_cellular 全链路拒绝 gadget 网卡, 取不到真出口时按 REQUIRE_CELLULAR_IFACE 直接失败而不是绑错 - generate/install/upgrade/watch/detect/verify 各入口独立设闸, verify.sh 发现出口是 gadget 时直接 exit 4 并给出修复命令 - 默认 CELLULAR_IFACE_PATTERNS 去掉 usb/enx;upgrade.sh 与根 install.sh 就地迁移已有 settings.conf,旧机器升级即修复 - 新增 tests/detect-gadget.sh:18 条离线断言,无需真机
This commit is contained in:
+87
-8
@@ -33,7 +33,7 @@ load_config() {
|
||||
CELLULAR_SOURCE_IP="${CELLULAR_SOURCE_IP:-}"
|
||||
BIND_SOURCE_IP="${BIND_SOURCE_IP:-false}"
|
||||
REQUIRE_CELLULAR_IFACE="${REQUIRE_CELLULAR_IFACE:-true}"
|
||||
CELLULAR_IFACE_PATTERNS="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
CELLULAR_IFACE_PATTERNS="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
ENABLE_CELLULAR_WATCH="${ENABLE_CELLULAR_WATCH:-true}"
|
||||
WATCH_INTERVAL_SEC="${WATCH_INTERVAL_SEC:-60}"
|
||||
WATCH_MM_EVENTS="${WATCH_MM_EVENTS:-true}"
|
||||
@@ -77,6 +77,34 @@ iface_exists() {
|
||||
[[ -n "$ifc" ]] && [[ -d "/sys/class/net/$ifc" ]]
|
||||
}
|
||||
|
||||
# 识别「本机当 USB 设备端」的 gadget 网卡(usb0/rndis0 等)。
|
||||
# 这类接口是本机 → 上游主机(PC/路由)的下行链路,绑上去所有出站都会失败,
|
||||
# sing-box 表现为 open outbound connection: context deadline exceeded,SOCKS 回 rep=0x01。
|
||||
# 注意与真正的 USB 上行模组区分:qmi_wwan / cdc_* / rndis_host 是 host 侧驱动,可作出口。
|
||||
iface_is_usb_gadget() {
|
||||
local ifc="$1" drv dev
|
||||
[[ -n "$ifc" ]] || return 1
|
||||
drv="$(iface_driver "$ifc" 2>/dev/null || true)"
|
||||
case "${drv,,}" in
|
||||
configfs-gadget*|g_ether*|g_ncm*|g_ether|g_multi*|g_cdc*|gadget*|usb_f_*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
# configfs 组合设备的 device 实体挂在 gadget 总线下
|
||||
dev="$(readlink -f "/sys/class/net/$ifc/device" 2>/dev/null || true)"
|
||||
if [[ -n "$dev" && "$dev" == *"/gadget"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -d "/sys/class/net/$ifc/device" ]]; then
|
||||
local sub
|
||||
sub="$(readlink -f "/sys/class/net/$ifc/device/subsystem" 2>/dev/null || true)"
|
||||
if [[ "$(basename "${sub:-}")" == "gadget" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
is_virtual_or_skip_iface() {
|
||||
local name="$1"
|
||||
case "$name" in
|
||||
@@ -88,6 +116,10 @@ is_virtual_or_skip_iface() {
|
||||
if [[ -d "/sys/class/net/$name/bridge" ]]; then
|
||||
return 0
|
||||
fi
|
||||
# 本机作为 USB gadget 暴露给上游主机的链路(RNDIS/NCM/ECM),只通向 PC,不是数据出口
|
||||
if iface_is_usb_gadget "$name"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -127,8 +159,10 @@ iface_driver() {
|
||||
iface_looks_cellular_by_driver() {
|
||||
local ifc="$1" drv
|
||||
drv="$(iface_driver "$ifc" || true)"
|
||||
# gadget 网卡即使名字像 usb0 也不算蜂窝
|
||||
iface_is_usb_gadget "$ifc" && return 1
|
||||
case "${drv,,}" in
|
||||
qmi_wwan|cdc_mbim|cdc_ncm|cdc_ether|cdc_wdm|option|huawei_cdc_ncm|rndis_host|GobiNet|GobiSerial|simcom*|rmnet*|mhi_net|ipa)
|
||||
qmi_wwan|cdc_mbim|cdc_ncm|cdc_ether|cdc_wdm|option|huawei_cdc_ncm|rndis_host|GobiNet|GobiSerial|simcom*|rmnet*|mhi_net|ipa|bam-dmux|bam_dmux|qcom-ipa|ipa_wan)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
@@ -137,7 +171,9 @@ iface_looks_cellular_by_driver() {
|
||||
|
||||
iface_matches_patterns() {
|
||||
local name="$1" p
|
||||
local patterns="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,usb,enx,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
# 默认不含 usb/enx:本机的 USB gadget 链路也叫 usb0,靠名字猜会把出口绑到 PC 侧。
|
||||
# 真正的 USB 上行模组由 iface_looks_cellular_by_driver(rndis_host/cdc_*/qmi_wwan)识别。
|
||||
local patterns="${CELLULAR_IFACE_PATTERNS:-wwan,wwp,ppp,cdc,rmnet,ccmni,mbim,qmi}"
|
||||
IFS=',' read -r -a arr <<< "$patterns"
|
||||
for p in "${arr[@]}"; do
|
||||
p="${p// /}"
|
||||
@@ -149,6 +185,23 @@ iface_matches_patterns() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# 最权威的来源:ModemManager 自己报的 bearer 网卡名。
|
||||
# 比名称/驱动启发式打分更可靠,能彻底避开 usb0 这类同分误选。
|
||||
detect_cellular_iface_via_mm() {
|
||||
local m b ifc
|
||||
command -v mmcli >/dev/null 2>&1 || return 1
|
||||
for m in $(mmcli -L 2>/dev/null | grep -oE 'Modem/[0-9]+' | grep -oE '[0-9]+$'); do
|
||||
for b in $(mmcli -m "$m" 2>/dev/null | grep -oE 'Bearer/[0-9]+' | grep -oE '[0-9]+$'); do
|
||||
ifc="$(mmcli -b "$b" 2>/dev/null | sed -n 's/.*interface:[[:space:]]*\([A-Za-z0-9._-]\{1,\}\).*/\1/p' | head -1)"
|
||||
if [[ -n "$ifc" ]] && iface_exists "$ifc" && ! iface_is_usb_gadget "$ifc"; then
|
||||
echo "$ifc"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# 多默认路由时,metric 更大的往往是数据网(WiFi metric 更小优先)
|
||||
detect_secondary_default_iface() {
|
||||
local primary secondary
|
||||
@@ -186,9 +239,9 @@ score_iface_as_cellular() {
|
||||
ip="$(ip -4 -o addr show dev "$name" 2>/dev/null | awk '{print $4}' | head -1 | cut -d/ -f1)"
|
||||
drv="$(iface_driver "$name" || true)"
|
||||
|
||||
# 名称
|
||||
if iface_matches_patterns "$name"; then score=$((score + 80)); fi
|
||||
# 驱动
|
||||
# 名称(仅启发式,权重必须低于驱动判定,避免 usb0 与 wwan0 打成平手)
|
||||
if iface_matches_patterns "$name"; then score=$((score + 40)); fi
|
||||
# 驱动(强证据)
|
||||
if iface_looks_cellular_by_driver "$name"; then score=$((score + 100)); fi
|
||||
# 非默认网卡(关键:系统默认走 WiFi)
|
||||
if [[ -n "$default_if" && "$name" != "$default_if" ]]; then score=$((score + 40)); fi
|
||||
@@ -209,7 +262,8 @@ score_iface_as_cellular() {
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
# 自动探测数据网卡:配置 > 名称/驱动 > 次默认路由 > 非默认有 IP 物理口
|
||||
# 自动探测数据网卡:ModemManager bearer > 配置 > 名称/驱动打分 > 次默认路由 > 非默认有 IP 物理口
|
||||
# 任何一步都不允许返回 USB gadget 网卡
|
||||
detect_cellular_iface() {
|
||||
local configured="${1:-}"
|
||||
local default_if name state ip best_name="" best_score=0 score
|
||||
@@ -217,6 +271,8 @@ detect_cellular_iface() {
|
||||
if [[ -n "$configured" ]]; then
|
||||
if [[ "$configured" == "lo" ]]; then
|
||||
warn "CELLULAR_IFACE=lo 无效,将尝试自动探测"
|
||||
elif iface_is_usb_gadget "$configured"; then
|
||||
warn "CELLULAR_IFACE=$configured 是本机对上游主机暴露的 USB gadget 网卡,不能作为数据出口,将重新探测"
|
||||
elif iface_exists "$configured"; then
|
||||
echo "$configured"
|
||||
return 0
|
||||
@@ -230,6 +286,15 @@ detect_cellular_iface() {
|
||||
|
||||
default_if="$(detect_default_iface || true)"
|
||||
|
||||
# 0) ModemManager bearer 是权威答案,优先采用
|
||||
if name="$(detect_cellular_iface_via_mm 2>/dev/null || true)"; then
|
||||
if [[ -n "$name" ]]; then
|
||||
info "按 ModemManager bearer 确定数据网卡: $name"
|
||||
echo "$name"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1) 按评分扫描全部接口
|
||||
while IFS='|' read -r name state ip; do
|
||||
[[ -z "$name" ]] && continue
|
||||
@@ -250,7 +315,7 @@ detect_cellular_iface() {
|
||||
|
||||
# 2) 多默认路由的次要口
|
||||
if name="$(detect_secondary_default_iface 2>/dev/null || true)"; then
|
||||
if [[ -n "$name" ]] && iface_exists "$name"; then
|
||||
if [[ -n "$name" ]] && iface_exists "$name" && ! iface_is_usb_gadget "$name"; then
|
||||
info "根据次要默认路由探测数据网卡: $name"
|
||||
echo "$name"
|
||||
return 0
|
||||
@@ -335,5 +400,19 @@ resolve_cellular() {
|
||||
warn "未找到数据网卡"
|
||||
cell=""
|
||||
fi
|
||||
# 最后一道闸:探测链路上任何环节都不得交出 gadget 网卡
|
||||
if [[ -n "$cell" ]] && iface_is_usb_gadget "$cell"; then
|
||||
warn "拒绝把 USB gadget 网卡 $cell 当作数据出口,改问 ModemManager"
|
||||
cell="$(detect_cellular_iface_via_mm 2>/dev/null || true)"
|
||||
if [[ -z "$cell" ]]; then
|
||||
if [[ "$REQUIRE_CELLULAR_IFACE" == "true" ]]; then
|
||||
die "唯一候选是 USB gadget 网卡,无法确定数据出口。请设置 CELLULAR_IFACE=wwanX"
|
||||
fi
|
||||
warn "未找到可用数据网卡"
|
||||
cell=""
|
||||
else
|
||||
info "改用 $cell"
|
||||
fi
|
||||
fi
|
||||
echo "$cell"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user