背景
在构建完AIO家庭服务器后,如何在异地访问家庭服务器就成了接下来需要解决的问题,这时候N2N就进入了我的视野。
N2N虚拟局域网不但能够像DDNS一样暴露局域网中的服务(原理不太一样),还能够连接异地不同局域网组成一个超级局域网,这很满足我的需求。
但是,组建N2N虚拟局域网需要一个中心服务器,刚好手上有个ECS服务器,这对我来说完全不是问题。
说干就干,Let’s Do It。
软件安装
我最初接触N2N时,还是1.x向2.0转换的过程中,现在N2N已经发展到了3.1,4.0也在规划中了,有兴趣的可以上N2N的github看看。
我们将采用N2N 3.0,但某些操作系统(如Ubuntu)仓库中的N2N版本太老,所以我们需要从github下载最新版本,如果没有对应的二进制文件,就需要从源码编译(如macOS)。
Ubuntu
直接从github下载二进制文件。目前稳定版本是3.0.0,直接从Release页面下载即可。ECS服务器可通过如下命令下载和安装
1
2
3
| wget https://github.com/ntop/n2n/releases/download/3.0/n2n_3.0.0-1038_amd64.deb
sudo dpkg -i n2n_3.0.0-1038_amd64.deb
|
OpenWRT
由于我使用的OpenWRT版本较新,仓库内存版本已经更新到3.0.0,可以直接使用。如果没有,可能就需要想其他办法了。
1
2
3
4
| opkg install n2n luci-app-n2n
# 可能有用的调试工具
opkg install kmod-ipt-nat n2n luci-app-n2n dmesg iptables-nft net-tools-route bind-dig diffutils
|
macOS
目前官方并没有放出macOS的二进制文件,只能从源码编译了。由于N2N使用tun/tap来构建虚拟网卡,所以需要先安装一些依赖。
1
2
3
| # 请先安装homebrew
brew install --cask tunnelblick
brew install make gcc
|
1
2
3
4
| # 下载源码
git clone https://github.com/ntop/n2n.git
cd n2n
./configure && make && make install
|
没有条件的可以直接下载我已经编译好的文件:edge_macOS.zip
驱动程序:tap_tun_kext.zip 或者 tunnelblick_tap_tun_kext.zip
选择其中一个,解压后将文件拖上/Library/Extensions
,中间可能提示授权,允许即可。
1
2
3
4
5
6
7
| # 安装
sudo kextload /Library/Extensions/tap.kext
sudo kextload /Library/Extensions/tun.kext
# 卸载
sudo kextunload /Library/Extensions/tap.kext
sudo kextunload /Library/Extensions/tun.kext
|
中心节点配置
我们的ECS节点有公网IP,这个很重要,这能够保证我们在任何地方访问ECS服务器,在加上N2N的辅助,这样就可以访问家庭服务器。
在服务端,我们不但要开启N2N server,同时也需要开启一个N2N edge node作为网关使用,提供网络加速服务。
N2N服务器
1
2
3
4
5
6
7
8
9
10
| sudo vim /etc/n2n/supernode.conf
# 侦听端口,同时在ECS控制台打开该端口的入口流量/UDP
-p=7777
# 关闭MAC地址欺骗保护。
# 至少调试阶段需要关闭这个选项,不然你刚断开再连接就会失败。
-M
sudo systemctl enable supernode.service
sudo systemctl start supernode.service
|
N2N网关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| sudo vim /etc/n2n/edge.conf
# 网卡名称
-d=n2n0
# 网络名称与密码
-c=test
-k=123456
# 使用静态IP&MAC更安全,也更方便
-m=02:xx:xx:xx:xx:xx
-a=static:10.20.30.1/24
# 服务器地址与端口
-l=xxxxxxxx:xxxx
# 定义MTU,这个很重要
-M=1390
# 网关数据转发
-r
# 添加路由表
-n=10.10.10.0/24:10.20.30.254
sudo systemctl enable edge.service
sudo systemctl start edge.service
|
配置NAT规则
1
2
3
4
5
6
7
8
| sudo iptables -A FORWARD -i n2n0 -j ACCEPT
sudo iptables -A FORWARD -o n2n0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# MASQUERADE
sudo iptables -t nat -A POSTROUTING -s 10.20.30.0/24 -j MASQUERADE
# 如果有静态IP,可使用SNAT代替MASQUERADE,性能更好
sudo iptables -t nat -A POSTROUTING -s 10.20.30.0/24 -o -j SNAT --to-source <ip>
|
更多可参考官方文档
内网网关配置
由于我的家庭服务器使用OpenWRT作为主路由,所以直接将N2N edge安装到OpenWRT上,这样可以省去为每个设备设置N2N网络的麻烦。
- 配置网卡名称
- 配置静态IP和MAC地址
- 配置中心节点地址
- 配置网络名称与密码
- 网关一定要启用数据包转发
- 启用IP动态伪装(MASQUERADE)方便服务器上管理
防火墙配置
新建interface(接口) N2N,指定n2n0为其端口,同时将N2N添加到firewall的lan。而其他配置,基本上就是什么都不要,只要firewall把其当成lan口处理就行。
不要担心n2n0不会得到配置,由于n2n服务晚于network启动,所以当n2n启动时,n2n0的ip、route都会配置好。
OpenWrt上的n2n提供了一个参数 - IP动态伪装,为n2n0出口数据做了Masquerade转发,对应以下iptables rule:
定义入口流量NAT规则, 网络 - 防火墙 - NAT规则:
外网主机/macOS配置
使用刚刚编译好的软件,其实一条命令就可以连接整个N2N网络:
1
| edge -c <name> -k <key> -a static:10.20.30.99/24 -l <服务器> -M 1390
|
然后设置路由表将需要的流量导入N2N网络即可。但这样多少有些不方便,写个小脚本吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| vim n2n.sh
#!/bin/bash
ip=${1:-10.20.30.99/24}
net=${2:-10.10.10.0/24}
gw=${3:-10.20.30.254}
exec 2>&1
exec 1>/tmp/edge.log
sudo ps aux | grep edge > /dev/null && sudo pkill edge
sudo route -n delete -net $net $gw > /dev/null 2>&1
sudo edge -c <网络> -k <密码> -a static:$ip -l <服务器> -M 1390
# fix for -n for macOS
sleep 1
sudo route -n add -net $net $gw > /dev/null 2>&1
|
备注:macOS上edge -n
配置路由表存在bug,不生效!
配置路由表
在所有N2N edge(OpenWRT路由器除外)上配置内网路由表
1
2
3
4
5
| # add route with 'ip'
sudo ip route add 10.10.10.0/24 via 10.20.30.1 dev n2n0
# macOS
sudo route -n add -net 10.10.10.0/24 10.20.30.1
|
测试
1
2
3
4
| ping 10.20.30.254 # 测试内网路由器的n2n0接口 - 检查内网路由器的N2N设置
ping 10.10.10.1 # 测试内网路由器的物理接口 - 检查路由器NAT规则
ping 10.10.10.99 # 测试内网主机(未运行N2N) - 检查路由器NAT规则,可尝试在路由器上ping进行对比
ping 10.20.30.99 # 测试外网主机(运行N2N) - (外网不受控,很可能ping不通)
|
1
2
3
4
5
6
7
8
9
| ping 10.20.30.1 # 测试N2N服务器 - 检查本地和服务器上N2N的设置
ping 10.20.30.99 # 测试外网主机 - (外网不受控,很可能ping不通)
# 找个ping得通的公共ip,将其添加到路由表中
ip route add 106.11.253.86/32 via 10.20.30.1 dev n2n0
traceroute 106.11.253.86 # 测试路由是否设置成功
ping 106.11.253.86 # 测试N2N服务器网关 - 检查服务器的NAT规则
ip route del 106.11.253.86
|
1
2
3
| ping 10.20.30.254 # 测试内网路由器 - 检查路由器NAT规则
ping 10.20.30.1 # 测试N2N服务器 - 检查路由器的路由表
ping 10.20.30.99 # 测试外网主机 - (外网不受控,很可能ping不通)
|
1
2
3
4
| ping 10.20.30.1 # 测试N2N服务器 - 检查本地N2N设置
ping 10.20.30.254 # 测试内网路由器的n2n0
ping 10.10.10.1 # 测试内网路由器的物理接口
ping 10.10.10.99 # 测试内网主机
|
如何设置适当的MTU值
由于N2N需要对IP数据包进行封装,所以必然有额外的开销,这导致默认的MTU=1500并不适用于N2N网卡。这里将讲述如何设置MTU。
实际链路MTU一般为1500或1492,为了保证我们的N2N网络正常运行,N2N MTU <= 1500/1492 - N2N报文头大小。
测试物理链路MTU:
1
2
3
4
5
6
7
| # 禁用IP分片,并带数据ping服务器
ping -M do -s 1472 <服务器地址> # 1500 - 28
ping: local error: message too long, mtu=1492
# 这里的MTU值直接检测出来了,但不一定可靠,继续减少数据大小,直到能够ping通服务器
# MTU = ping携带的数据大小 + 28(ping报文头大小)
|
本地测试ping最多能够发送1464+28字节数据,所以MTU=1492。
测试N2N链路MTU:
1
2
3
4
5
| # 前提: 将所有N2N节点MTU设置为1500或更大
# 禁用IP分片,并带数据ping N2N服务器网关
ping -M do -s 1464 10.20.30.1 # 1492 - 28
# 持续减少数据大小,直到能够ping通服务器
|
本地测试ping最多能够发送1368+28字节数据,所以MTU=1396,N2N报文头大小为96。
哪些MAC地址可以安全的使用
有私有IP,自然也有私有MAC地址,可以放心使用:
1
2
3
4
| x2-xx-xx-xx-xx-xx
x6-xx-xx-xx-xx-xx
xA-xx-xx-xx-xx-xx
xE-xx-xx-xx-xx-xx
|
N2N服务器网关无响应问题
在配置路由表的过程中,我们发现部分网站或部分终端无法通过N2N服务器网关,通过tcpdump
发现N2N服务器上出现Destination unreachable (Fragmentation needed)
的错误:
通过分析发现:
上游网络发来巨型帧(2882),这显然无法通过我们的N2N网络,所以需要分片。但服务器没有分片,而是给上游网络发送ICMP告知其下一跳MTU为1390。
上游网络收到ICMP将数据分片重发,但是重发的数据仍然大于MTU,这样就限入死循环了。
好吧,没办法,找个正常的情况对比一下:
正常情况下,MSS = MTU - 40。但是不管我们设置MTU为多少,TCP SYN中MSS值始终为1460,也就是MTU被当成了1500,而实际MSS为1390 - 40 = 1350。这样,返回的报文自然无法在服务器上自动完成分片操作,所以连接就无响应。
进一步测试发现,使用不同的软件,该MSS值也将不同:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 在N2N服务器网关上打开tcpdump
sudo tcpdump -i n2n0
# 在本地进行测试
tcping 10.20.30.1
# MSS = 1350 ==> 正确
19:37:20.249736 IP 10.20.30.254.42978 > ECS.http: Flags [S], seq 3264860104, win 64800, options [mss 1350,sackOK,TS val 4094004489 ecr 0,nop,wscale 7], length 0
# 在本地测试
wget https://api.github.com/meta
# MSS = 1460 ==> 错误
19:38:06.520800 IP 10.20.30.254.32774 > 20.205.243.168.https: Flags [S], seq 3752455363, win 64240, options [mss 1460,sackOK,TS val 3038086888 ecr 0,nop,wscale 7], length 0
|
好吧,经过3天的努力,问题终于找到了,但更多的问题也来了。这个MSS到底什么情况,怎么还能变化?头都要爆炸了!!!
通过无尽的探索,终于让我们发现了解决方案:
解决方案1: 修改advmss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 获取网卡特性
ethtool -k eth0
# 关闭物理网卡的tso/gso/gro
# tso - tcp-segmentation-offload
# gso - generic-segmentation-offload
# gro - generic-receive-offload
ethtool -k eth0 tso off
...
# 在本地和N2N服务器同时设置advmss
# 服务器
ip route change 10.20.30.0/24 dev n2n0 proto kernel scope link src 10.20.30.1 advmss 1350
# 本地
ip route change 10.20.30.0/24 dev n2n0 proto kernel scope link src 10.20.30.254 advmss 1350
|
==> 此方法未经验证,修改网卡属性影响实在太大,特别是在服务器上。
==> 不修改网卡属性(关闭tso/gso/gro),advmss
是不会生效的。
本质问题:现在市面上多数网卡(包括虚拟网卡)都支持自动分片,但我们N2N使用的虚拟网卡却不支持,所以上游网络发来的巨型帧是无法通过我们的N2N网络,除非在入口网卡或内核中完成IP分片。
解决方案2:强写MSS值
1
2
3
4
5
6
7
8
9
10
| # 使用 --clamp-mss-to-pmtu
sudo iptables -I FORWARD -i n2n0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
# 使用 --set-mss
sudo iptables -I FORWARD -i n2n0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1350
# 修改前:
20:01:19.942146 IP ECS.54044 > 20.205.243.168.https: Flags [S], seq 951446441, win 64240, options [mss 1460,sackOK,TS val 3039480309 ecr 0,nop,wscale 7], length 0
# 修改后
20:03:16.621032 IP ECS.60406 > 20.205.243.168.https: Flags [S], seq 2888455217, win 64240, options [mss 1350,sackOK,TS val 3039596987 ecr 0,nop,wscale 7], length 0
|
OpenWRT/N2N的改进
OpenWRT上N2N网络的启动脚本/etc/init.d/n2n
还是有些小问题:
- 不支持将host ip添加到路由表
- 不支持MAC地址设置
- IP转发默认为MASQUARADE,不支持SNAT,太不智能了
- 服务器地址未加入路由表中,这导致n2n0无法作为默认网关使用,也容易导致问题。
- 启动时间太晚,导致某些服务无法使用N2N网络初始化
- 重启n2n会让路由丢失 [update 2023-05-22]
这是修改过的版本:n2n-fixed.zip
Updated 2023-05-22:n2n-fixed.zip
生成随机密码
由于N2N网络一般不会随意改动,所以适合使用非常复杂的密码。
这里推荐一个生成随机密码的简单办法,只要软件支持,你可以生成一个最大长度的密码,让那些想破解的人直接闷逼。
1
2
3
| openssl rand -base64 12 # 随机生成一个16字节的字符串
# ‘+’在N2N中是通配符,如果生成的结果包含‘+’,重新生成一个
|
==> N2N报文头加密支持19字节,而加密算法支持128/192/256 bits,也就是16/24/32字节。所以,我们选择一个16-19字节的字符串作为网络名称与密码最合适
结束语
因为MSS的事情,做这个网络,前前后后花了3-4天的时间,我甚至给阿里云的友人们提了几个工单,这里特别感谢在后台为我提供技术支持的友人们。
参考
- https://github.com/ntop/n2n
- https://blog.51cto.com/fengjicheng/5177620