From 12366790d2e338653a0bde4b0e895ff902793226 Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:30:23 +0800 Subject: [PATCH] fix(network): auto-detect wired iface and ping via -I for hasInternet - Scan sysfs for carrier when CLAWD_ETH_IFACE unset (end0/enp* etc.) - Explicit CLAWD_ETH_IFACE still pins a single interface - Ping fallback uses -I wired iface when default route fails (e.g. AP on wlan) - Exclude can/docker/veth/wl* and similar from auto scan - Clarify client log when waiting for WAN Made-with: Cursor --- lib/client.js | 2 +- lib/network.js | 87 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/lib/client.js b/lib/client.js index bc857fc..339cb3c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -110,7 +110,7 @@ class ClawClient { this._connectionStarted = true; await this._proceedWithConnection(); } else if (!hasInternet()) { - log.info('clawd', '等待网络就绪(WiFi 配网或网线接入)...'); + log.info('clawd', '等待外网就绪(可配网;有线已接时将自动识别网卡,含非 eth0 口)...'); } } diff --git a/lib/network.js b/lib/network.js index 00e5120..7ae0577 100644 --- a/lib/network.js +++ b/lib/network.js @@ -9,43 +9,91 @@ const AP_SSID_PREFIX = 'ClawBox-'; const AP_IP = '10.42.0.1'; const AP_PASSWORD = '12345678'; const AP_IFACE = process.env.CLAWD_WIFI_IFACE || ''; -const ETH_IFACE = process.env.CLAWD_ETH_IFACE || 'eth0'; const CON_NAME = 'clawd-hotspot'; -/** - * 检查有线网卡物理链路是否接通(读 sysfs carrier,无延迟) - */ -function hasWiredCarrier() { - try { - const carrier = fs.readFileSync(`/sys/class/net/${ETH_IFACE}/carrier`, 'utf8').trim(); - return carrier === '1'; - } catch (_) { - return false; - } +/** 非 WiFi、非典型虚拟接口,用于自动发现有线口(如 end0、enp*,而非仅 eth0) */ +function _isExcludedVirtualIface(name) { + if (name === 'lo' || name === 'bonding_masters') return true; + if (name.startsWith('wl')) return true; + if (name.startsWith('docker')) return true; + if (name.startsWith('veth')) return true; + if (name.startsWith('virbr')) return true; + if (name.startsWith('br-')) return true; + if (name.startsWith('tun') || name.startsWith('tap')) return true; + if (name.startsWith('wg') || name.startsWith('bond')) return true; + if (name.startsWith('can')) return true; + return false; } /** - * 检测是否有互联网连接(尝试 DNS 解析 + HTTP 连通性) + * 返回当前链路 up 的有线网卡名。 + * 若设置 CLAWD_ETH_IFACE,只认该接口;否则扫描 sysfs(与仅默认 eth0 相比适配更多板型)。 */ -function hasInternet() { - // 物理层快检:无 WiFi STA 且有线 carrier=0 → 立即返回 false(nmcli 有缓存,不可信) - if (!isWifiStaConnected() && !hasWiredCarrier()) return false; +function getWiredIfaceWithCarrier() { + const explicit = process.env.CLAWD_ETH_IFACE; + if (explicit) { + try { + if (fs.readFileSync(`/sys/class/net/${explicit}/carrier`, 'utf8').trim() === '1') { + return explicit; + } + } catch (_) {} + return null; + } - // 优先用 nmcli 的 connectivity check try { - const out = run('nmcli networking connectivity check').trim(); - if (out === 'full' || out === 'limited') return true; + const names = fs.readdirSync('/sys/class/net'); + for (const name of names.sort()) { + if (_isExcludedVirtualIface(name)) continue; + try { + if (fs.readFileSync(`/sys/class/net/${name}/carrier`, 'utf8').trim() === '1') { + return name; + } + } catch (_) {} + } } catch (_) {} - // 兜底:ping DNS + return null; +} + +/** + * 是否存在任一带 carrier 的有线接口(无延迟) + */ +function hasWiredCarrier() { + return getWiredIfaceWithCarrier() !== null; +} + +function _tryPingInternet() { try { run('ping -c 1 -W 3 8.8.8.8'); return true; } catch (_) {} + // 开热点时默认路由可能走 wlan,无 -I 的 ping 会误判;指定有线口再试 + const wired = getWiredIfaceWithCarrier(); + if (wired) { + try { + run(`ping -c 1 -W 3 -I ${wired} 8.8.8.8`); + return true; + } catch (_) {} + } return false; } +/** + * 检测是否有互联网连接(nmcli 连通性 + ping 兜底) + */ +function hasInternet() { + // 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 false(nmcli 有缓存,不可信) + if (!isWifiStaConnected() && !hasWiredCarrier()) return false; + + try { + const out = run('nmcli networking connectivity check').trim(); + if (out === 'full' || out === 'limited') return true; + } catch (_) {} + + return _tryPingInternet(); +} + /** * 获取默认 WiFi 接口名(wlan0 等) */ @@ -250,6 +298,7 @@ function getLocalIps() { module.exports = { hasInternet, hasWiredCarrier, + getWiredIfaceWithCarrier, hasSavedWifiConnection, isWifiStaConnected, getWifiIface,