前言

物理专线可以实现多个局域网之间的设备互通(像在同一个局域网内),但由于复杂的流程(运营商审批与实施),对于个人开发者和中小型团队并不友好。

本文将介绍如何基于 WireGuard® 的 “云专线”,以较低的成本(无需审批,每年只需 60 元1)实现多个局域网之间设备互通。

挑战

  • 局域网层面的流量转发。即局域网 A 中任意设备可以访问局域网 B 中任意设备(如同在同一个局域网内)。
  • 局域网内的 WireGuard 节点位于运营商 NAT 设备之后,不支持 NAT 穿透且无公网 IP。
  • 每个局域网内只运行一个 WireGuard 节点。

场景

公司有多个办公地点(分布在不同城市),每个办公地点一个局域网,局域网内自建了私有服务(K8s,GitLab,CI)。

  • 不同办公地点的开发者之间需互相访问。
  • 不同办公地点的私有服务之间需互相访问。
  • A 办公地点的开发者需要访问 B 办公地点的私有服务。
  • A 办公地点的开发者需要接收来自 B 办公地点私有服务的回调。

架构

wireguard-leased-line-architecture-diagram

思路

假设有两个局域网 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 联系我,另外你也可以在 关于 里面找到我的微信二维码。

参考


  1. 这里的费用参考的是阿里云物理专线 的端口费(每年 2000 元起步),不含流量费。 ↩︎

  2. 公网服务器 z 可通过 WireGuard IP 主动访问局域网服务器 a 和 c,a 和 c 之间可以相互访问。 ↩︎

  3. WireGuard 是一个基于 UDP 的协议,只有经过节点公钥签名的握手请求才会被该节点响应,从而避免服务器探测(公钥泄露会导致节点被攻击者发现,导致可探测),即无状态,见 WireGuard 白皮书。 ↩︎