内网外网互通工具:Node.js 原生库实现,零第三方依赖,Agent/Server 双角色,支持多通道映射
| Files | Last commit | Last update |
|---|---|---|
| 18 days ago | ||
| 1 month ago | ||
| 2 months ago | ||
| 2 months ago | ||
| 18 days ago | ||
| 2 months ago | ||
| 1 month ago | ||
| 18 days ago | ||
| 2 months ago | ||
| 2 months ago | ||
| 18 days ago | ||
| 1 month ago |
nagent-remote
轻量级内网穿透工具,基于 Node.js 原生库实现,零第三方依赖。
同一程序通过配置切换 内网 Agent(server 模式)和 ECS 公网服务端(client 模式)两种角色。适合个人/小团队将家中电脑、办公内网的服务暴露到公网。
当前版本:v1.7.3
✨ 特性
- 零依赖 — 仅使用 Node.js 内置
net/fs/path/crypto等模块 - 单一程序双角色 — 通过
--role server|client切换,一套配置文件部署两台机器 - Token 认证 — 控制通道握手验证,防止未授权隧道注册
- 断线自动重连 — 指数退避(2s → 60s),网络恢复后自动重建隧道
- 心跳检测 — 30s 间隔,3 次超时自动断开死连接
- 并发限制 — 单隧道最大 500 并发连接
- 日志按天分割 — 每日一个日志文件,自动清理超过保留天数的旧日志
- 优雅退出 — SIGINT/SIGTERM 信号触发时清理所有连接
- PM2 兼容 — 与 PM2 进程管理无缝配合
- IP 封禁 — 控制口 token 认证连续失败自动封禁来源 IP
- TLS 支持(v1.7.0)— 控制通道可走 TLS,支持
servername指定 SNI,解决用 IP 连接域名证书时的ERR_TLS_CERT_ALTNAME_INVALID - RDP 认证失败封禁(v1.7.0)— 按认证是否失败判定,密码正确永远不计数
- 封禁加固(v1.7.1)— 封禁白名单(IP / CIDR / fail2ban
ignoreip文件)、封禁时长递增、封禁状态持久化落盘、fail2ban 事件文件对接 - 准入与封禁解耦(v1.7.3)—
allowAnySource(放谁进来)与enableFailBan(封谁)拆成两个独立开关,动态 IP 场景既能不限来源又能保持封禁生效
🔐 RDP 认证失败封禁(v1.7.0)
传统做法按「连接频率」封禁,无法区分正常访问与爆破 —— 一旦降阈值必然误伤自己。 v1.7.0 改为按「认证是否失败」判定:密码正确永远不计数,从根本上消除误伤。
工作方式
- 隧道配置加
"service": "rdp" - 内网 Agent 为每条隧道连接分配一个独立回环源地址
127.0.0.x出站; 该地址会原样出现在 Windows 安全日志的IpAddress字段 - Agent 轮询安全日志
4625(登录失败)/4624(登录成功), 用回环地址把认证结果关联回具体连接,只回传connId、不回传 IP (IP 由公网侧自行认定,即使 Agent 被攻陷也无法伪造封禁目标) - 公网侧按
(真实IP × 隧道)计数,连续失败达阈值即封禁该 IP 对该隧道的访问
配置(公网侧 client 段)
| 字段 | 默认 | 说明 |
|---|---|---|
evidenceEnforce |
false |
false = 观察模式(只记日志不封禁);建议先观察再开启 |
maxEvidenceFails |
5 |
窗口内认证失败次数阈值 |
evidenceWindow |
600 |
计数窗口(秒) |
evidenceBanDuration |
1800 |
封禁时长(秒) |
前置条件
- 内网机为 Windows,且 RDP 启用 NLA(
UserAuthentication=1) - 开启登录失败审计:
auditpol /set /subcategory:"Logon" /failure:enable - 运行 Agent 的账号需能读取安全日志(服务账号
LocalSystem即可) service字段缺省为tcp,不影响其他类型隧道,向后兼容
专用日志行(纯 ASCII,可直接接入 fail2ban)
[EVIDENCE] RDP auth failed src=<IP> tunnel=<name> count=<n>
[EVIDENCE] IP <IP> 已封禁 <n>s(隧道: <name>,RDP 认证连续失败 <n> 次)
🛡️ 封禁加固(v1.7.1)
v1.7.1 在公网侧(client 角色)新增三项封禁增强,全部可向后兼容关闭:
- 封禁白名单 —
banWhitelist(静态 IP/CIDR 数组)+banWhitelistFrom(fail2banjail.local的ignoreip文件路径,自动解析)。白名单 IP 永不计入失败、永不封禁,杜绝误伤自己/内网。 - 封禁时长递增 — 同一 IP 反复触发时,封禁时长按
banFactor(默认 2)倍增,封顶banMax(默认 604800s = 7 天)。 - 封禁持久化 —
banStateFile把封禁状态落盘,程序重启后不丢;banEventFile写入纯 ASCII 事件行,可直接对接 fail2ban 的actionban。
配套回归测试见 test/test_v171.js(覆盖 CIDR 匹配、白名单、递增封禁、持久化、事件文件,含 v1.7.3 准入/封禁解耦用例)。
🔀 准入与封禁解耦(v1.7.3)
v1.7.1 的白名单本质是「免封名单」,v1.7.2 为了支持动态 IP 引入了 ipPolicy,
却把「允许谁来连」和「要不要封失败 IP」绑在同一个开关上 —— 设成 any 时整套封禁一起失效,
实测单日数万次爆破全部放行。v1.7.3 把两件事拆开:
| 字段 | 默认 | 作用 |
|---|---|---|
allowAnySource |
true |
是否允许任意来源 IP 接入。true = 只校验端口/协议/token;false = 仅放行白名单 IP(白名单为空时自动回退 true,避免全拒) |
enableFailBan |
false |
是否对认证失败达阈值的 IP 临时封禁。与准入完全无关,开启不会挡住任何新来源 |
兼容:两字段任一缺省时回退旧 ipPolicy("guard" = 准入不限 + 封禁开启)。
旧配置想保留封禁,加一行 "enableFailBan": true 即可,不用碰准入。
注意:升级本版本后封禁默认是关闭的,动态 IP 场景请显式配置 "enableFailBan": true。
启动日志会分别打印准入策略与封禁状态,便于确认。
📁 目录结构
nagent-remote/
├── nagent.js # 主程序(--role server|client),当前 v1.7.3
├── nagent.json # 内网端配置示例(role: server)
├── nagent-client.json # ECS 公网端配置示例(role: client)
├── ecosystem.config.json # PM2 进程配置
├── install-service.ps1 # Windows 服务安装脚本(nssm,需管理员运行)
├── nagent-deploy-ecs.html # ECS 部署可视化页
├── nagent-deploy-pc.html # PC 部署可视化页
├── test/
│ └── test_v171.js # v1.7.1 封禁加固单测
└── README.md
🚀 快速开始
1. 准备配置文件
# 内网电脑:编辑 nagent.json,修改 token 与 remoteHost(ECS 公网 IP)
# ECS 服务器:编辑 nagent-client.json,修改 token(双方 token 必须一致)
2. ECS(公网服务器)启动
node nagent.js --role client
# 或使用 PM2
pm2 start nagent.js --name nagent-client -- --role client
3. 内网电脑启动
# 先确认 nagent.json 中 remoteHost 为 ECS 公网 IP
node nagent.js --role server
# 或使用 PM2
pm2 start nagent.js --name nagent-server -- --role server
4. Windows 安装为系统服务(可选)
将 nagent.js 与本仓库的 install-service.ps1、nssm.exe 放在同一目录,
右键 install-service.ps1 → 以管理员身份运行。脚本会自动发现 node.exe、
注册并启动 nagent-server 服务,并验证隧道是否真正建立。
⚙️ 配置说明
内网端(role: server)
{
"role": "server",
"token": "双方一致的认证令牌",
"server": {
"remoteHost": "你的ECS公网IP",
"remotePort": 7000,
"tunnels": [
{ "name": "rdp", "localHost": "127.0.0.1", "localPort": 3389, "remotePort": 63389, "service": "rdp" },
{ "name": "web", "localHost": "127.0.0.1", "localPort": 80, "remotePort": 8080 }
],
"heartbeatInterval": 30000,
"reconnectInitialDelay": 2000,
"tls": { "enabled": true, "rejectUnauthorized": true, "servername": "你的证书域名" }
},
"log": { "dir": "./logs", "level": "info", "maxDays": 30 }
}
公网端(role: client)
{
"role": "client",
"token": "双方一致的认证令牌",
"client": {
"bindHost": "0.0.0.0",
"bindPort": 7000,
"maxFailures": 5,
"banDuration": 3600,
"tls": { "enabled": true, "rejectUnauthorized": true },
"maxEvidenceFails": 5,
"evidenceWindow": 600,
"evidenceBanDuration": 1800,
"evidenceEnforce": false,
"banFactor": 2,
"banMax": 604800,
"banWhitelist": [],
"banWhitelistFrom": "",
"banStateFile": "./ban-state.json",
"banEventFile": "./ban-events.log"
},
"log": { "dir": "./logs", "level": "info", "maxDays": 30 }
}
命令行参数
| 参数 | 说明 |
|---|---|
--role server |
以内网 Agent 模式运行 |
--role client |
以 ECS 服务端模式运行 |
--config <path> |
指定配置文件路径(默认 ./nagent.json) |
--version / -v |
显示版本号 |
--help / -h |
显示帮助 |
🧪 运行测试
node test/test_v171.js
🔒 安全建议
- Token 使用 32 位以上随机字符串
- ECS 安全组对隧道端口(如 63389)做 IP 白名单限制
- 建议搭配 Nginx/HAProxy 做 TLS 终止(如传输敏感数据)
- 公网端
evidenceEnforce先设false观察一段时间,确认无误后再开启执行封禁 - 把内网/自家 IP 加入
banWhitelist,避免被自己的探测误封
📜 许可证 / License
本项目基于 Apache License 2.0 开源 — 详见 LICENSE 与 NOTICE。
Copyright 2026 白志明 (Bai Zhiming)