本文适用于拥有两个家庭网络,并且最少有一个家庭网络是拥有外网IP,然后希望可以在两个家庭网络中自由共享网络资源的情景。
技术选型
可以实现虚拟隧道技术的技术有很多,比如大名鼎鼎的OpenXXX。本文抛弃OpenXXX从而选择WireGuard,原因大体有两个:
- OpenXXX的服务端在openwrt中的表现实测中,无法打印日志,所以也就没有办法在无法启动服务时第一时间排查原因。
- OpenXXX可能由于其太过于隐蔽自己行为的原因变得难以追踪,所以当文章中的出现的技术又恰好适用于FQ时,会导致发文不成功。
网络架构设计
- 服务端拥有运营商分配的外网地址,比如:8.8.8.188
- 客户端仅仅拥有内网IP。
- 服务端的openwrt的内网IP地址为:192.168.20.1/24
- 客户端的openwrt的内网IP地址为:192.168.12.1/24
- 服务端WireGuard的隧道IP地址为:192.168.99.1/24
- 客户端WireGuard的隧道IP地址为:192.168.99.2/24
注意:两个局域网中的网络段不能够冲突。
目标
- 192.168.12.2 能够与 192.168.20.2 通讯
- 192.168.20.2 能够与 192.168.12.2 通讯
- 除双方需要与对方通信时,才走VPN网络。其它的通讯不受VPN的影响。
实现(图形界面)
服务端
todo
客户端
安装WireGuard
先安装: wireguard-tools
再安装: luci-proto-wireguard
最后安装qrencode:
最后重新启动路由器。
创建VPN接口
为接口起个名字,比如vpn,然后选择协议为WireGuard VPN,点击创建接口。
如上图生成密钥对,复制Public Key备用,同时设置该接口的网络地址为:192.168.99.2
然后点击 Firewall Setting 选项卡:
在最下方的输入框中输入vpn并回车,此时将会为我们创建一个vpn区域。最后回来Generate Setting ,点击右下角的 Save.
回到Interfaces后,继续点击save and apply
防火墙设置
接着我们设置防火墙:
选择防火墙。
编辑lan:
点击保存,这表示允许lan向vpn发送数据包。
编辑 VPN
改变这几项后,点击Save
此时便允许vpn发送过来的请求且当前路由器下的内网机器进行通讯了。
服务端客户端互通
客户端
注意:Route Allowed IPs 需要选中。最后点击save,然后再点击save.
回到网络界面后,点击restart
成功的看到数据包后,就说明我们的操作成功了。
实现(命令行)
服务端初始化
首先使用ssh连接到openwrt。
安装WireGuard
参考官网使用ssh连接到openwrt后安装服务如下:
# Install packages
opkg update
opkg install wireguard-tools
opkg install luci-proto-wireguard qrencode
service rpcd restart此时我们需要重启一次路由器,以使luci-proto-wireguard被识别。
防火墙设置
为了使外网的请求能够成功的连接到服务端的WireGuard,需要先设置一下防火墙。
首先,需要确认自己当前openwrt的内网与外网在防火墙中的标识是否为lan及wan(一般情况下,默认值就是lan和wan ),然后执行以下命令:
# 添加防火墙内网列表
uci del_list firewall.lan.network="vpn"
uci add_list firewall.lan.network="vpn"
# 删除名为"wg"的防火墙配置(为了避免多次操作对防火墙的污染)
uci -q delete firewall.wg
# 设置WireGuard规则
uci set firewall.wg="rule"
# 设置WireGuard规则的名称为"Allow-WireGuard"
uci set firewall.wg.name="Allow-WireGuard"
# 设置允许由wan外网访问WireGuard的服务端口51820(协议为UDP)
uci set firewall.wg.src="wan"
uci set firewall.wg.dest_port="51820"
uci set firewall.wg.proto="udp"
uci set firewall.wg.target="ACCEPT"
# 提交防火墙配置更改
uci commit firewall
# 重启防火墙服务以应用更改
service firewall restart上述操作主要是增加了一条规则:允许外部的请求,发起对本机51820端口的udp访问。这样其它的客户端便可以成功连接服务端的51820端口了。
生成密钥
然后我们在服务端生成服务端私钥、公钥以及用于与客户端进行认证的密钥。
# 生成服务端的私钥wgserver.key, 公钥wgserver.pub
# 生成服务端与客户端连接时认证密钥:wgclient.psk
umask go=
wg genkey | tee wgserver.key | wg pubkey > wgserver.pub
wg genpsk > wgclient.pskumask go=的作用是移除 g(roup), o(thers) 的用户的权限, 等同于:只有创建用户拥有访问权限。tee的作用是将输出展示在屏幕的同时并保存在相应的文件上。wg genkey | tee wgserver.key相当于执行wg genkey > wgserver.key | cat wgserver.key
此时屏幕上将打印一串base64编辑的字符串,该字符串即为服务端私钥。
创建VPN接口
# 适用于重复设置,先将原来的接口删除(如有)
uci -q delete network.vpn
# 设置vpn接口,协议指定为wireguard
uci set network.vpn="interface"
uci set network.vpn.proto="wireguard"
# 协议是 wireguard,所以以下配置将被 wireguard 协议解析
uci set network.vpn.private_key="这里粘贴为上一步屏幕上显示的服务端的私钥"
# 设置端口号(可以变更,注意与防火墙中设置的相同)
uci set network.vpn.listen_port="51820"
# 参考自己的网络架构设置IPV4地址
uci add_list network.vpn.addresses="192.168.99.1/24"
# 参考自己的网络架构设置IPV6地址
uci add_list network.vpn.addresses="fd00:99::1/64"上述代码创建了一个基于 wireguard 协议的新接口,并对该接口进行了相关设置,当启用该接口时,该接口便会调用wireguard协议进行解析并提供对应的服务了。
接下来我们继续在客户端的openwrt上操作。
客户端初始化
首先使用ssh连接到openwrt。
安装WireGuard
参考官网使用ssh连接到openwrt后安装服务如下:
# Install packages
opkg update
opkg install wireguard-tools
opkg install luci-proto-wireguard qrencode
service rpcd restart此时我们需要重启一次路由器,以使luci-proto-wireguard被识别。
防火墙设置
为了使隧道的请求能够成功地转发给处理lan内网的主机,需要对防火墙进行设置。
首先,需要确认自己当前openwrt的内网与外网在防火墙中的标识是否为lan及wan(一般情况下,默认值就是lan和wan ),然后执行以下命令:
# 删除后新增名为"vpn"的防火墙wan配置(为了避免多次操作对防火墙的污染)
uci del_list firewall.wan.network="vpn"
uci add_list firewall.wan.network="vpn"
# 提交防火墙配置更改
uci commit firewall
# 重启防火墙服务以应用更改
service firewall restart上述操作主要在防火墙的wan侧增加了VPN接口。
然后我们来到WEB端,继续增加防火墙配置:
上述配置允许了VPN的数据走了lan口,保障了服务端向lan口主机的请求被放行。注意要点击apply,否则不生效。
生成密钥
然后我们在服务端生成服务端私钥、公钥以及用于与客户端进行认证的密钥。
# 生成服务端的私钥wgclient.key, 公钥wgclient.pub
umask go=
wg genkey | tee wgclient.key | wg pubkey > wgclient.pub此时屏幕上将打印一串base64编辑的字符串,该字符串即为客户端私钥。
创建VPN接口
uci -q delete network.vpn
uci set network.vpn="interface"
uci set network.vpn.proto="wireguard"
uci set network.vpn.private_key="替换为客户端私钥"
uci add_list network.vpn.addresses="192.168.99.2/24"
uci add_list network.vpn.addresses="fd00:99::2/64"服务端客户端互通
首先我们需要在服务端上打印出服务端公钥:
# cat wgserver.pub 再继续在服务端上打印出共享密钥
# cat wgclient.psk接着在客户端上打印出客户端公钥
# cat wgclient.pub执行命令后打印出的字符串,即为对应的密钥。
服务端
在服务端设置一个客户端对应的peer:
uci -q delete network.wgclient
uci set network.wgclient="wireguard_vpn"
uci set network.wgclient.public_key="替换为客户端公钥"
uci set network.wgclient.preshared_key="替换为共享密钥"
uci set network.wgclient.route_allowed_ips="1"
uci add_list network.wgclient.allowed_ips="192.168.99.2/32"
uci add_list network.wgclient.allowed_ips="192.168.12.0/24"
uci add_list network.wgclient.allowed_ips="fd00:99::2/64"
# 提交并重启
uci commit network
service network restart上述命令配置了客户端的公钥,共享密码以及只有赂192.168.99.2/32 192.168.12.0/24发起请求时,才走本隧道。route_allowed_ips也很关键,它保证了自动生成路由。
需要注意的是:这里的公钥需要配置为客户端公钥,而不服务端公钥。
当前每次VPN重启后都需要重新添加,不然VPN重启后路由即失败。
客户端
客户端同样也需要添加一个服务端peer:
uci -q delete network.wgserver
uci set network.wgserver="wireguard_vpn"
uci set network.wgserver.public_key="替换为服务端公钥"
uci set network.wgserver.preshared_key="替换为共享密钥"
uci set network.wgserver.endpoint_host="8.8.8.188替换为服务端外网IP"
uci set network.wgserver.endpoint_port="51820"
uci set network.wgserver.persistent_keepalive="25"
uci set network.wgserver.route_allowed_ips="1"
uci add_list network.wgserver.allowed_ips="192.168.99.1/32"
uci add_list network.wgserver.allowed_ips="192.168.20.0/24"
uci add_list network.wgserver.allowed_ips="fd00:99::2/64"
uci commit network
service network restart上述命令设置了对等的服务端连接信息的同时,设置了只有向192.168.99.1/32及192.168.20.0/24发起请求时,才走本隧道。
route_allowed_ips作用是为allowed_ips的地址添加一条路由。
当前每次VPN重启后都需要重新添加,不然VPN重启后路由即失败。
测试
最后结合ping及tracert命令完成测试。
DDNS
由于家庭外网IP会动态的变更,所以还需要创建定时任务来解决这个服务端 IP 自动变更的问题,首先登录客户端,然后使用以下代码打印网络变量:
# uci show network在显示的内容中找到xxx.endpoint_host, 比如当前路由器显示为:
network.@wireguard_vpn[0].endpoint_host='8.8.8.188'然后根据上述的显示,在/root下创建以下脚本vpn.sh:
#!/bin/sh
# 设置域名,该域名应该是你的动态 IP 的对应的域名
DOMAIN="test.yz.club"
# 获取当前域名解析结果
CURRENT_IP=$(nslookup "$DOMAIN" | awk '/^Address: / { print $2 }')
# 读取WireGuard服务端地址(注意这个:network.@wireguard_vpn[0].endpoint_host 应该是在上一步中执行uci show network获取的值)
WG_IP=$(uci get network.@wireguard_vpn[0].endpoint_host)
# 检查当前IP是否与WireGuard服务端地址一致
if [ "$CURRENT_IP" != "$WG_IP" ]; then
echo "Detected IP change. Updating WireGuard configuration..."
# 更新WireGuard服务端地址
uci set network.@wireguard_vpn[0].endpoint_host="$CURRENT_IP"
uci commit network
echo "stoping vpn..."
ifdown vpn
sleep 10
echo "start vpn ..."
ifup vpn
echo "WireGuard configuration updated. WireGuard restarted."
else
echo "No IP change detected. Exiting."
fi接着为其添加执行权限:chmod +x vpn.sh 。
最后编辑定时任务 vi /etc/crontabs/root
加入:
*/5 * * * * /root/vpn.sh编辑完成后执行 /etc/init.d/cron restart重启定时任务。接下来当服务端的 IP 地址变更时,客户端便可以通过重新解析域名的方式自动进行重连了。
基础知识
其实配置了这么多,好像感觉很乱,但其实如果把基础的知识铺垫一下,则会有种豁然开朗的感觉。
wiregruad
在WireGuard中一个服务端可以连接多个客户端,每个客户端都需要在服务端进行配置。对于WireGuard而言,服务端与客户端是对等的关系,所以又被称为对等端。每个对等端,都通过两对非对称密钥进行通讯,所以每个对等端都需要配置一个自己的私钥以及对端的公钥。
除了非对称密钥外。还可以设置一个额外的对称密钥用于进行连接认证及密钥交换(这个未仔细研究)。
如果对这方面还不太了解,则需要学习一下计算机安全课程中的 RSA 非对称加密与DES对称加密两个著名的算法。
路由
路由决定了数据的转发情况,在IP协议中,会根据路由表对路由进行匹配,然后根据匹配的结果转发给相应的主机。
比如192.168.12.2向192.168.20.2发起请求时,则:
- 首先在路由表中对
192.168.20.2进行比对,发现其与路由表信息192.168.20.0/24匹配成功;则将数据尝试转发给192.168.20.0/24路由表对应的出口192.168.99.1 - 接着在将
192.168.99.1与路由表进行比对,发现其与路由表信息192.168.99.1/32匹配成功,则将数据转发给链路VPN.
192.168.99.1在接收到请求时,同样使用目标地址192.168.20.2与自己的路由表进行匹配。
最终将数据转发给接口lan。
防火墙
openwrt内置了防火墙,这也是家庭路由器的通用做法。由于防火墙默认关闭了外网对内网的一切请求,所以这从很大程序上保证了家庭网络的安全性。
所以无论对于服务端还是客户端而言,想主动的响应外部的请求,则必须制定相应的防火墙开放策略。
如果当路由表没有问题,隧道已成功建立后,网络仍然不可达,则排查的方向就应该向防火墙转移了。
总结
通过WireGuard可以不算简单的完成两个局域网间的资源共享。这实现的过程中,我们使用到了https协议中同样使用的对称加密算法以及非对称加密算法。同时见证了虚拟隧道技术的伟大,同时又根据计算机网络中学习的路由、防火墙技术完成了这一技术实现。
学而时习之,不亦乐乎?---- 将课本上学习到理论知识,经常性应用到现实的场景中,这难度不是一件非常快乐的事情吗?
习 不等于 复习; 习更多的应该是练习的习,习武的习。学习知识后复习并不快乐,只有应用起来的时候才会感觉快乐。
参考资料:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。