用 Nebula 创建私人局域网

缘起

作为一个 IT 苦力的我,由于常年受白嫖怪朋友们的厚(qi)爱(fu),时常需要帮友人重装系统,配置网络,设置路由器,组装 NAS 什么的。毕竟学通信工程的人,不就是修电脑、修手机和修网络的吗?😂

理论上来讲,很多东西装好,配置好,应该就不会有任何问题了。一开始,我也是这样以为的,然而很多时候往往事与愿违。今天不是张三的电脑出问题了,就是明天李四的 NAS 挂了。

作为一个懒人,现场支持什么的,那是肯定不可能的,毕竟咱这工时费也不低不是(可惜从来没收过)。再者说来,朋友们碰到的大多数问题其实都不大需要现场支持这么严重,除非是系统级别的灾难。

然而,我又一次天真了。同一个问题,我自己可能几分钟就解决了,而跟好友打电话、视频、语音等各种沟通,可能要花上数小时。别问我为什么不用远程桌面(RDP)TeamViewer、QQ 远程协助类的工具。绝大多数人的网络都在 NAT 后面,很多还是好几级的 NAT,根本就连不上。在 macOS 系统下,是无法使用 QQ 远程协助的,因为根本没有这个功能。TeamViewer 要付费,这个也就没什么好聊的啦,参考第一段第一句话。🤦‍♂️

比较稳定能用的网络连接工具也就是 ZeroTier 了,但是要跟对方讲怎么安装,怎么加入网络也是个吃力不讨好的事儿。而且可能就一个小问题,还要安装一个额外的软件,也是得不偿失。再者,ZeroTier 中继的速度非常慢,而且就算连上了,还是得用屏幕共享的方式解决问题,也并不怎么优雅。更不要说像 NAS 这类的 Linux 系统了,我该如何跟小白解释 SSH,证书还有 Terminal 了 。

如果在路由器级别就能实现互联互通,解决掉动态域名NAT 打洞的问题,那么就可以把远程协助的工作变成一个局域网内的远程管理,这样就非常美好了。起码不用让对方去查看自己的公网 IP,不用做 UPnP 映射,下载工具什么的。其实这种方式也非常适合有多个办公地点的小伙伴,或者是那种多 sites 办公的群体。还有一个应用场景,就是有自己 NAS 的小伙伴,可以在外面安全地访问家里存放的私人数据。

需求

在思考过可能的使用场景后,整理需求如下:

  1. 能实现多个地点的局域网化(SD-LAN),比如北京、上海、广州和深圳办公室的互联
  2. 在外可以实现远程访问公司或者家里 NAS 上存储的数据
  3. 客户端支持主流操作系统 macOS、Windows、Linux 以及手机 iOS、Android 应用
  4. 实现端对端加密,且支持现代加密技术,以保证数据传输的安全性
  5. 可以运行在路由器上,实现不同路由器下子网间的互联互通
  6. 新加入网络的设备不改变现有网络拓扑结构,无需修改现有配置

可选需求:

  1. 配置简单、方便,有图形化界面,或者类似工具
  2. 去中心节点化,尽可能多的容灾方案
  3. 最好是开源代码,而非商业代码,以便于代码审查

工具对比

在做了一大轮的功课以后,我分别尝试了以下这些工具:

总体来讲,ZeroTier 的体验是最好的,虽然中继服务器经常连不上,或者很慢,但是可以通过自建 moon 的方式进行中转。基本上所有类型的 NAT 都可以打通,对于不能打通的可以使用中继服务器进行中转。但使用中继的时候,带宽受限于中转服务器的带宽。相对来说,新手上手难度比较低,但是商业公司,中心服务器,数据安全可能是个问题。

