内网穿透的几种方案
ssh
端口转发、zerotier
、nps
内网穿透、frp
内网穿透这几种内网穿透技术的总结。
先说最后结论,最终根据我的需求,我使用了 zerotier
和 nps
的方案来从外部访问内网环境。
背景
需要一种方式,能够即使在外网也能像内网一样访问家里的服务,尤其是文件共享 samba
服务。
内网和外网俱为一体: 最好能使用一个地址,比如都使用 192.168.31.31
,而不是在家里时使用 192.168.31.31
,在外网时使用xxx.xxx.xxx.xxx
。
安全便捷: 在外网时,最好不要使用账号密码登录,也不要把服务完全暴露给外网,把家里的网络直接暴露给外网太危险了,外网有很多人在不断扫描尝试爆破服务。
在内网有一台 openwrt
设备作为设备,在外网的腾讯去上有一台有公网IP的轻量云服务器。
ssh端口转发
ssh端口转发分为本地端口转发(-L),远程端口转发(-R)和动态端口转发几种
本地端口转发和远程端口转发都比较好理解,仅作下记录,给个命令,方便后面查询。
1. 本地端口转发
用来把远程的端口服务映射到本地localhost的端口服务。一般用于远程机器上有一个服务,但是有防火墙无法直接访问,通过ssh映射到本地越过防火墙进行访问。
1 | ssh -L 主机A:端口X:主机C:端口Z username@hostname -N |
-L
表示本地端口转发。
主机A即为本地机器,可忽略。
需要注意的是,主机C可以是任何能够被主机B识别到的设备(比如说主机C所在内网的其它设备),也可以是主机B自身。-N
表示仅做端口映射,但并不登入主机C
2. 远程端口转发
用来把远程主机的端口服务映射为本地服务。应用场景比如: 微信小程序服务端、webhook开发等。
1 | ssh -R 主机B端口Y:主机C:主机C端口Z username@hostname |
-L
表示远程端口转发
主机B为远程主机
需要注意的是, 主机C可以是任何能够被主机A识别到的设备(比如说主机C所在内网的其它设备),也可以是主机A自身。
3. 动态端口转发
动态端口转发可以把本地主机A上运行的SSH客户端转变成一个SOCKS代理服务器;实际上它是一种特殊的本地端口转发,或者说叫它「动态本地端口转发」更科学。这个动态,就动在这种转发不规定目标地址(主机C)和目标端口(端口Z),而是去读取应用发起的请求,从请求中获取目标信息。
1 | ssh -D 本地网卡地址:本地端口 username@hostname -N |
-D
表示动态转发本地网卡地址
是本地主机,可忽略本地端口
是 SOCKS5 代理端口-N
表示这个 SSH 连接只进行端口转发,不登录远程 Shell,不能执行远程命令,只能充当隧道。
举例来说,如果本地端口是2121,那么动态转发的命令就是下面这样。
1 | ssh -D 2121 username@hostname -N |
注意,这种转发采用了 SOCKS5 协议。访问外部网站时,需要把 HTTP 请求转成 SOCKS5 协议,才能把本地端口的请求转发出去。
可以在系统或应用(浏览器等)中设置一个使用SOCKS5协议、服务器为localhost、端口为X的代理,利用代理使请求走端口X。
下面是 SSH 隧道建立后的一个使用实例。
1 | curl -x socks5://localhost:2121 http://www.example.com |
上面命令中,curl 的 -x
参数指定代理服务器,即通过 SOCKS5 协议的本地2121端口,访问 http://www.example.com
。
如果经常使用动态转发,可以将设置写入 SSH 客户端的用户个人配置文件(~/.ssh/config
)。
1 | DynamicForward tunnel-host:local-port |
4. 总结
结合 ssh 的本地转发和远程转发,就可以成功在外网环境下,通过外网的中间服务器和内网的openwrt服务访问家里的所有内网服务。
之所以没有使用这种方式是因为,这种端口映射的方案,最终访问的是本地的端口,并不能做到内外俱为一体的效果,而且在移动端设备上似乎也没有比较好的免费客户端来满足需求。
ZeroTier
ZeroTier
是一个非常完善的在线方案,不需要中间服务器。
注册
ZeroTier
账号,添加一个 network, 选择一个子网段,不要和openwrt
所在的内网网段一样,并记下 network id。openwrt
输入network并启动ZeroTier
。打开
zerotier
后台,在对应的Members
中勾选前面的对勾,批准openwrt
加入网络。在管理台添加
openwrt
所在内网网段的路由: Advanced AddRoutes填写如下:这一步主要是为了让其它加入此 network 的设备能够通过
openwrt
使用内网IP像在家里一样从外部访问iPhone端下载
ZeroTier One
APP,并加入此network(此APP国区 App Store 没有,需要去美区下载),管理台批准此设备的加入启动APP进入网络,此时会有VPN启动,这时就可以使用
92.168.1.x
的内网IP访问家里的服务了。
这样能完美契合我的需求,但美中不足的是,ZeroTier
的服务可能在国外,比较慢,使用起来体验并不好。虽然 ZerotTier
可以建立自定义 Moon
但需要中间服务器并且 ZeroTier One
APP并不支持连接 Moon 服务,所以并没有什么好的解决方案。
frp内网穿透
frp主要就是用于把内网服务映射到公网上,针对外网访问提供了鉴权的能力。
虽然也有 安全地暴露内网服务 的方案,但是需要访问端安装客户端。而客户端目前也仅支持(MacOS、Linux和Windows)三端,不支持移动端,而我在外网的访问场景主要是在移动端。
由于不符合需求和使用场景,所以只是看了一下文档,没有仔细研究。
nps内网穿透
相对于 frp
来说, nps
更符合我的使用场景,它支持通过 HTTP 和 SOCKS5 代理使用 内肉地址 来访问内网环境,使用步骤按官方文档一步一步来即可,需要中间服务器。但是在使用的过程踩了一些坑,标记一下。
1. 挂上 SOCKS5 代理后,无法访问内网,即使开全局模式也不行。
使用 shadowrocket
应用连接 SOCKS5 代理后,无法访问内网,即使开了全局模式也无法访问内网。
其实遇到这个问题心里有个大概猜测,因为是 192 开头的内网IP,一般的代理软件都会刻意忽略内网IP不走代理。
那么这里需要修改默认配置,让192开头的内网IP也能走 SOCKS5 代理。
shadowrocket
的修改步骤如下
- 首先先把默认配置复制一份出来,命名为回家
- 修改回家的配置文件,通用 -> 跳过代理, 移除
192.168.0.0/16
,右上角对号保存 - 使用回家的配置文件,再挂上 SOCKS5 代理,不管是使用配置文件还是代理就都可以正常内网了。
注意
在 shadowrocket
中,即使全局路由是代理,配置文件的通用配置应该也会影响代理行为。所以在使用回家配置的情况下,全局路由为代理的情况下也是可以访问内网的,但是在默认配置的情况下,全局路由为代理的情况下,无法访问内网。
2. openwrt 重启后,无法连接内网。
这里发现是因为 openwrt 重启后,客户端的 ID 变了,而原来的 SOCKS5 和 HTTP 代理绑定的还是原来的客户端 ID ,所以无法访问内网。
而之所以在重启后客户端ID会变,是因为使用了公共的密钥校验,每次链接都会认为是一个新的客户端。
所以 openwrt
不要使用 vkey
连接,最好是在 nps 的管理台中,新增客户端并指定密钥,然后 openwrt
使用特定的密钥连接。这样即使重启,客户端ID也不会发生变化了。
总结
其实可能大多数人都不太需要把内网的服务直接暴露到外网,既不安全,也不方便,他们需要的可能更多是一种从外网访问内网的方式。目前来看,nps
是最好的方案,但是最好也部署一个 ZeroTier
作为 nps 不可用时的容灾方案。