6

本文适用于拥有两个家庭网络,并且最少有一个家庭网络是拥有外网IP,然后希望可以在两个家庭网络中自由共享网络资源的情景。

技术选型

可以实现虚拟隧道技术的技术有很多,比如大名鼎鼎的OpenXXX。本文抛弃OpenXXX从而选择WireGuard,原因大体有两个:

  1. OpenXXX的服务端在openwrt中的表现实测中,无法打印日志,所以也就没有办法在无法启动服务时第一时间排查原因。
  2. OpenXXX可能由于其太过于隐蔽自己行为的原因变得难以追踪,所以当文章中的出现的技术又恰好适用于FQ时,会导致发文不成功。

网络架构设计

image.png

  1. 服务端拥有运营商分配的外网地址,比如:8.8.8.188
  2. 客户端仅仅拥有内网IP。
  3. 服务端的openwrt的内网IP地址为:192.168.20.1/24
  4. 客户端的openwrt的内网IP地址为:192.168.12.1/24
  5. 服务端WireGuard的隧道IP地址为:192.168.99.1/24
  6. 客户端WireGuard的隧道IP地址为:192.168.99.2/24
注意:两个局域网中的网络段不能够冲突。

目标

  1. 192.168.12.2 能够与 192.168.20.2 通讯
  2. 192.168.20.2 能够与 192.168.12.2 通讯
  3. 除双方需要与对方通信时,才走VPN网络。其它的通讯不受VPN的影响。

实现(图形界面)

服务端

todo

客户端

安装WireGuard

先安装: wireguard-tools

image.png

再安装: luci-proto-wireguard

image.png

最后安装qrencode:

image.png

最后重新启动路由器。

创建VPN接口

image.png

为接口起个名字,比如vpn,然后选择协议为WireGuard VPN,点击创建接口。

image.png

如上图生成密钥对,复制Public Key备用,同时设置该接口的网络地址为:192.168.99.2

然后点击 Firewall Setting 选项卡:

image.png

在最下方的输入框中输入vpn并回车,此时将会为我们创建一个vpn区域。最后回来Generate Setting ,点击右下角的 Save.

image.png

回到Interfaces后,继续点击save and apply

防火墙设置

接着我们设置防火墙:

image.png

选择防火墙。

image.png

编辑lan:

image.png

点击保存,这表示允许lan向vpn发送数据包。

image.png

编辑 VPN

image.png

改变这几项后,点击Save

image.png

此时便允许vpn发送过来的请求且当前路由器下的内网机器进行通讯了。

服务端客户端互通

客户端

image.png

image.png

image.png

image.png

注意:Route Allowed IPs 需要选中。最后点击save,然后再点击save.

image.png

回到网络界面后,点击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的内网与外网在防火墙中的标识是否为lanwan(一般情况下,默认值就是lanwan ),然后执行以下命令:

# 添加防火墙内网列表
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.psk
  • umask 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的内网与外网在防火墙中的标识是否为lanwan(一般情况下,默认值就是lanwan ),然后执行以下命令:

# 删除后新增名为"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端,继续增加防火墙配置:

image.png

上述配置允许了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/32192.168.20.0/24发起请求时,才走本隧道。

route_allowed_ips作用是为allowed_ips的地址添加一条路由。

当前每次VPN重启后都需要重新添加,不然VPN重启后路由即失败。

测试

最后结合ping及tracert命令完成测试。

image.png

image.png

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发起请求时,则:

  1. 首先在路由表中对 192.168.20.2进行比对,发现其与路由表信息 192.168.20.0/24 匹配成功;则将数据尝试转发给192.168.20.0/24路由表对应的出口192.168.99.1
  2. 接着在将 192.168.99.1 与路由表进行比对,发现其与路由表信息 192.168.99.1/32 匹配成功,则将数据转发给链路VPN.

image.png

192.168.99.1在接收到请求时,同样使用目标地址192.168.20.2与自己的路由表进行匹配。

image.png

最终将数据转发给接口lan。

防火墙

openwrt内置了防火墙,这也是家庭路由器的通用做法。由于防火墙默认关闭了外网对内网的一切请求,所以这从很大程序上保证了家庭网络的安全性。

所以无论对于服务端还是客户端而言,想主动的响应外部的请求,则必须制定相应的防火墙开放策略。

如果当路由表没有问题,隧道已成功建立后,网络仍然不可达,则排查的方向就应该向防火墙转移了。

总结

通过WireGuard可以不算简单的完成两个局域网间的资源共享。这实现的过程中,我们使用到了https协议中同样使用的对称加密算法以及非对称加密算法。同时见证了虚拟隧道技术的伟大,同时又根据计算机网络中学习的路由、防火墙技术完成了这一技术实现。

学而时习之,不亦乐乎?---- 将课本上学习到理论知识,经常性应用到现实的场景中,这难度不是一件非常快乐的事情吗?

习 不等于 复习; 习更多的应该是练习的习,习武的习。学习知识后复习并不快乐,只有应用起来的时候才会感觉快乐。

参考资料:


潘杰
3.1k 声望239 粉丝