WireGuard 是一个已经加入到 Linux 内核里面的协议,配置也不复杂,实测传输速度也很不错。默认不支持在 NAT 网络下打洞,不过网上已经有人通过 CoreDNS 的 Plugin 实现了 WireGuard 的打洞,但还要编译代码什么的比较啰嗦。同时,配置起来也比较麻烦,每个节点(Node)需要单独配置公私钥,多个节点之间必须每两两进行配置。也就是说如果 2 个服务器就要配置 2 个节点信息,3 个需要配置 6 个,如果站点多的话,自己算吧……

Cisco AnyConnect 属于商用软件,Server / Client 模式,同时还需要占用 443/TCP 端口,属于比较传统的产品。虽然有 ocserv,但是配置也很麻烦,客户端强依赖于 Cisco,并不适用于现有需求,直接劝退吧。

frp,也需要服务器中转,受限于服务器带宽。有朋友说现在有 xtcp 模式可以支持直连打洞,但本人并没有试验过。n2n,ngrok 什么的在 Linux 服务器上配置过,对于终端用户来说,还是不那么理想。

而 Nebula,从 2020 年开始关注以来,一直没有真正部署过。主要是文档太少,官方开发速度缓慢,一些该有的核心功能还不完善。比如刚开始的时候,他们没有 iOS 和 Android 的客户端。再有就是必须要自己有一个公网 IP 的服务器做 Lighthouse,同时还不支持数据中转。一些复杂的 NAT 网络无法穿透,不支持 IPv6 等等。

但是他们最近发布了 1.4.0 的版本,突然之间就变得真香了。对于我来说,最重要的一点,就是支持 IPv6 了。也就是说,在有 IPv6 支持的情况下,完全不需要关心 NAT 类型了,可以直接建立连接。再有就是手机客户端官方也开源了,同时 iOS 上也可以正常使用了。这就意味着只用一个软件,我就可以把已知的网络通过路由器全部连通的同时,还可以用手机直接访问内网 NAS 上的数据。

Nebula

首先说说,什么是 Nebula?其官方介绍如下:

Nebula is a scalable overlay networking tool with a focus on performance, simplicity and security. It lets you seamlessly connect computers anywhere in the world. Nebula is portable, and runs on Linux, OSX, Windows, iOS, and Android. It can be used to connect a small number of computers, but is also able to connect tens of thousands of computers.

简单来说,Nebula 是由 slack 开源的一款专注于性能、易用性和安全性的 P2P 网络通信工具。可以运行在 Linux、macOS、Windows、iOS 和 Android 等设备上,使得用户可以无缝连接任何一台网络设备。

Nebula 的底层使用了 Noise Protocol Framework 协议以实现SDN(Software Defined Network) 双向认证的 P2P 网络。可以使用 AES 或者 CHACHAPOLY 加密算法,以保证数据传输的安全性。

下载

首先去 Nebula 的 GitHub Release 页面下载最新的 Nebula:

https://github.com/slackhq/nebula/releases

当前最新的版本为 Nebula 1.4.0,已经支持 IPv6 了,针不戳。

下载自己对应平台的版本就可以了,因为我要跑在 MTK 的路由器上(红米 AC2100,Newifi-D2 等),官方没有 mipsle-softfloat 的架构,所以就自己编译了一个版本,可以在下面的地址下载:

https://github.com/TommyLau/nebula/releases

同时,因为朋友需要在 Windows 32 位系统下面使用,所以顺便也编译了一个 x86 的版本。有需要的小伙伴,同样可以在上面的链接里面找到对应的下载。

下面是 iOS 和 Android 的版本:

https://apps.apple.com/us/app/mobile-nebula/id1509587936
https://play.google.com/store/apps/details?id=net.defined.mobile_nebula

需要注意的是,iOS 版本在中国区是没有上架的,Android 版本好像也只有 Google Play 上才有,这个大家自己想办法解决吧。

解包 Nebula 以后主要有 2 个文件,nebulanebula-cert

创建 CA 证书

创建证书,好比使用其他工具一样,一如既往的简单。只需要输入如下的内容便可以生成我们需要的 CA 证书了。

