在Linux容器中使用ZeroTier P2P VPN

关于 ZeroTier P2P VPN

P2P VPN 和我们平常使用的 PPTP、OpenVPN 等 VPN 的不同之处在于其只负责将两个或多个主机通过点对点的方式进行连接,不需要中心服务器支持,多个主机之间通过 STUN 打洞或者 TURN 中继等方式进行连接,比较适合不同网络间的多个主机进行直接连接,不像 PPTP 这种在国内用的最多的就是拿来爬墙的。

目前使用的比较多的包括 n2n、tinc 和本文要介绍的 ZeroTier 等几种免费开源的 P2P VPN 软件。

n2n 由于已经没人继续开发支持了所以不予考虑,我在对比了 tinc 和 ZeroTier 之后发现 ZeroTier 虽然看起来性能比 tinc 略差一点,但其客户端配置步骤非常简单,不需要像 tinc 那样要在多个主机分别手工生成配置文件,而且 ZeroTier 自己也自带了几个不同国家区域的中继服务器方便普通用户使用,另外一点就是本文要介绍的 ZeroTier 支持在 Linux 容器中使用,因此平时我主要使用 ZeroTier,tinc 用作辅助。

ZeroTier 的虚拟网络可以包含多个处于相同或不同物理网络里的主机,每个虚拟网络都有一个唯一的网络 ID,多个运行 ZeroTier 服务的主机加入同一个 ZeroTier 网络 ID 就可以构成一个虚拟网络。虚拟网络都是由 ZeroTier Controller 控制器来管理的,默认可以使用 ZeroTier 提供的控制器来管理,用户也可以自行搭建控制器来实现完全自己管理 ZeroTier 网络。

另外 ZeroTier 有一个特殊的 8056c2e21c000001 网络 ID 表示全球公共 ZeroTier 网络,如果只是想测试连接网络的话也可以加入这个公共网络;但实际使用中为了安全性考虑建议最好在 ZeroTier 网站上注册一个账户并新建你自己的 P2P VPN 网络用于管理。

有关 ZeroTier P2P VPN 的详细介绍可以参考其官网:

https://www.zerotier.com/

容器中使用 ZeroTier 的问题

简单了解 ZeroTier P2P VPN 主机端的工作机制之后我就发现一个问题,其依赖的 TUN/TAP 驱动(n2n 和 tinc VPN 也同样依赖)在很多 Linux 容器系统中是无法使用的。

我使用的 OpenVZ VPS 容器还好还可以在控制面板里开启 TUN/TAP 支持,而对于现在非常流行的 Docker 容器(例如之前我用到的 IBM Bluemix 容器)就比较麻烦了,Docker 容器在运行时必须包含 SYS_NET_ADMIN 特权才能正常执行 TUN/TAP 虚拟网卡设备的创建操作(Docker 容器中同样也不能使用 OpenVPN、OpenConnect 等依赖 TUN/TAP 驱动的程序),但目前的容器服务厂商基本上不会以特权方式运行容器。

ZeroTier 为此提出的解决方案就是 Network Containers 模式,通过实现一个轻量级的应用层 TCP/IP API 来 hook 现有应用程序的 Socket API,这样可以使现有的程序也能使用 ZeroTier 技术连接其它主机。

最近 ZeroTier 已经将 Network Containers 模式更名为 ZeroTier SDK 并使用了单独的版本库,开发者也可以选择将 ZeroTier SDK 直接编译进自己的应用程序以方便直接使用。有关容器模式的详细介绍可以参考官方介绍文章:ZeroTier Network Containers

关于 Codeanywhere 容器

这里我使用 Codeanywhere 虚拟机来测试 ZeroTier Network Containers,Codeanywhere 作为一个云端 IDE 平台还是比较好用的,其提供的免费版本的容器支持:

  • 2GB 磁盘空间;
  • 256MB RAM (+ 512 MB 交换空间);
  • sudo 支持;
  • 支持 SSH 登录(免费版本的容器每次启动时 SSH 端口不固定);
  • 支持访问所有 HTTP 和 Websocket 端口(默认 1024 - 9999)。

问题是 Codeanywhere 免费版本的虚拟机不支持固定 IP 地址,也不支持域名绑定或者固定子域名,每次虚拟机启动之后分配到的都是一个超长的子域名,例如你看到的子域名可能就是下面的形式:

preview.7reszf59tfv18aor9x7odheaio340a4ih8tcn8fvndzvkj4i.box.codeanywhere.com

这样实际开发测试起来还是有点不太方便。

