
Normal view
利用 WireGuard 搭建 VPN 用于远程访问家庭局域网

我家的路由器和 NAS

把红米 AX5 路由器刷成了 OpenWrt 系统

在 VPN 场景下的跨子网通信防火墙配置方法
这篇文章介绍手搓 Site-to-Site 组网的方法。
1 场景简述
假设我们有两个机器 A 与 B,其中 A 和 B 在各自的局域网中的网段分别是 192.168.2.0/24 和 192.168.3.0/24。A 和 B 都接入了一个 VPN 网络,其内网地址是 10.0.0.0/16。那么,如何让 A 访问 B 所在局域网内的另一台机器 C 呢?注意 C 是没有接入到 VPN 私有网络的。一个办法是直接让 C 也接入到 VPN 网络中,但是如果 B 的局域网中有众多设备需要被 A 访问时,将这些节点都加入 VPN 网络配置会非常麻烦。在一些场景下,VPN 的连接数量有限制(例如 Zerotier)。因此我们需要新的方案。这篇文章我们介绍通过巧妙地配置路由表和防火墙来实现。
我们假定 A 的地址是 192.168.2.2/24 和 10.0.0.2/16,B的地址是 192.168.3.2/24 和 10.0.0.3/16,C 的地址是 192.168.3.3/24。我们这里介绍的是让 A 访问 B 所在的子网。反过来让 B访问 A的子网的设置方法是类似的。进行双向设置以后就可以实现完整的 Site-to-Site 组网。这里我们假设 A 和 B 都安装了 Ubuntu 操作系统。
2 路由配置
首先我们需要进行路由表的配置,让 A 知道应该以 B 为网关来来访问 192.168.3.0/24 子网。在 A 上运行
1 | sudo ip route add 192.168.3.0/24 via 10.0.0.3 |
然后,我们需要在 B 上启用 IP 转发。在Linux系统中开启IP转发功能,可以通过以下步骤:
- 临时开启(系统重启后失效)
- 对于基于Debian或Ubuntu的系统,可以使用以下命令:
- 查看当前IP转发状态:
cat /proc/sys/net/ipv4/ip_forward
,如果输出为0,则表示IP转发功能是关闭的;如果输出为1,则表示IP转发功能是开启的。 - 开启IP转发:
sudo sysctl -w net.ipv4.ip_forward=1
。这条命令会在运行时修改内核参数,使系统能够进行IP转发。
- 查看当前IP转发状态:
- 对于基于Red Hat或CentOS的系统,操作如下:
- 查看IP转发状态:
cat /proc/sys/net/ipv4/ip_forward
。 - 开启IP转发:
sudo sysctl -w net.ipv4.ip_forward=1
。这会立即生效,但是在系统重启后会恢复到原来的设置。
- 查看IP转发状态:
- 对于基于Debian或Ubuntu的系统,可以使用以下命令:
- 永久开启(系统重启后依然有效)
- 在Debian或Ubuntu系统中:
- 编辑
/etc/sysctl.conf
文件:sudo nano /etc/sysctl.conf
。 - 在文件中找到
#net.ipv4.ip_forward=1
这一行(如果没有这一行,可以添加),将前面的#
去掉,使这一行变为net.ipv4.ip_forward = 1
。保存并退出文件。 - 使配置生效:
sudo sysctl -p
。这条命令会重新加载sysctl.conf
文件中的配置,使IP转发功能在系统重启后依然有效。
- 编辑
- 在Red Hat或CentOS系统中:
- 编辑
/etc/sysctl.conf
文件:sudo vi /etc/sysctl.conf
。 - 找到
net.ipv4.ip_forward = 0
这一行,将0改为1。保存并退出文件。 - 执行
sudo sysctl -p
命令,让配置生效,这样在系统重启后IP转发功能也会开启。
- 编辑
- 在Debian或Ubuntu系统中:
3 防火墙配置
完成路由配置之后,A 机器能够知道到达 192.168.3.0/24 子网的路由路径,但是此时让 A 直接访问 C 的地址会失败,因为他们之间的通信会被 B 的防火墙拦截。因此我们需要对防火墙进行进一步配置。防火墙的配置分为两个主要的步骤:首先我们需要允许经过 B进行路由的数据包经过防火墙。在 B 上运行下面的代码:
1 | sudo iptables -A FORWARD -i tun0 -o enp2s0 -s 10.0.0.0/16 -d 192.168.3.0/24 -j ACCEPT |
注意其中的 -i
-o
指定的接口名称需要针对你的服务器的实际情况调整。这里我们进行的往返路由路径的分别配置,将路由策略都设置为 ACCEPT
使得数据包能够通过 B 的防火墙。
对防火墙进行配置的第二个步骤是在 B 上开启地址伪装,即 MASQUERADE 机制。注意到我们在 A 上配置了路由表,但是没有作为访问目标的 C 则并不知道通向 A 的路由路径(A 和 B 之间在的 VPN 私有网络中是直通的,不需要额外路由)。因此我们需要配置进行一个额外的配置,即在 B 中开启地址伪装,此时 B 会将来自 A 的,以 C 为目标的数据包的源地址修改为自身的地址,那么 C 会认为这个包来自 B,响应也会被发送给 B,B 再将包转发回到 A。要开启地址伪装,需要在 B 上开启 iptables 配置:
1 | sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/16 -o enp2s0 -j MASQUERADE |
注意上面的命令结果会在重启后失效。要持久化 iptables 命令的配置,可以采用下面的方法:
- 使用iptables - save和iptables - restore命令(适用于大多数Linux发行版)
- 保存配置:
- 首先,使用
iptables - save
命令可以将当前的iptables
规则保存到一个文件中。例如,可以将规则保存到/etc/iptables.rules
文件中,命令如下:sudo iptables - save > /etc/iptables.rules
。 - 这个命令会将当前活动的
iptables
规则以文本格式输出并保存到指定的文件。输出的内容包含了表(如filter
、nat
、mangle
)以及每个表中的链(如INPUT
、OUTPUT
、FORWARD
)的规则。
- 首先,使用
- 恢复配置:
- 要在系统启动时自动恢复这些规则,可以在合适的启动脚本中添加
iptables - restore
命令。例如,在基于Debian或Ubuntu的系统中,可以创建一个脚本文件(如/etc/network/if - post - up.d/iptables - restore
),并在其中添加以下内容:然后给这个脚本文件添加可执行权限:1
2#!/bin/sh
iptables - restore < /etc/iptables.rulessudo chmod +x /etc/network/if - post - up.d/iptables - restore
。 - 在基于Red Hat或CentOS的系统中,可以将
iptables - restore
命令添加到/etc/rc.d/rc.local
文件中(不过要确保rc.local
文件有可执行权限),如:iptables - restore < /etc/iptables.rules
。这样,在系统启动后,iptables
规则就会根据保存的文件进行恢复。
- 要在系统启动时自动恢复这些规则,可以在合适的启动脚本中添加
- 保存配置:
- 使用iptables - persistent工具(适用于Debian和Ubuntu系统)
- 安装iptables - persistent:
- 在Debian或Ubuntu系统中,可以使用以下命令安装
iptables - persistent
工具:sudo apt - get install iptables - persistent
。 - 这个工具会自动在安装过程中保存当前的
iptables
规则,并且会创建一些系统服务相关的配置来确保规则在系统启动时自动加载。
- 在Debian或Ubuntu系统中,可以使用以下命令安装
- 保存和恢复规则:
- 安装完成后,
iptables
规则会被自动保存到/etc/iptables
目录下的文件中(/etc/iptables/rules.v4
用于IPv4规则,/etc/iptables/rules.v6
用于IPv6规则)。 - 在系统启动时,
iptables - persistent
服务会自动读取这些文件中的规则并恢复iptables
配置。如果后续修改了iptables
规则,想保存新规则,可以使用sudo iptables - save > /etc/iptables/rules.v4
(对于IPv4)和sudo iptables - save > /etc/iptables/rules.v6
(对于IPv6)命令手动保存,或者使用dpkg - reconfigure iptables - persistent
命令重新配置并保存规则。
- 安装完成后,
- 安装iptables - persistent:
- 使用systemd服务(适用于有systemd的Linux发行版)
- 创建systemd服务单元文件:
- 可以创建一个自定义的
systemd
服务单元文件来保存和恢复iptables
规则。例如,创建一个名为iptables - save - service.service
的文件,内容如下:1
2
3
4
5
6
7
8[Unit]
Description=Save iptables rules
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/iptables - save > /etc/iptables.rules
[Install]
WantedBy=multi - user.target - 将这个文件保存到
/etc/systemd/system/
目录下,然后使用systemd
命令来管理这个服务。
- 可以创建一个自定义的
- 启用和启动服务:
- 执行
sudo systemd - enable iptables - save - service.service
命令来设置服务在系统启动时自动运行。 - 可以使用
sudo systemd - start iptables - save - service.service
命令来手动启动服务,保存当前的iptables
规则。 - 同时,还需要创建一个用于恢复规则的服务单元文件,例如
iptables - restore - service.service
,内容如下:1
2
3
4
5
6
7
8[Unit]
Description=Restore iptables rules
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/iptables - restore < /etc/iptables.rules
[Install]
WantedBy=multi - user.target - 同样将这个文件保存到
/etc/systemd/system/
目录下,然后使用systemd - enable iptables - restore - service.service
命令来设置在系统启动时自动恢复规则,并且可以使用systemd - start iptables - restore - service.service
命令手动启动恢复规则的服务。
- 执行
- 创建systemd服务单元文件:
解决在 Openvpn 客户端中部署的 Web 服务不可用的问题
遇到这样一个蛋疼的问题,我有一台服务器,上面部署了一个 web 服务,同时我也想把这个服务器作为客户端连入一个 OpenVPN 虚拟网络,并且我希望服务器上的程序能够通过 OpenVPN 的网关来访问外部网络,这主要是为了隐藏服务器的身份。但是在 OpenVPN 连接之后,原有的 Web 服务将无法访问。通过调试分析可以法线,造成这一现象的原因是 Web 服务的响应包也被路由配置路由到 OpenVPN 的 tun0 接口中。这导致响应无法返回给原来的请求服务器。如何解决这个问题呢?
如果你在网络上搜索这类问题你会发现各种文章给出的方案一般都是为 OpenVPN 的配置文件添加 route-nopull
选项来阻止 OpenVPN 设置客户端路由,但是这会导致客户端的对外访问无法通过 VPN 进行。事实上,从 IP 路由的角度来看,如果我的服务器是 A,而某个访问 Web 服务的 IP 是 B,同时 B 也可能是 A 试图访问的外部的目标服务提供者。对传输层路由而言,A 给 B 的 Web 服务响应和 A 主动发往 B 的请求是无法区分的。从这个角度来看,要达成 inbound 和 outbound 流量分别路由似乎是不可能的。
但是其实我们有一个突破点:那就是 Web 服务总是在本机使用固定的端口和请求者通信,我们可以通过端口来区分 inbound 和 outbound 流量。首先我们输入下面的命令:
1 | iptables -t mangle -A OUTPUT -p tcp -m multiport --sports 80,443 -j MARK --set-mark 1 |
在这个命令中,我们会让路由表观察从本地的 80 和 443 端口发出的包,并给这些包打上一个特定的标签(--set-mark 1
的作用)。然后输入下面的命令:
1 | ip route add default via [gateway addr of eth0] dev eth0 table 100 |
这个命令会创建一个新的路由表 table 100
,然后在这个表中加入一个默认路由,将流量送往原有的网关。然后再运行下面的命令:
1 | ip rule add fwmark 1 table 100 |
这个命令会让带有标记 1
的包使用 table 100
路由表进行路由。运行完上面的配置后,web 服务就可以正常访问呢了。
以上方法是在 ChatGPT 的协助下摸索出来的,我的提问是:
如何通过 iptables 或者 route 命令,让访问 80 与 443 端口的包使用 tun0 接口,而访问其他端口的包使用 eth0 接口?
GPT 的回答是
可以使用iptables和route命令结合起来实现此目的。具体操作如下:
首先需要添加一条规则,将访问80和443端口的包标记为一个特定的标记值(比如"1"),使用tun0接口发送出去。iptables规则如下:
1 | iptables -t mangle -A OUTPUT -p tcp -m multiport --dports 80,443 -j MARK --set-mark 1 |
接着需要添加一条规则,将标记为"1"的包使用tun0接口发送出去。route命令如下:
1 | ip route add default dev tun0 table 100 |
最后需要添加一条规则,将所有未被标记的包使用eth0接口发送出去。route命令如下:
1 | ip route add default dev eth0 |
以上三个步骤分别实现了:将访问80和443端口的包标记,并使用tun0接口发送出去;将标记为"1"的包使用tun0接口发送出去;将所有未被标记的包使用eth0接口发送出去。这样就实现了访问80和443端口的包使用tun0接口,访问其他端口的包使用eth0接口的效果。