1
./nebula-cert ca -name "Myorganization, Inc"

其中 "Myorganization, Inc" 为这个 Nebula 网络的证书名,可以改成任意方便记忆的名字。

程序执行完毕后,会在当前目录下生成 ca.key(私钥)和 ca.crt(证书)两个文件。

ca.key 用于签名所有的 Nebula 节点,因此格外重要,请务必妥善保管。又因为其重要性,建议仅在签名时使用,并作单独存放,不要放到 Lighthouse 或者任何一个 Nebula 节点上。

创建密钥与证书

假设我们现在要配置 3 个主机,分别是 lighthouserouterlaptop。我们可以直接使用刚才说的 3 个名字,也可以用域名的方式来命名,都是可以的。

同时我们需要提前规划好网络定义,比如:192.168.100.0/24。这个网络要跟我们现在已有的网络地址区分开,比如 192.168.1.0/24 等。因为 Nebula 是 Overlay Network,它必须基于现有网络的基础之上,如果 IP 地址冲突的话,就无法正常工作了。

我们可以通过下面的命令,来生成不同的证书:

1
2
3
./nebula-cert sign -name "lighthouse" -ip "192.168.100.1/24"
./nebula-cert sign -name "router" -ip "192.168.100.11/24" -subnets "172.16.1.0/24" -groups "router,servers"
./nebula-cert sign -name "laptop" -ip "192.168.100.100/24" -groups "laptop,ssh"

其中:

  • -name,就是这个节点的名字,可以任意填写,也可以用域名的方式填写
  • -ip,指定 Nebula 分配给该节点的 IP 地址,需要手动指定,且不能与已分配的 IP 地址冲突
  • -subnets,指定当前节点的非 Nebula 路由,以便其它节点能访问当前节点的子网
  • -group,指定该节点所在的组,方便 Nebula 进行防火墙规则配置

在上面的例子中,我们创建了 3 个不同的节点。lighthouse 需要部署在有公网 IP 的服务器上,用于连通各个不同的节点。router 部署在家里的路由器上,以方便访问 NAS 或者智能家居等设备。laptop 相当于一个笔记本,用于在任何地方接入这个私有网络。类似的,我们也可以增加 phonepad 等设备,其它证书的生成方式与 laptop 类似,使用不同的名称就可以了。

配置 Nebula

对于 Nebula 来说,每一个节点都需要一个单独的配置文件。我们可以下载官方的配置文件作为参考模板:

https://github.com/slackhq/nebula/blob/master/examples/config.yml

我们把这个文件复制成两份,分别是 config-lh.yamlconfig.yaml,分别对应 lighthouse 和其它主机。

1
2
cp config.yml config-lh.yaml
cp config.yml config.yaml

配置 Lighthouse

在 Lighthouse 节点上,最重要的是要确认 am_lighthouse: true 为开启状态。

一般来说,Lighthouse 节点不需要指定静态路由 static_host_map,因为所有的节点都会跟 Lighthouse 进行通信。就算我个人使用了多 Lighthouse 的配置,其实也不需要设置静态路由,除非是需要在 Lighthouse 之间进行通信。

另外,为了更好的组网,IPv6 当然是必不可少的啦!在官方的配置文件中,默认使用 IPv4 方式:host: 0.0.0.0,这里我们需要手动修改为:host: "[::]" 来同时监听 IPv4 和 IPv6 的数据。

修改过的参数配置如下:

config-lh.yaml
1
2
3
4
5
6
7
static_host_map:

lighthouse:
am_lighthouse: true

listen:
host: "[::]"

除此之外,配置文件里面还有子网相关的配置,因为我使用了与官方参考配置相同的子网: 192.168.100.0/24,所以不需要做额外地修改。如果你配置了不同的子网和 IP 的话,请相应地做出调整。

配置节点