当然 Codeanywhere 也是不提供 TUN/TAP 支持的,这样如果 ZeroTier 的容器模式可以在 Codeanywhere 虚拟机内正常使用,我就可以直接在自己的电脑上直接使用虚拟的 P2P VPN 节点 IP 地址来访问 Codeanywhere 虚拟机了,可以省掉那一长串恶心的子域名了。

使用 ZeroTier Network Containers

编译安装 ZeroTier Network Containers

首先创建一个新的 Codeanywhere 虚拟机,建议使用 C/C++ Development Stack 类型方便后续编译安装 ZeroTier 程序,另外操作系统最好选择 Centos 6.5 64 位,如果使用 Ubuntu 系统可能存在连接不上 P2P VPN 网络的问题。

在 Codeanywhere 虚拟机中使用 git 命令检出 ZeroTier 官方版本库,检出之后运行 make netcon 命令就可以编译出 ZeroTier Network Containers 模式需要的文件了:

[cabox@box-codeanywhere ZeroTierOne]$ ls                                           
AUTHORS.md   examples                 netcon        ui
LICENSE.txt  ext                      node          version.h
Makefile     include                  objects.mk    windows
README.md    java                     one.cpp       world
artwork      libzerotierintercept.so  osdep         zerotier-cli
attic        make-freebsd.mk          selftest.cpp  zerotier-idtool
cluster-geo  make-linux.mk            service       zerotier-netcon-service
controller   make-mac.mk              tcp-proxy

其中最重要的就是 zerotier-netcon-service 服务和 libzerotierintercept.so 以及 netcon 目录中的 liblwip.so 应用层 Socket API 库文件了,接着可以先将 ZeroTier Network Container 模式程序和有关库文件安装到 /var/lib/zerotier-one 目录中:

[cabox@box-codeanywhere ZeroTierOne]$ mkdir -m /var/lib/zerotier-one
[cabox@box-codeanywhere ZeroTierOne]$ cp -ra zerotier-* /var/lib/zerotier-one
[cabox@box-codeanywhere ZeroTierOne]$ cp libzerotierintercept.so netcon/liblwip.so /var/lib/zerotier-one

将容器主机加入 ZeroTier 虚拟网络

下面就可以在 Codeanywhere 虚拟机中进行测试了,首先启动 Network Container 模式下的 ZeroTier 主服务程序:

[cabox@box-codeanywhere workspace]$ /var/lib/zerotier-one/zerotier-netcon-service -d

服务启动成功之后稍等片刻,运行 zerotier-cli 命令加入现有的 ZeroTier 虚拟网络,下面命令行中的 565799d8f61077e9 就是我在 ZeroTier 网站上新创建的虚拟网络 ID:

[cabox@box-codeanywhere workspace]$ /var/lib/zerotier-one/zerotier-cli join 565799d8f61077e9

提示

如果你加入的是 8056c2e21c000001 这个全球公共 ZeroTier 虚拟网络那就可以省掉下面这一段落的配置步骤了。

join 操作完成之后需要在 ZeroTier 网站后台虚拟网络管理界面中先允许新的主机加入此网络,接着可以为此主机配置虚拟网络上的 IP 地址、子网掩码之类的,具体配置操作步骤还算比较简单基本都可以在 ZeroTier Central 网站后台中完成。

配置完成之后就可以在容器中继续运行 zerotier-cli 命令查询该主机上 ZeroTier 虚拟网络状态:

[cabox@box-codeanywhere workspace]$ /var/lib/zerotier-one/zerotier-cli listnetworks
200 listnetworks <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>
200 listnetworks 565799d8f61077e9 zohead-network fa:d9:cf:a8:b2:40 OK PRIVATE /var/lib/zerotier-one/nc_565799d8f61077e9 10.10.8.4/24

可以看到该容器中已经正确连接到我建立的私有 ZeroTier 虚拟网络了,而且分配到了一个 10.10.8.4 的 IP 地址,子网掩码为 255.255.255.0(对应 24)。

通过虚拟网络访问容器主机

此时需要特别注意的是容器中运行的程序还不能直接使用 ZeroTier 虚拟网络与其它主机进行连接,必须通过预加载 Network Container 库文件的方式运行程序才可以在容器系统中正确访问 ZeroTier 虚拟网络,这里我们以 SSH 服务器为例,首先可以新运行一个 sshd 服务程序:

[cabox@box-codeanywhere workspace]$ sudo ZT_NC_NETWORK=/var/lib/zerotier-one/nc_565799d8f61077e9 LD_PRELOAD=/var/lib/zerotier-one/libzerotierintercept.so /usr/sbin/sshd -p 27001

