Files
clawd/lib/dns-hijack.js
stswangzhiping eb9f4ab1c3 feat: add WiFi provisioning for headless devices (AP + Captive Portal)
- Add lib/network.js: WiFi scan, connect, AP hotspot via nmcli
- Add lib/dns-hijack.js: dnsmasq management for DNS hijack + DHCP
- Add lib/captive-server.js: embedded HTTP captive portal with WiFi setup page
- Add lib/provisioning.js: orchestrator (detect network -> AP mode -> wait -> exit)
- Update client.js: call ensureNetwork() before WS connection
- Update install.sh: auto-install dnsmasq dependency

Made-with: Cursor
2026-03-16 08:58:51 +08:00

100 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const { spawn, execSync } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const log = require('./logger');
const { Watchdog } = require('./watchdog');
const CONFIG_DIR = process.env.CLAWD_CONFIG_DIR
|| (process.getuid && process.getuid() === 0 ? '/etc/clawd' : path.join(os.homedir(), '.clawd'));
const DNSMASQ_CONF = path.join(CONFIG_DIR, 'dnsmasq-captive.conf');
const CAPTIVE_DOMAIN = 'ap.cutos.ai';
/**
* 管理 dnsmasq 进程:
* - 所有 DNS 查询 → 网关 IP触发 Captive Portal 弹窗)
* - DHCP 分配 10.42.0.50 ~ 10.42.0.150
* - ap.cutos.ai 专门指向网关
*/
class DnsHijack {
constructor() {
this._watchdog = null;
}
/**
* 启动 dnsmasq
* @param {string} iface - AP 接口名(如 wlan0
* @param {string} gatewayIp - 网关 IP如 10.42.0.1
*/
start(iface, gatewayIp) {
this.stop();
const rangeStart = gatewayIp.replace(/\.\d+$/, '.50');
const rangeEnd = gatewayIp.replace(/\.\d+$/, '.150');
const conf = [
`interface=${iface}`,
'bind-interfaces',
`listen-address=${gatewayIp}`,
'',
'# DHCP',
`dhcp-range=${rangeStart},${rangeEnd},255.255.255.0,12h`,
`dhcp-option=3,${gatewayIp}`, // gateway
`dhcp-option=6,${gatewayIp}`, // DNS server
'',
'# DNS: 所有域名指向网关(触发 Captive Portal 检测)',
`address=/#/${gatewayIp}`,
'',
'# 日志',
'log-queries',
'log-facility=-', // stdout → Watchdog 采集
'',
'# 禁用系统 resolv.conf',
'no-resolv',
'no-poll',
].join('\n');
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(DNSMASQ_CONF, conf, 'utf8');
log.info('dns', `dnsmasq 配置已写入: ${DNSMASQ_CONF}`);
// 终止系统可能残留的 dnsmasq
try { execSync('pkill -f "dnsmasq.*clawd"', { timeout: 3000 }); } catch (_) {}
// 检查 dnsmasq 是否安装
try {
execSync('which dnsmasq', { timeout: 3000 });
} catch (_) {
log.error('dns', 'dnsmasq 未安装,请运行: apt install dnsmasq');
return;
}
this._watchdog = new Watchdog('dns', 'dnsmasq', [
'--no-daemon',
`--conf-file=${DNSMASQ_CONF}`,
], {
maxRestarts: 5,
windowMs: 60_000,
restartDelay: 2_000,
onStdout: (line) => log.debug('dns', line),
onStderr: (line) => log.debug('dns', line),
});
this._watchdog.start();
log.info('dns', `dnsmasq 已启动: ${CAPTIVE_DOMAIN}${gatewayIp}, 全域劫持`);
}
stop() {
if (this._watchdog) {
this._watchdog.stop();
this._watchdog = null;
}
try { execSync('pkill -f "dnsmasq.*clawd"', { timeout: 3000 }); } catch (_) {}
try { fs.unlinkSync(DNSMASQ_CONF); } catch (_) {}
}
}
module.exports = { DnsHijack, CAPTIVE_DOMAIN };