dns.lookup()走系统resolver(查hosts等),不发网络请求;dns.resolve4()绕过系统直连dns服务器,故hosts对其无效,需统一dns代理层拦截。

直接改 /etc/hosts 或 C:\Windows\System32\drivers\etc\hosts 不解决动态测试需求,Node 本身不参与 DNS 解析,必须靠本地 DNS 代理层拦截并返回预设 IP。
为什么 dns.lookup() 和 dns.resolve4() 行为完全不同
dns.lookup() 走系统 resolver(查 /etc/hosts、nsswitch.conf、mDNS),不发网络请求;dns.resolve4() 绕过系统,直连 DNS 服务器——这意味着你在 hosts 里加的条目对后者完全无效。
- 开发中若用
http.get({ host: 'myapp.test' }),底层调的是dns.lookup(),能命中 hosts - 但若显式调
dns.resolve4('myapp.test')或用某些 HTTP 客户端(如 undici 默认行为),就会跳过 hosts,走真实 DNS 查询 - 验证方式:在代码里分别执行
dns.lookup('myapp.test')和dns.resolve4('myapp.test'),看是否都返回127.0.0.1 - 结论:仅靠 hosts 无法覆盖所有 Node DNS 调用路径,必须统一 DNS 层出口
macOS/Linux 用 dnsmasq 实现通配劫持
Homebrew 安装后,关键不是启动服务,而是确认配置生效且系统 DNS 指向它。
- 编辑
/usr/local/etc/dnsmasq.conf,只加这一行:address=/.test/127.0.0.1(注意开头的/.表示通配) - 运行
sudo brew services restart dnsmasq,别漏sudo,否则监听不了 53 端口 - 系统网络设置里,把 DNS 服务器手动设为
127.0.0.1(不是“自动”,也不是“继承自路由器”) - 验证:
nslookup foo.test必须返回127.0.0.1;若返回空或超时,90% 是 DNS 设置没生效 - 常见坑:
dnsmasq默认不读取/etc/resolv.conf,所以即使你改了那里也没用,必须在系统网络偏好里显式填
Windows 用 SimpleDNSProxy 替代 hosts
它比改 hosts 强在支持通配、JSON 配置、无需管理员权限运行(只要监听端口不冲突)。
- 下载最新 release 后解压,编辑
config.json的rules数组:{"domain": "*.dev", "ip": "127.0.0.1"} - 双击
SimpleDNSProxy.exe启动,任务栏右下角托盘图标变绿即表示运行中 - 控制面板 → 网络和 Internet → 更改适配器选项 → 右键当前连接 → 属性 → IPv4 → 手动填 DNS 为
127.0.0.1 - Chrome 对
.local域名有硬编码 mDNS 处理,会绕过你的代理,务必用.test或.dev - 如果
nslookup myapp.dev返回127.0.0.1但浏览器打不开,检查 Express 是否校验了req.headers.host,否则可能 400 或 CORS
VSCode 调试时 DNS 解析卡住的离线绕过法
不是所有卡顿都来自业务代码——VSCode 的 pwa-node 调试器自身会在启动阶段尝试联网校验调试适配器版本、加载远程 source map。
- 离线环境启动前,在 launch.json 的
runtimeArgs加两项:--dns-result-order=ipv4first和--no-dns(Node ≥20.10) - 同时在
env中设NO_PROXY=*,防止调试器内部 fetch 试图走代理 - 禁用 VSCode 扩展 Marketplace 相关功能:设置里搜
telemetry关掉所有遥测,搜extensions.autoCheckUpdates设为false - 最关键的一步:彻底退出 VSCode(macOS 上 Cmd+Q,Windows 上右键任务栏图标退出),再重开——旧进程残留会导致 DNS 缓存未刷新
- 如果仍卡在
getaddrinfo ENOTFOUND,说明某处代码或依赖(比如 axios 默认配置)触发了 DNS 查询,用strace -e trace=connect,sendto node index.js(Linux/macOS)定位具体调用点
真正难的不是配通一个域名,而是让所有 DNS 调用路径(系统 resolver、C-Ares、调试器自身、第三方库)全部收敛到同一出口。多数失败源于某一层被忽略,比如改了 dnsmasq 却忘了系统 DNS 设置,或写了 address=/.test/127.0.0.1 却用了 .local 测试。











