diff --git a/lib/client.js b/lib/client.js index 1b47205..d72a36c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -11,7 +11,7 @@ const { collect } = require('./metrics'); const { getDashboardInfo, resolveOpenclawConfigFile, startTtyd, FrpcManager } = require('./frpc'); // getDashboardInfo 也用于心跳中定期刷新 const { ProvisionManager } = require('./provisioning'); const { BtMonitor } = require('./bt-monitor'); -const { hasInternet, hasWiredInternetProbe, getLocalIps } = require('./network'); +const { hasInternet, hasWiredInternetProbe, getLocalIps, getLocalNetworks } = require('./network'); const { applyFullProviderFromVps, removeProviderByName, refreshModelsIfChanged, isFullProvider } = require('./openclaw-provider'); const led = require('./led'); @@ -371,8 +371,9 @@ class ClawClient { box_id: this._boxId, claw_id: this._cfg.claw_id ?? null, token: this._cfg.token ?? null, - local_ip: getLocalIps(), - external_ip: this._externalIp ?? null, + local_ip: getLocalIps(), + local_networks: getLocalNetworks(), + external_ip: this._externalIp ?? null, location: this._location ?? null, ...this._dashInfo, }; @@ -595,8 +596,10 @@ class ClawClient { // 每 METRICS_EVERY_N 次心跳(30 秒)采集一次指标,其余发轻量心跳 const msg = { type: 'heartbeat', - claw_id: this._cfg.claw_id, - token: this._cfg.token, + claw_id: this._cfg.claw_id, + token: this._cfg.token, + local_ip: getLocalIps(), + local_networks: getLocalNetworks(), ...this._dashInfo, }; if (this._hbCount % METRICS_EVERY_N === 0) { diff --git a/lib/network.js b/lib/network.js index e85300b..1b3eb34 100644 --- a/lib/network.js +++ b/lib/network.js @@ -507,22 +507,37 @@ function isWifiStaConnected() { return true; } +function _ifaceNetworkType(name) { + const wifi = getWifiIface(); + if (name === wifi || name.startsWith('wl')) return 'wifi'; + if (name === DEFAULT_ETH_IFACE || name.startsWith('en') || name.startsWith('eth')) return 'lan'; + return null; +} + +function _localNetworkEntries() { + const ifaces = os.networkInterfaces(); + const entries = []; + for (const [name, addrs] of Object.entries(ifaces)) { + if (!addrs) continue; + const type = _ifaceNetworkType(name); + if (!type) continue; + for (const addr of addrs) { + if (addr.family !== 'IPv4' || addr.internal) continue; + // clawd-hotspot 的 AP 管理网段只用于配网,不上报为 BOX 可访问地址。 + if (addr.address.startsWith('10.42.')) continue; + entries.push({ ip: addr.address, type, iface: name }); + } + } + return entries; +} + /** - * 获取本机所有非回环 IPv4 地址,逗号拼接返回 - * 例:'192.168.1.100' 或 '192.168.1.100,10.0.0.5' + * 获取本机所有非回环 IPv4 地址,逗号拼接返回。 + * 保持旧协议字段 local_ip 兼容:'192.168.1.100' 或 '192.168.1.100,10.0.0.5'。 */ function getLocalIps() { try { - const ifaces = os.networkInterfaces(); - const ips = []; - for (const [name, addrs] of Object.entries(ifaces)) { - if (!addrs) continue; - for (const addr of addrs) { - if (addr.family === 'IPv4' && !addr.internal && !addr.address.startsWith('10.42.')) { - ips.push(addr.address); - } - } - } + const ips = _localNetworkEntries().map((entry) => entry.ip); return ips.length > 0 ? ips.join(',') : null; } catch (e) { log.warn('network', '获取本机 IP 失败:', e.message); @@ -530,6 +545,20 @@ function getLocalIps() { } } +/** + * 获取本机 IPv4 地址及网络类型,用于上报服务器。 + * 例:[{ ip: '192.168.1.100', type: 'wifi', iface: 'wlan0' }] + */ +function getLocalNetworks() { + try { + const entries = _localNetworkEntries(); + return entries.length > 0 ? entries : null; + } catch (e) { + log.warn('network', '获取本机网络类型失败:', e.message); + return null; + } +} + module.exports = { hasInternet, hasWiredCarrier, @@ -547,4 +576,5 @@ module.exports = { stopAP, AP_IP, getLocalIps, + getLocalNetworks, };