这样可以新运行一个 sshd 服务程序,并绑定新的端口(例如上面例子中的 27001 端口)防止和系统默认的 22 端口冲突,ZT_NC_NETWORK 变量指定需要使用的 ZeroTier 虚拟网络路径(默认为:/var/lib/zerotier-one/nc_网络ID),LD_PRELOAD 是必须指定的,否则新运行的 sshd 程序将无法访问 ZeroTier 虚拟网络。

新的 sshd 服务启动成功之后,我们就可以在测试的 Windows、Linux 等主机上运行 ZeroTier 客户端,加入同样的虚拟网络并配置虚拟网络 IP 地址(例如我的 Windows 机器 ZeroTier 网络 IP 地址是 10.10.8.3)及子网掩码。

最后我们可以在 Windows 主机上 ping 测试 Codeanywhere 容器的虚拟网络 IP 地址了,如果通讯正常的话就可以通过 ssh 客户端工具直接连接登录 Codeanywhere 容器系统了,这样就顺利绕过了 Codeanywhere 的随机子域名限制。

后记

如果需要在 Codeanywhere 容器系统启动时自动连接 ZeroTier 虚拟网络并开启独立的 sshd 服务程序,那可以将类似下面的两句命令加到 /etc/rc.local 文件末尾:

/var/lib/zerotier-one/zerotier-netcon-service -d
echo "ZT_NC_NETWORK=/var/lib/zerotier-one/nc_565799d8f61077e9 LD_PRELOAD=/var/lib/zerotier-one/libzerotierintercept.so /usr/sbin/sshd -p 27001" | at now + 2 minutes

第二句命令表示在 ZeroTier Network Container 服务程序启动两分钟之后再启动单独的 sshd 服务程序,以使 ZeroTier 服务程序在等待的时间内能正确连接虚拟网络。

本文主要介绍 ZeroTier 程序里不太常见的 Network Container 容器模式,这对于越来越多的 Docker 容器系统而言能绕过 TUN/TAP 限制还是有点用处的,如果读者发现了有关 ZeroTier P2P VPN 的更多好玩法,欢迎提出交流哦,祝大家玩的开心~~~。

在Linux容器中使用ZeroTier P2P VPN》上的评论

  1. 博主你好,阅读以后感觉你的思路很好。有个小问题请教一下,编译安装 ZeroTier Network Containers是在容器内的ssh运行的命令还是在codeanywhere网页中ssh terminal中运行的?

        1. 老版本的 ZeroTier 需要检出版本库,然后运行 make netcon 自己编译出 Network Containers 模式的程序和库文件;
          如果需要可以下载我编译好的 Network Containers 文件:
          https://zohead.com/downloads/zerotier-netcon.tar.bz2

          新版本 ZeroTier 把 Network Containers 模式改成单独的 ZeroTier SDK 了,
          可以检出 GitHub 上的 ZeroTier SDK 项目编译安装:
          https://github.com/zerotier/ZeroTierSDK
          使用方法和 Network Containers 模式比较类似,后面有空我可以写篇介绍新 ZeroTier SDK 的文章。

    1. 现在是改成 libzt 库了,提供了各种编程语言的 SDK,方便其它程序根据需要访问 ZeroTier 网络。
      好像没有提供像老的 Network Containers 模式那样的直接 wrapper 就能用的库了。

  2. 你好,在Codeanywhere的ubuntu 16.04 xenial环境下 编译出现错误
    (使用gcc或g++ 9.4版本)
    cc1: warning: command line option ‘-Wreorder’ is valid for C++/ObjC++ but not for C
    cc1: warning: command line option ‘-fno-rtti’ is valid for C++/D/ObjC++ but not for C
    make-linux.mk:99: recipe for target ‘netcon’ failed
    make: *** [netcon] Error 1

    安装clang(默认版本13)则出现
    fatal error: too many errors emitted, stopping now [-ferror-limit=]
    20 errors generated.
    make-linux.mk:99: recipe for target ‘netcon’ failed
    make: *** [netcon] Error 1
    最后换成clang-3.5。sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-3.5 2 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-3.5 切换链接版本
    编译通过。
    PS.附件zerotier-netcon.tar.bz2不可下载了。

    1. 评论被自动归到垃圾评论里了才看到,打包的 zerotier-netcon.tar.bz2 也被删除了不好意思,
      我看最新的 ZeroTier 代码里都没有 netcon 了,现在主要推 libzt,不确定 netcon 还能不能用,
      Codeanywhere 现在也没有免费版了,容器用 Ubuntu 16.04 系统也比较老了,编译是很可能有问题的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*