配置节点与配置 Lighthouse 类似,唯一不同的是,需要在 static_host_map 中指定 Lighthouse 的公网 IP 和端口。这里使用 IP 或者域名的方式都可以,个人建议使用域名,这样可以减少更换 Lighthouse 服务器以后,需要逐个节点修改配置的麻烦。

static_host_map 前面的部分是 Lighthouse 在 Nebula 网络中的 IP 地址(参考本文证书部分),后面是公网服务器的域名或者 IP,以及其对应的端口。

am_lighthouse 必须要设置为 false,同时 hosts 里面必须要指定 Lighthouse 的 Nebula IP。

同样的,为了打开 IPv6,host -> listen 还是要调整下。同时这里有个坑,就是在 NAT 后面的网络,一定要把 port 设置为 0,以使用动态端口,不然各种连不上。其实我觉得除了 Lighthouse 以外,其它的节点都应该将端口设置为 0,以减少不必要的麻烦。

config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
static_host_map:
"192.168.100.1": ["your.domain.tld:4242"]

lighthouse:
am_lighthouse: false
interval: 60
hosts:
- "192.168.100.1"

listen:
host: "[::]"
port: 0

配置到这里,基本上就算是完成了。但是我们之前有一个 router 节点,为的就是能远程访问局域网的内容,所以这个时候我们还需要多配置一个 unsafe_routes。需要注意的是,如果要访问 router 节点背后的网络,我们需要在每个需要访问该网络的节点上,增加如下配置:

1
2
3
4
tun:
unsafe_routes:
- route: 172.16.1.0/24
via: 192.168.100.11

这里的 172.16.1.0/24 就是路由器对应的局域网(LAN)的网段,而 192.168.100.11 就是我们之前配置的 router 节点所对应的 Nebula 网络中的 IP。我们只需要把这个配置文件复制到 laptop 节点中,并配合相应的证书,就可以实现远程访问家庭网络的目的。

需要注意的是,对于 router 节点本身,不能添加这条路由规则,否额会导致路由冲突而组网失败。

防火墙配置

官方默认的防火墙配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
firewall:
outbound:
# Allow all outbound traffic from this node
- port: any
proto: any
host: any

inbound:
# Allow icmp between any nebula hosts
- port: any
proto: icmp
host: any

# Allow tcp/443 from any host with BOTH laptop and home group
- port: 443
proto: tcp
groups:
- laptop
- home

大概就是,使用这个配置的节点,对外访问 Nebula 网络无任何限制。Nebula 网络中的其它节点,只能通过 ICMP 协议 PING 当前节点。属于 laptophome 组的其它节点,可以访问当前节点的 443/TCP 端口。

大家刚开始配置的时候,我个人建议先不要设置防火墙规则,先让服务跑起来然后再说。毕竟绝大多数情况下,大家一开始的时候,网络都是各种不通,别说防火墙了。所以我们可以修改成如下的样子:

1
2
3
4
5
6
7
8
9
10
firewall:
outbound:
- port: any
proto: any
host: any

inbound:
- port: any
proto: any
host: any

允许各个节点之间自由通信。

其它配置

因为要在路由器上使用,且对于安全没有过度的痴迷,这里我将加密算法指定为 chachapoly。毕竟 aes 算法虽好,但是如果没有硬件指令集支持的话,还是很难过的。尤其是路由器这种嵌入式处理器,老旧一点的大多没有 AES 指令集,实测很多连带宽都无法跑满。

1
cipher: chachapoly

需要注意的是,各个节点之间必须使用相同的加密方式,也就是说要么全部用 aes,要么全部用 chachapoly,并不能混合使用。

运行 Nebula

运行 Lighthouse

下载服务器对应的 Nebula 二进制可执行文件,然后把 ca.crtlighthouse.keylighthouse.crt 还有 config-lh.yaml 上传到服务器。

创建 /etc/nebula 目录:

1
mkdir /etc/nebula

将配置文件移动到指定目录:

1
2
3
4
mv config-lh.yaml /etc/nebula/config.yaml
mv ca.crt /etc/nebula/ca.crt
mv lighthouse.crt /etc/nebula/host.crt
mv lighthouse.key /etc/nebula/host.key

运行 Nebula

1
./nebula -config /etc/nebula/config.yaml

运行节点

类似 Lighthouse,同样的把相关的文件复制到需要运行的主机上,主要是 ca.crtconfig.yaml 和对应节点的两个证书文件,比如:router.keyrouter.crt

同样的,创建 /etc/nebula 目录:

1
mkdir /etc/nebula

将配置文件移动到指定目录:

1
2
3
4
mv config.yaml /etc/nebula/config.yaml
mv ca.crt /etc/nebula/ca.crt
mv router.crt /etc/nebula/host.crt
mv router.key /etc/nebula/host.key

运行 Nebula

1
./nebula -config /etc/nebula/config.yaml

验证

在任何一个非 Lighthouse 的节点上,应该可以 ping 通 Lighthouse:

1
ping 192.168.100.1

如果能 ping 通则表示 Nebula 网络建立成功。

其他

不同于其它的组网程序,Nebula 的 Lighthouse 是不负责数据的中转的,也就是说所有的数据都是 P2P 的,如果能连上就能连上,连不上就……连不上。😅

所以如果大家能 ping 通 Lighthouse,但是两个节点之间无法互通的话,大概率就是 NAT 穿透的问题。

这也是为什么我觉得 IPv6 很重要的原因,因为只要双方有 IPv6,除非防火墙配置有问题,否则就不可能出现连不上的情况。哪怕是在手机热点的辣鸡 Symmetric NAT 下,也能有效连通。

用 Nebula 的好处就是,只要网络规划合理,添加新的节点非常容易。只需要用 CA 签名一个新的证书就可以了,现存的节点都不需要修改。而且节点跟节点之间都是直连,如果 CPU 够用的话,基本上都可以在任意两个节点之间跑满带宽。

除了「真香」,我还能说什么呢?😊

附件

最后,提供 lighthouselaptop 的完整配置文件,供大家参考。

config-lh.yaml

config-lh.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
pki:
ca: /etc/nebula/ca.crt
cert: /etc/nebula/host.crt
key: /etc/nebula/host.key

static_host_map:

lighthouse:
am_lighthouse: true
interval: 60
hosts:
- "192.168.100.1"

listen:
host: "[::]"
port: 4242

punchy:
punch: true

cipher: chachapoly

tun:
disabled: false
dev: nebula1
drop_local_broadcast: false
drop_multicast: false
tx_queue: 500
mtu: 1300

logging:
level: info
format: text

firewall:
conntrack:
tcp_timeout: 12m
udp_timeout: 3m
default_timeout: 10m
max_connections: 100000

outbound:
- port: any
proto: any
host: any

inbound:
- port: any
proto: any
host: any

config.yaml

config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
pki:
ca: /etc/nebula/ca.crt
cert: /etc/nebula/host.crt
key: /etc/nebula/host.key

static_host_map:
"192.168.100.1": ["your.domain.tld:4242"]

lighthouse:
am_lighthouse: false
interval: 60
hosts:
- "192.168.100.1"

listen:
host: "[::]"
port: 0

punchy:
punch: true

cipher: chachapoly

tun:
disabled: false
dev: nebula1
drop_local_broadcast: false
drop_multicast: false
tx_queue: 500
mtu: 1300
unsafe_routes:
- route: 172.16.1.0/24
via: 192.168.100.11

logging:
level: info
format: text

firewall:
conntrack:
tcp_timeout: 12m
udp_timeout: 3m
default_timeout: 10m
max_connections: 100000

outbound:
- port: any
proto: any
host: any

inbound:
- port: any
proto: any
host: any