From ea502fc26a71a89a879cbbc7fdd84af05b97143f Mon Sep 17 00:00:00 2001 From: stswangzhiping <59632378+stswangzhiping@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:33:29 +0800 Subject: [PATCH] refactor(network): unify sysfs carrier reads and LAN vs wired iface logic --- lib/network.js | 118 +++++++++++++++++++++---------------------------- 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/lib/network.js b/lib/network.js index 0dcd454..c66a151 100644 --- a/lib/network.js +++ b/lib/network.js @@ -1,17 +1,41 @@ 'use strict'; const { execSync } = require('child_process'); -const fs = require('fs'); -const os = require('os'); +const fs = require('fs'); +const os = require('os'); const log = require('./logger'); 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 CON_NAME = 'clawd-hotspot'; +const AP_IP = '10.42.0.1'; +const AP_PASSWORD = '12345678'; +const AP_IFACE = process.env.CLAWD_WIFI_IFACE || ''; +const CON_NAME = 'clawd-hotspot'; -/** 非 WiFi、非典型虚拟接口,用于自动发现有线口(如 end0、enp*,而非仅 eth0) */ +/** 产品 RJ45 在 sysfs 中的默认名;等价于检测 `cat /sys/class/net/end0/carrier` */ +const DEFAULT_ETH_IFACE = 'end0'; + +function _ethIfaceEnvOrDefault() { + return process.env.CLAWD_ETH_IFACE || DEFAULT_ETH_IFACE; +} + +function _netIfaceExists(name) { + try { + return fs.existsSync(`/sys/class/net/${name}`); + } catch (_) { + return false; + } +} + +/** 读取 `/sys/class/net//carrier`,`1` 为链路 up;缺失或异常视为 down */ +function _sysfsCarrierUp(iface) { + try { + return fs.readFileSync(`/sys/class/net/${iface}/carrier`, 'utf8').trim() === '1'; + } catch (_) { + return false; + } +} + +/** 非 WiFi、非典型虚拟接口,用于开发机扫描有线口(enp* 等) */ function _isExcludedVirtualIface(name) { if (name === 'lo' || name === 'bonding_masters') return true; if (name.startsWith('wl')) return true; @@ -26,85 +50,45 @@ function _isExcludedVirtualIface(name) { } /** - * 返回当前链路 up 的有线网卡名。 - * 若设置 CLAWD_ETH_IFACE,只认该接口;否则扫描 sysfs(与仅默认 eth0 相比适配更多板型)。 + * 开发机:无 CLAWD_ETH_IFACE 且无 end0 时,扫描 sysfs 找第一个 carrier=1 的有线口。 */ -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; - } - +function _firstScanWiredIfaceWithCarrier() { try { 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 (_) {} + if (_sysfsCarrierUp(name)) return name; } } catch (_) {} - return null; } /** - * 是否存在任一带 carrier 的有线接口(无延迟) + * 返回当前可用于「有线 ping / 路由」的网卡名。 + * 优先级:CLAWD_ETH_IFACE → 存在 end0 则只用 end0 → 否则扫描 sysfs。 */ +function getWiredIfaceWithCarrier() { + const explicit = process.env.CLAWD_ETH_IFACE; + if (explicit) { + return _netIfaceExists(explicit) && _sysfsCarrierUp(explicit) ? explicit : null; + } + if (_netIfaceExists(DEFAULT_ETH_IFACE)) { + return _sysfsCarrierUp(DEFAULT_ETH_IFACE) ? DEFAULT_ETH_IFACE : null; + } + return _firstScanWiredIfaceWithCarrier(); +} + function hasWiredCarrier() { return getWiredIfaceWithCarrier() !== null; } /** - * 物理链路是否 up:carrier + operstate 双读,避免单读 carrier 拔线不更新、或与 WiFi 状态不同步的误判。 - */ -function _ifacePhysicalLinkUp(iface) { - try { - const base = `/sys/class/net/${iface}`; - const carrier = fs.readFileSync(`${base}/carrier`, 'utf8').trim(); - if (carrier !== '1') return false; - let oper = 'unknown'; - try { - oper = fs.readFileSync(`${base}/operstate`, 'utf8').trim(); - } catch (_) {} - if (oper === 'down' || oper === 'lowerlayerdown') return false; - return true; - } catch (_) { - return false; - } -} - -/** - * LAN 面板灯:只反映指定/板载以太网 RJ45 链路,与 WiFi 无关。 - * - CLAWD_ETH_IFACE:只读该口 - * - 否则只认 end0、eth0(存在则仅用其 sysfs,链路 down 即灭,不再退回「任一带 carrier 口」以免跟 WiFi 耦合) - * - 无 end0/eth0 节点时(开发机)退回 hasWiredCarrier() + * LAN 面板灯:只反映 RJ45 对应口,与 `cat /sys/class/net/end0/carrier 2>/dev/null` 同源(仅读 carrier)。 + * 若配置的接口在 sysfs 中不存在(常见为开发机无 end0),则退回与 hasWiredCarrier() 一致,避免灯永远灭。 */ function hasLanCableCarrier() { - const explicit = process.env.CLAWD_ETH_IFACE; - if (explicit) { - try { - return fs.existsSync(`/sys/class/net/${explicit}`) && _ifacePhysicalLinkUp(explicit); - } catch (_) { - return false; - } - } - - let hasBoardIface = false; - for (const n of ['end0', 'eth0']) { - if (!fs.existsSync(`/sys/class/net/${n}`)) continue; - hasBoardIface = true; - if (_ifacePhysicalLinkUp(n)) return true; - } - if (hasBoardIface) return false; - + const iface = _ethIfaceEnvOrDefault(); + if (_netIfaceExists(iface)) return _sysfsCarrierUp(iface); return hasWiredCarrier(); }