nps 搭建内网穿透隧道教程及可能的问题分析
本文介绍
- 内网穿透原理
- nps 搭建 cs 模式的 ssh 中转隧道
- nps 搭建 p2p 模式的 ssh 隧道
我所使用的为:
- 一台带公网的云服务器,作为 server
- 一台 Ubuntu 虚拟机,作为被控端 client1
- 一台 windows10 主机,作为访问端 client2
(但是,由于 NAT 类型不支持,p2p 的穿透失败了,但是过程是对的,后续会补上 p2p 的成功教程)
内网穿透原理
先介绍一下 nps
nps 是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持 tcp、udp 流量转发,可支持任何 tcp、udp 上层协议(访问内网网站、本地支付接口调试、ssh 访问、远程桌面,内网 dns 解析等等……),此外还支持内网 http 代理、内网 socks5 代理、p2p 等,并带有功能强大的 web 管理端。
官方网站
内网穿透原理
内网穿透是一种将内部网络中的服务暴露到公网上的技术,其原理是通过一个中间代理服务器,将公网请求转发到内网中的指定服务上,从而实现内网服务的对外访问。
具体来说,内网穿透的原理如下:
- 在内网中部署一个客户端程序,该程序会与内网中的服务建立连接,并将服务的请求转发到代理服务器上。
- 在公网中部署一个服务器程序,该程序会接收来自代理服务器的请求,并将请求转发到内网中的客户端程序上。
- 当公网用户访问内网服务时,请求会先发送到公网服务器,然后由公网服务器将请求转发到内网中的客户端程序上,最终客户端程序将请求转发到内网服务上,并将服务的响应返回给公网用户。
因此,内网穿透分为 CS 模式和 P2P 模式两种
CS 模式所有流量通过公网服务器代理,比较稳定,但是带宽受服务器的限制
P2P 模式公网服务器仅做连接的媒介,不稳定,但是带宽仅受客户端限制,很快
但是,p2p 模式是否成功会跟 NAT 的模式有很大关系
在 STUN 标准中,NAT 模式有 4 种:
- Full Cone NAT(完全锥型 NAT)
所有从同一个私网 IP 地址和端口(IP1:Port1)发送过来的请求都会被映射成同一个公网 IP 地址和端口(IP:Port)。并且,任何外部主机通过向映射的公网 IP 地址和端口发送报文,都可以实现和内部主机进行通信。
这是一种比较宽松的策略,只要建立了私网 IP 地址和端口与公网 IP 地址和端口的映射关系,所有的 Internet 上的主机都可以访问该 NAT 之后的主机。 - Restricted Cone NAT(限制锥型 NAT)
所有从同一个私网 IP 地址和端口(IP1:Port1)发送过来的请求都会被映射成同一个公网 IP 和端口号(IP:Port)。与完全锥型 NAT 不同的是,当且仅当内部主机之前已经向公网主机发送过报文,此时公网主机才能向私网主机发送报文。 - Port Restricted Cone NAT(端口限制锥型 NAT)
与限制锥型 NAT 很相似,只不过它包括端口号。也就是说,一台公网主机(IP2:Port2)想给私网主机发送报文,必须是这台私网主机先前已经给这个 IP 地址和端口发送过报文。 - Symmetric NAT(对称 NAT)
所有从同一个私网 IP 地址和端口发送到一个特定的目的 IP 地址和端口的请求,都会被映射到同一个 IP 地址和端口。如果同一台主机使用相同的源地址和端口号发送报文,但是发往不同的目的地,NAT 将会使用不同的映射。此外,只有收到数据的公网主机才可以反过来向私网主机发送报文。
这和端口限制锥型 NAT 不同,端口限制锥型 NAT 是所有请求映射到相同的公网 IP 地址和端口,而对称 NAT 是不同的请求有不同的映射。
也就是说,要实现 p2p 模式,两个客户端不能都是对称型 NAT, 否则不能成功
然而我所在的校园网都是对称性 NAT, p2p 实验没法成功 (开专!输!)
检测 NAT 的方法可以看文章末尾
下载并启动 nps
通过上面的原理可知,我们需要一台有公网的服务器 (安装 nps), 和若干个客户端 (npc)
下文我们称服务器为 server, 被控客户端为 client1, 访问端为 client2.
即我在 client2 上通过 server 建立 p2p 连接,访问到 client1
注意: nps 和 npc 分别是在服务端 server 和客户端 client 上安装,使用命令时要注意拼写
下载
首先服务端 server 下载并解压 nps
1 | wget https://github.com/ehang-io/nps/releases/download/v0.26.10/linux_amd64_server.tar.gz |
客户端 client 下载 npc
推荐使用 docker 的方式
1 | docker pull ffdfgdfg/npc |
当然,也可以自己手动下载,如下
Linux 平台
1 | wget https://github.com/ehang-io/nps/releases/download/v0.26.10/linux_amd64_client.tar.gz |
windows 平台自己下载解压,点击这里下载
启动 nps
首先进入 nps 目录编辑配置
1 | sudo vim /etc/nps/conf/nps.conf |
示例如下
其中,需要修改的地方如下,我都留成 TODO 了
- http_proxy_port (我的是 7997, 防止冲突)
- https_proxy_port (我的是 7998)
- bridge_port (我开放的是 8000)
- public_vkey
- p2p_ip (如果要用 p2p 服务的话)
- p2p_port
- web_username
- web_password
- web_port (我开的是 7999)
1 | appname = nps |
PS: 记得开端口,既要在服务器防火墙上设置开启,又要在服务器的供应商那里开放安全组
服务端直接执行如下启动
1 | sudo nps start |
然后执行
1 | sudo cat /var/log/nps.log |
可以看到如下的这种就成功了
搭建隧道
CS 模式
CS 模式时,client 通过 server 当桥梁,流量都经过 server 中转.
也就是,在访问端 client2 通过访问 <服务器IP>:<端口>
即可定向到被控端 client1
我们使用 docker 容器的方式安装,毕竟相对方便
首先在 client1, 即被控端,找个地方建立一个文件夹 conf, 比如我是在 ~/test/conf
1 | mkdir conf |
在客户端 client1 的配置文件 npc.conf 修改如下
其中注意
- server_addr 是自己服务器 server 的访问 IP 和端口,比如我的是 8000
- vkey 要修改成一个独立不重复的,这是客户端的 key
- target 为客户端 client1 上你要访问的目的端口,一般而言就是 localhost:22 表示 ssh 连接
- server_port 为我们访问服务器的端口,会定向到 client1 去
1 | [common] |
访问端,也就是 client2, 无需配置
接着在 nps 的 web 控制面板添加一个客户端
验证密钥要和 client1 的一样,下面的加密和压缩都填是,如下
然后在 client1 上,通过 docker 容器启动 npc (记得修改本机的 conf 目录为刚刚创建的配置目录)
1 | docker run -d --name npc --net=host -v <本机conf目录>:/conf ffdfgdfg/npc -config=/conf/npc.conf |
比如我在 user 用户目录下的 test 挂载,就是如下
1 | docker run -d --name npc --net=host -v /home/user/test/conf:/conf ffdfgdfg/npc -config=/conf/npc.conf |
此时在 server 面板上可以看到如下已连接
然后在访问端 client2 执行如下
1 | ssh -p <PORT> username@IP |
比如我的 port 是 5500, IP 填服务器公网 IP (也可以是域名)
如下,访问成功
p2p 模式
p2p 的原理是客户端 client1 和 server 保持长期连接,我们通过另一个客户端 client2 去访问 server 对应端口,并使用独特的密码标识,就可以通过 server 打通两个客户端的隧道,就可以访问到 client1
我们还是使用 docker 容器的方式安装,毕竟相对方便
首先在 client1, 即被控端,找个地方建立一个文件夹 conf, 比如我是在 ~/test/conf
1 | mkdir conf |
在客户端 client1 的配置文件 npc.conf 修改如下
其中注意
- server_addr 是自己服务器 server 的访问 IP 和端口,比如我的是 8000
- vkey 要修改成一个独立不重复的,这是客户端的 key
- p2p 的 password 也要修改成独立不重复的,自定义
- target 为客户端 client1 上你要访问的目的端口,一般而言就是 localhost:22 表示 ssh 连接
1 | [common] |
在访问端,也就是 client2, 配置文件如下
要注意的也和上面一样,此外多了一个 local_port, 这个就是通过 client2 访问时,client2 上的端口,自定义即可 (不指定也行,默认为 2000)
1 | [common] |
接着在 nps 的 web 控制面板添加一个客户端
验证密钥要和 client1 的一样,下面的加密和压缩都填是,如下
然后在 client1 上,通过 docker 容器启动 (记得修改本机的 conf 目录为刚刚创建的配置目录)
1 | docker run -d --name npc --net=host -v <本机conf目录>:/conf ffdfgdfg/npc -config=/conf/npc.conf |
比如我在 user 用户目录下的 test 挂载,就是如下
1 | docker run -d --name npc --net=host -v /home/user/test/conf:/conf ffdfgdfg/npc -config=/conf/npc.conf |
此时在 server 面板上可以看到如下已连接
然后在访问端 client2 的 npc 的目录,执行如下命令即可
1 | ./npc.exe -server=<IP>:<Port> -vkey=testubt -type=tcp -password=pwdforssh -target=localhost:22 |
不出意外,此时应该会成功启动,显示内容参考如下
2023/04/30 20:38:59.274 [I] [npc.go:231] the version of client is 0.26.10, the core version of client is 0.26.0
2023/04/30 20:38:59.317 [I] [control.go:97] Loading configuration file C:\Users\LENOVO\Desktop\windows_amd64_client\conf\npc.conf successfully
2023/04/30 20:38:59.391 [N] [control.go:174] web access login username:user password:testubt
2023/04/30 20:38:59.394 [I] [local.go:115] successful start-up of local tcp monitoring, port 2999
2023/04/30 20:38:59.444 [I] [client.go:72] Successful connection with server 117.50.182.150:8000
2023/04/30 20:39:00.392 [N] [local.go:142] try to connect to the server 1
2023/04/30 20:39:28.829 [D] [local.go:117] new p2p connection
此时,再在访问端 client2 执行如下命令
1 | ssh -p 2999 root@127.0.0.1 |
即可成功连接 client1 (我的因为 NAT 不支持,没成功)
debug 过程
port or host may have been occupied
p2p 模式启动时,访问端 client2 显示如下
[control.go:158] The server returned an error, which port or host may have been occupied or not allowed to open. ssh_p2pmode
原因是端口没开
此处注意要开放 2 个地方,在系统防火墙里开端口,还要去服务器供应商那里开安全组的端口
connection refused
客户端无法连接到 nps, 连接被拒
我们通过执行如下命令
sudo cat /var/log/nps.log
查看 nps 的日志文件
可以看到,端口已被使用,需要换一下端口,改一下 nps 配置文件即可
read session unpack from connection
客户端出现如下错误
mux: read session unpack from connection err read tcp 198.18.0.1:50290-><ip>:8000: use of closed network connection
原因是客户端的配置文件出错
查看服务器日志可以发现如下,这里是 key 没有唯一,所以认证出错,网络被关闭.
改一下认证 key 即可
invalid syntax
客户端启动报错如下
2023/04/27 03:09:38.334 [E] [control.go:290] strconv.Atoi: parsing "": invalid syntax
2023/04/27 03:09:38.334 [E] [control.go:310] strconv.Atoi: parsing "": invalid syntax
2023/04/27 03:09:38.334 [E] [local.go:206] strconv.Atoi: parsing "": invalid syntax
2023/04/27 03:09:38.335 [N] [local.go:142] try to connect to the server 2
2023/04/27 03:09:38.334 [E] [control.go:290] strconv.Atoi: parsing "": invalid syntax
2023/04/27 03:09:38.336 [E] [control.go:310] strconv.Atoi: parsing "": invalid syntax
2023/04/27 03:09:38.336 [E] [client.go:126] strconv.Atoi: parsing "": invalid syntax
2023/04/27 03:09:38.388 [E] [control.go:290] strconv.Atoi: parsing "": invalid syntax
原因是配置文件错误,多余了或者少了,删除无用条目即可
(官方文件这里真的不人性化,给的配置过多了,不用得删)
NAT 类型不支持
如下,在解决好一切艰难险阻之后,仍然出问题了,访问端 client2 显示如下
maybe the nat type is not support p2p
说明我们的 NAT 类型不支持
Linux 查看 NAT 类型可以使用如下命令
1 | sudo apt-get install stun |
我的显示如下,0x000012 类型就是 NAT4, 即 symmetric NAT, 很垃圾,不能 p2p 穿透
STUN client version 0.97
Primary: Independent Mapping, Independent Filter, random port, no hairpin
Return value is 0x000012
为什么 NAT4 不支持 p2p?
因为 NAT4 每次建立新的连接时 IP 和端口都会变化,而 p2p 建立连接之前是通过 server 通信,server 会告诉客户端另一个客户端的地址.
而建立 p2p 连接时,此时就又算另一个连接了,如果是 NAT4, 新建立 p2p 连接时地址和端口就对不上之前 server 告诉的地址和端口,于是失败
参考文章
https://ehang-io.github.io/nps/
https://hub.docker.com/r/ffdfgdfg/npc
https://info.support.huawei.com/info-finder/encyclopedia/zh/NAT.html