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
This commit is contained in:
stswangzhiping
2026-03-27 14:30:23 +08:00
parent 88283ad6b6
commit 12366790d2
2 changed files with 69 additions and 20 deletions

View File

@@ -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 → 立即返回 falsenmcli 有缓存,不可信)
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 → 立即 falsenmcli 有缓存,不可信)
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,