前言
物理专线可以实现多个局域网之间的设备互通(像在同一个局域网内),但由于复杂的流程(运营商审批与实施),对于个人开发者和中小型团队并不友好。
本文将介绍如何基于 WireGuard® 的 “云专线”,以较低的成本(无需审批,每年只需 60 元1)实现多个局域网之间设备互通。
挑战
- 局域网层面的流量转发。即局域网 A 中任意设备可以访问局域网 B 中任意设备(如同在同一个局域网内)。
- 局域网内的 WireGuard 节点位于运营商 NAT 设备之后,不支持 NAT 穿透且无公网 IP。
- 每个局域网内只运行一个 WireGuard 节点。
场景
公司有多个办公地点(分布在不同城市),每个办公地点一个局域网,局域网内自建了私有服务(K8s,GitLab,CI)。
- 不同办公地点的开发者之间需互相访问。
- 不同办公地点的私有服务之间需互相访问。
- A 办公地点的开发者需要访问 B 办公地点的私有服务。
- A 办公地点的开发者需要接收来自 B 办公地点私有服务的回调。
架构
思路
假设有两个局域网 LAN1,LAN2,对应的网段是 192.168.201.0/24,192.168.200.0/24。其中 z(IP 前的字母)是公网服务器(有公网 IP),a 和 c 是内网服务器(无公网 IP)。
公网服务器(z)和内网服务器(a 和 c)组建了一个 WireGuard 网络,且三台服务器开启封包转发(IP Forward)。
LAN1 的设备 b(192.168.201.55)请求 LAN2 的设备 d(192.168.200.244),比如在 b 上执行:ping 192.168.200.244
,b 和 d 是局域网内的普通设备,而不是 WireGuard 节点。
设备 b 通过默认网关(192.168.201.1)确认数据包的下一跳,默认网关中配置了静态路由规则(指定 192.168.200.0/24 的下一跳是 a),因此默认网关将数据包发给 a。
内网服务器(a)收到请求后,a 的内核通过系统路由表匹配 192.168.200.0/24 由 wg0 负责接收,内核将数据包发给 wg0。WireGuard 收到数据包后,根据目的地址和内部路由表实现(WireGuard 内部有自己的路由表实现,基于二叉前缀树,通过 AllowedIPs 配置项指定前缀树中的节点,树节点跟 Peer 建立映射),匹配 192.168.200.0/24 的目标 Peer 是 z,WireGuard 将数据包发给 z。
公网服务器(z)收到请求后,z 的内核通过系统路由表匹配 192.168.200.0/24 由 wg0 负责接收,内核将数据包发给 wg0。WireGuard 收到数据包后,根据目的地址和内部路由表实现,匹配 192.168.200.0/24 的目标 Peer 是 c,WireGuard 将数据包发给 c。
内网服务器(c)收到请求后,c 的内核通过系统路由表发现 192.168.200.244/32 不是自己(192.168.200.248),而是局域网设备 d。因为开启了封包转发,c 将数据包发给目标设备 d。
设备 d 接收到请求并处理,生成响应 b(192.168.201.55)的数据包。设备 d 通过默认网关(192.168.200.1)确认数据包的下一跳,默认网关中配置了静态路由规则(指定 192.168.201.0/24 的下一跳是 c),因此默认网关将数据包发给 c。
内网服务器(c)收到请求后,c 的内核通过系统路由表匹配 192.168.201.0/24 由 wg0 负责接收,内核将数据包发给 wg0。WireGuard 收到数据包后,根据目的地址和内部路由表实现,匹配 192.168.201.0/24 的目标 Peer 是 z,WireGuard 将数据包发给 z。
公网服务器(z)收到请求后,z 的内核通过系统路由表匹配 192.168.201.0/24 由 wg0 负责接收,内核将数据包发给 wg0。WireGuard 收到数据包后,根据目的地址和内部路由表实现,匹配 192.168.201.0/24 的目标 Peer 是 a,WireGuard 将数据包发给 a。
内网服务器(a)收到请求后,a 的内核通过系统路由表发现 192.168.201.55/32 不是自己(192.168.201.247),而是局域网设备 b。因为开启了封包转发,a 将数据包发给目标设备 b。
最终 LAN1 中的设备 b 收到了 LAN2 设备 d 的响应包。
操作
部署 WireGuard 网络
WireGuard 是 Linux 内核模块,配置 WireGuard 网络只需添加两份 systemd 配置后重启 systemd-networkd 即可:systemctl restart systemd-networkd
。
生成密钥对的命令如下(在开发本地执行如下命令即可):
prikey="$(wg genkey)"; pubkey="$(wg pubkey <<< ${prikey})"; pskkey="$(wg genpsk)"
echo -e "PrivateKey: ${prikey}\nPublicKey: ${pubkey}\nPresharedKey: ${pskkey}"
各个服务器的配置如下:
公网服务器(z)配置:
文件:/etc/systemd/network/wg0.netdev
(WireGuard 内部路由表规则通过这份文件中的 AllowedIPs 来指定)
[NetDev]
Name=wg0
Kind=wireguard
MTUBytes=1280
[WireGuard]
ListenPort=51820
PrivateKey=qJwN1zu5alr89c6k18U4SUwZXnUgVhr/tvSgLM7rmHQ=
[WireGuardPeer]
PublicKey=u4vkZywF9hZcy5QUhcbOCjD2EZ7GL2yXgoAH+IoIbHE=
PresharedKey=IBAsO/DwbautQjq/Gksv3lFJZwnUXvkMR3GyxeelS4o=
AllowedIPs=10.70.0.2/32
AllowedIPs=192.168.201.0/24
PersistentKeepalive=15
[WireGuardPeer]
PublicKey=/d8UR69JrdjxC2Fo9hvfZaoSC35LSGNLJ5mqjEyWjRo=
PresharedKey=YdpxayZdKMeoHzo9fVvBSWoDTn1c++r5awhm28Is9Ic=
AllowedIPs=10.70.0.3/32
AllowedIPs=192.168.200.0/24
PersistentKeepalive=15
文件:/etc/systemd/network/wg0.network
(系统级的路由表规则通过这份文件指定)
[Match]
Name=wg0
[Network]
Address=10.70.0.1/16
[Route]
Destination=192.168.200.0/24
[Route]
Destination=192.168.201.0/24
内网服务器(a)配置:
文件:/etc/systemd/network/wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
MTUBytes=1280
[WireGuard]
ListenPort=51820
PrivateKey=gNCkDUdQ3yBynzEST9XOd31NsPluF3Q7aeFu82QsV04=
[WireGuardPeer]
PublicKey=CmmeC0yqofMMZhEGHuK5dd2Mxyxe7tA8wSniDWiI5V0=
PresharedKey=IBAsO/DwbautQjq/Gksv3lFJZwnUXvkMR3GyxeelS4o=
Endpoint=39.101.166.124:51820
AllowedIPs=10.70.0.0/16
AllowedIPs=192.168.200.0/24
PersistentKeepalive=15
文件:/etc/systemd/network/wg0.network
[Match]
Name=wg0
[Network]
Address=10.70.0.2/16
[Route]
Destination=192.168.200.0/24
内网服务器(c)配置:
文件:/etc/systemd/network/wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
MTUBytes=1280
[WireGuard]
ListenPort=51820
PrivateKey=SF6RqNRo1c9vwFVteFonRwpbU1Ee1zsKp6v9c8pXwnA=
[WireGuardPeer]
PublicKey=CmmeC0yqofMMZhEGHuK5dd2Mxyxe7tA8wSniDWiI5V0=
PresharedKey=YdpxayZdKMeoHzo9fVvBSWoDTn1c++r5awhm28Is9Ic=
Endpoint=39.101.166.124:51820
AllowedIPs=10.70.0.0/16
AllowedIPs=192.168.201.0/24
PersistentKeepalive=15
文件:/etc/systemd/network/wg0.network
[Match]
Name=wg0
[Network]
Address=10.70.0.3/16
[Route]
Destination=192.168.201.0/24
添加好配置并重启 systemd-networkd 后,WireGuard 网络就组建好了。
在公网服务器 z 测试一下内网穿透效果2:
# 测试 z 访问 a
ping 10.70.0.2
# 测试 z 访问 c
ping 10.70.0.3
封包转发
为了实现局域网级别的通信,我们需要开启三台服务器 a/c/z(WireGuard 节点)的封包转发,在服务器上分别执行一遍如下命令:
# sysctl 配置文件名开头的数字会影响优先级
# 为了确保配置项的确有效,请在最后执行:systemd-analyze cat-config sysctl.d 进行验证
echo -e 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/10-ip-forward.conf
sysctl -p /etc/sysctl.d/10-ip-forward.conf
效果如下:
配置静态路由
设备 b 和 d 分别是 LAN1/LAN2 中的普通设备(不是 WireGuard 节点),用于模拟局域网中的普通用户。
为了能让 b 访问到 d,我们需要在 b 所在局域网的默认网关上,添加如下静态路由规则(我的测试环境的网关不支持静态路由规则,所以我直接在 b 上添加路由规则):
ip route add 192.168.200.0/24 via 192.168.201.247
同理,在 d 所在局域网的默认网关上,添加如下静态路由规则:
ip route add 192.168.201.0/24 via 192.168.200.248
LAN1/LAN2 中的所有设备就可以互通了。
测试
b 的 IP 地址是: 192.168.201.55,d 的 IP 地址: 192.168.200.244
在 b 上测试能否访问 d:
ping 192.168.200.244
在 d 上测试能否访问 b:
ping 192.168.201.55
最终效果如下:
本文中的两个局域网实际上是通过虚拟化隔离出来的,我的虚拟化环境在广州,公网服务器(z)在北京(阿里云张家口节点 2C0.5G T6 实例,5 年 300 元,折合每年 60 元),由于物理距离远所以延迟较高。实际采购公网服务器最好选在两个局域网中间。
总结
本文介绍了如何基于 Linux 封包转发和 WireGuard 模块(不安装其他工具情况下),实现多个局域网的互通互联。
WireGuard 作为一个简单,安全,高性能的解决方案,系统级(内核模块)实现避免了系统调用带来的开销(相对于 UserSpace 的 Tinc)。UDP 无连接特性,协议本身的无状态3,前向保密性,DoS 防御等,为安全的跨城通信奠定基础。
对于成本优先的中小型企业,这是一个不错的选择。
如果你发现本文中存在任何语法错误,欢迎通过 主页 中 Email 联系我,另外你也可以在 关于 里面找到我的微信二维码。
参考
-
公网服务器 z 可通过 WireGuard IP 主动访问局域网服务器 a 和 c,a 和 c 之间可以相互访问。 ↩︎
-
WireGuard 是一个基于 UDP 的协议,只有经过节点公钥签名的握手请求才会被该节点响应,从而避免服务器探测(公钥泄露会导致节点被攻击者发现,导致可探测),即无状态,见 WireGuard 白皮书。 ↩︎