内网穿透的几种方案

ssh 端口转发、zerotiernps 内网穿透、frp 内网穿透这几种内网穿透技术的总结。

先说最后结论,最终根据我的需求,我使用了 zerotiernps 的方案来从外部访问内网环境。

背景

需要一种方式,能够即使在外网也能像内网一样访问家里的服务,尤其是文件共享 samba 服务。

内网和外网俱为一体: 最好能使用一个地址,比如都使用 192.168.31.31,而不是在家里时使用 192.168.31.31 ,在外网时使用xxx.xxx.xxx.xxx

安全便捷: 在外网时,最好不要使用账号密码登录,也不要把服务完全暴露给外网,把家里的网络直接暴露给外网太危险了,外网有很多人在不断扫描尝试爆破服务。

在内网有一台 openwrt 设备作为设备,在外网的腾讯去上有一台有公网IP的轻量云服务器。

ssh端口转发

ssh端口转发分为本地端口转发(-L),远程端口转发(-R)和动态端口转发几种

本地端口转发和远程端口转发都比较好理解,仅作下记录,给个命令,方便后面查询。

1. 本地端口转发

用来把远程的端口服务映射到本地localhost的端口服务。一般用于远程机器上有一个服务,但是有防火墙无法直接访问,通过ssh映射到本地越过防火墙进行访问。

1
2
ssh -L 主机A:端口X:主机C:端口Z username@hostname -N
# 简单理解为:将对A:X的访问转变成对C:Z的访问

-L 表示本地端口转发。

主机A即为本地机器,可忽略。

需要注意的是,主机C可以是任何能够被主机B识别到的设备(比如说主机C所在内网的其它设备),也可以是主机B自身。
-N 表示仅做端口映射,但并不登入主机C

2. 远程端口转发

用来把远程主机的端口服务映射为本地服务。应用场景比如: 微信小程序服务端、webhook开发等。

1
2
ssh -R 主机B端口Y:主机C:主机C端口Z username@hostname
# 简单理解为:将对B:Y的访问转变成对C:Z的访问

-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 是一个非常完善的在线方案,不需要中间服务器。

  1. 注册 ZeroTier 账号,添加一个 network, 选择一个子网段,不要和 openwrt 所在的内网网段一样,并记下 network id。

  2. openwrt 输入network并启动 ZeroTier

  3. 打开 zerotier 后台,在对应的 Members 中勾选前面的对勾,批准 openwrt 加入网络。

  4. 在管理台添加 openwrt 所在内网网段的路由: Advanced AddRoutes填写如下:

    添加内网网段

    这一步主要是为了让其它加入此 network 的设备能够通过 openwrt 使用内网IP像在家里一样从外部访问

  5. iPhone端下载 ZeroTier One APP,并加入此network(此APP国区 App Store 没有,需要去美区下载),管理台批准此设备的加入

  6. 启动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 的修改步骤如下

  1. 首先先把默认配置复制一份出来,命名为回家
  2. 修改回家的配置文件,通用 -> 跳过代理, 移除 192.168.0.0/16,右上角对号保存
  3. 使用回家的配置文件,再挂上 SOCKS5 代理,不管是使用配置文件还是代理就都可以正常内网了。

注意
shadowrocket 中,即使全局路由是代理,配置文件的通用配置应该也会影响代理行为。所以在使用回家配置的情况下,全局路由为代理的情况下也是可以访问内网的,但是在默认配置的情况下,全局路由为代理的情况下,无法访问内网。

2. openwrt 重启后,无法连接内网。

这里发现是因为 openwrt 重启后,客户端的 ID 变了,而原来的 SOCKS5 和 HTTP 代理绑定的还是原来的客户端 ID ,所以无法访问内网。

而之所以在重启后客户端ID会变,是因为使用了公共的密钥校验,每次链接都会认为是一个新的客户端。

所以 openwrt 不要使用 vkey 连接,最好是在 nps 的管理台中,新增客户端并指定密钥,然后 openwrt 使用特定的密钥连接。这样即使重启,客户端ID也不会发生变化了。

总结

其实可能大多数人都不太需要把内网的服务直接暴露到外网,既不安全,也不方便,他们需要的可能更多是一种从外网访问内网的方式。目前来看,nps 是最好的方案,但是最好也部署一个 ZeroTier 作为 nps 不可用时的容灾方案。

参考链接