Node.js 实现反向隧道/端口转发
使用 Node.js 应用进行反向端口转发/隧道连接到没有公网 IP 的 PC
引言
一个应用程序,允许您转发位于没有公网 IP 的网络内的某些机器的特定端口。
背景
我时不时需要通过 RDP、SSH、代理等方式连接到私有网络内的几台机器。可惜,TeamViewer、Hamachi、SSH 隧道、VPN 等工具都被禁用了……
所以我决定构建一个 Node.js 应用来满足我的需求。
Using the Code
本地运行
我将从运行应用程序和进行一些简单的尝试所需的最少操作开始。
先决条件:Node.js (测试过 8 版本),git 客户端
git clone https://github.com/mgrybyk/node-tunnel.git
cd node-tunnel
npm install
现在,让我们创建一个最小化的配置文件。我将提供两个示例:一个用于有SSH的人,另一个用于有浏览器的人。 :)
创建一个 .env 文件(完整的文件名是 ".env",不是扩展名!),内容如下:
注意:".env" 文件应在项目根文件夹中创建,本例中是:node-tunnel
用于SSH(您可以根据需要更改主机/端口到任何其他值)
N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=22
N_T_CLIENT_PORT=2222
用于http(您可以根据需要更改主机/端口到任何其他值;由于证书等问题,网站很可能无法正常工作,但这足以作为示例)
N_T_AGENT_DATA_HOST=inplainsite.org
N_T_AGENT_DATA_PORT=80
N_T_CLIENT_PORT=8000
我们已经准备好启动它了!启动三个终端窗口,因为我们需要启动 3 个 node 实例,然后运行:
node server
node agent
node client
如果您选择了SSH方式,请连接到 localhost:2222 -> ssh -p 2222 localhost
如果您选择了http方式,请打开浏览器访问 localhost:8000
是的!这是一个最小化的工作示例。您的所有流量都通过 client->server->agent 并返回。
实际案例,两台 PC
在这个例子中,我们面临这样的情况。您有一台拥有公网 IP 的 PC,另一台没有。目标 - 通过 SSH/RDP/其他方式连接到它。
让我们先在每台机器上克隆仓库并安装模块。
现在,转到远程 PC 并创建一个 .env 文件。在此示例中,我使用了 RDP 端口,您可以随意更改为任何其他端口。
N_T_SERVER_HOST=ip of machine with public ip
N_T_SERVER_PORT=1337
N_T_SERVER_PORTS_FROM=1338
N_T_SERVER_PORTS_TO=1340
N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=3389
现在,在此处启动 agent:node agent
切换到本地 PC,并创建一个 .env 文件
N_T_SERVER_HOST=localhost
N_T_SERVER_PORT=1337
N_T_SERVER_PORTS_FROM=1338
N_T_SERVER_PORTS_TO=1340
N_T_CLIENT_PORT=8000
现在,在此处启动 server 和 client
node server
node client
完成后,您可以通过 RPD 客户端连接到 localhost:8000,这将打开到远程 PC 的连接。
实际案例,两台 PC 和一台服务器
这看起来很棒,但如果您的本地 PC 没有公网 IP 怎么办?您必须通过一台拥有公网 IP 的 PC 来转发所有流量。如果您没有这样的 PC,您可以在 AWS 上创建一个免费的容器。
我们开始吧!照常,在每台机器上安装 node.js,克隆仓库并安装模块。
转到拥有公网 IP 的 PC(服务器),并在那里创建一个 .env 文件
N_T_SERVER_HOST=localhost
N_T_SERVER_PORT=1337
N_T_SERVER_PORTS_FROM=1338
N_T_SERVER_PORTS_TO=1340
很好,运行服务器:node server
在远程 PC 上,创建一个 .env 文件。在此示例中,我使用了 SSH 端口,但您可以将主机/端口更改为任何您想要的值。此外,我将为 agent 指定一个名称(应与 client 的名称匹配)。
N_T_SERVER_HOST=ip of machine with public ip
N_T_SERVER_PORT=1337
N_T_AGENT_DATA_HOST=localhost
N_T_AGENT_DATA_PORT=22
N_T_AGENT_NAME=test-ssh
准备启动 agent:node agent
切换到客户端 PC 并创建一个 .env 文件。客户端名称应与 agent 名称匹配。
N_T_SERVER_HOST=ip of machine with public ip
N_T_SERVER_PORT=1337
N_T_CLIENT_PORT=8000
N_T_CLIENT_NAME=test-ssh
最后,启动 client:node client
太棒了。现在我们可以打开到 localhost:8000 的 SSH 连接,这将打开到远程 PC 的 SSH 连接。
我们已经通过服务器机器创建了隧道,正如我们之前所做的那样。所有数据都如下传输:
ssh 客户端 -> client -> server -> agent -> ssh 服务器
然后返回:ssh 服务器 -> agent -> server -> client -> ssh 客户端
agent 和 client 之间没有直接连接。
更多 Agents 和 Clients
多个 agents 和 clients 可以通过一个 server 运行。例如:
您可以运行一个 agent 来处理 SSH,另一个来处理 RDP。请注意,每个 agent 都应该有一个名称 (N_T_AGENT_NAME
)。
每个 agent 可以与多个 client 一起工作,因此您可以运行一个 client 在您的机器上,其他 client 可以连接到特定的 agent。不要忘记通过提供名称 (N_T_CLIENT_NAME
) 来指定 client 应该使用哪个 agent。
多个 .env 文件
如果您需要运行多个 agents/clients/servers,您可以创建多个 .env 文件,例如:
.env.rdp
.env.ssh
.env.test
有了这些.env.*文件,您可以通过将 .env 文件名作为参数来启动 server/client/agent。
node server .env.rdp
node agent .env.ssh
node client .env.test
注意:".env" 文件应在项目根文件夹中创建,本例中是:node-tunnel。
它是如何工作的?
核心部分在此:Net,它允许创建基于流的 TCP 服务器/客户端和流 管道。
为了将数据从一个 socket 转发到另一个 socket 并返回,我简单地像这样管道它们:
agentSocket.pipe(clientSocket)
clientSocket.pipe(agentSocket)
让我们来看这个例子:
在 SSH 的例子中,发生以下情况:
- SSH 客户端连接到 client(它正在监听某个端口)
- Client 将所有数据转发给 server
- Server 知道需要将数据转发给特定的 agent
- Agent 打开到 SSH 服务器的连接,并将数据从 server 转发给它
来自 SSH 服务器的响应会返回到 agent、server、client,最终到达 SSH 客户端。
让我尝试解释应用程序的每个部分的作用。
服务器
- 默认情况下,server 监听 clients 和 agents 的连接。
- 一旦新的 agent 连接,server 就会为它创建一个专用服务器。
- 一旦新的 client 连接,server 会通知它有一个专用的 agent 服务器及其端口。
- 为了开始数据转发,client 和 agent 的名称必须匹配,不允许存在同名的 agent。
- 可以存在多个 agents,并且可以存在每个 agent 的多个 client(可以存在大量同名的 client)。
专用的 agent 服务器行为如下:
- 当 client 服务器有新连接时,会打开一个到 agent 专用服务器的新连接。
- 一旦 client 连接,就会向 agent 发送通知,以便它可以连接到服务器。
与此同时:client socket 被存储起来,等待 agent socket。 - 一旦 agent 连接,专用服务器会将 agent 和 client 相互管道,并移除 client 和 agent sockets 的数据监听器。
- 专用服务器向 client 发送通知,表明管道已创建,我们已准备就绪。
- 完成!
客户端
Client 创建一个服务器,并在 .env 文件中提供的端口上监听传入的连接。
新连接时,client 开始将数据转发给 server(详见 server 部分)。
Agent
Agent 等待关于 client 连接的通知。当发生这种情况时,agent 会连接到 .env 文件中指定的 host:port
并将其转发给 server。
下一步?
加密!目前数据未加密,对于 SSH、RDP 来说这不是问题,但对于明文协议来说是个问题。
目前只有服务消息被加密。
当然,修复一些缺陷,清理代码,提高稳定性。
如果您有任何想法,请随时分享。 :)
总结
感谢阅读!
我希望这或多或少有趣,或多或少易于理解,甚至可能有用。 :)
关注点
处理管道时,一个有用的模块可能是 though2。
使用此模块,您可以将数据通过您的 worker 进行处理,并执行诸如:日志记录、错误处理或更改数据,例如将某个关键字(ebay)更改为另一个关键字(amazon),或者将所有 0 更改为 1。 :)
历史
- 2017 年 7 月 8 日:初始版本,修复了一些拼写错误