OpenWRT路由器上的自愈随机VPN,带断网开关





5.00/5 (3投票s)
用于实现真正随机化和启用断网开关的VPN路由器的脚本
引言
熟悉 OpenWRT 路由器上启用 VPN 的标准方法的人(如 OpenWRT 网站 此处 和 此处 所述)会知道,它不支持“kill switch”,这意味着当 openvpn
命令未运行时或已退出时,会阻止来自接口的流量。如果 openvpn
命令因断开连接或其他原因而失败,网络将回退到 WAN 接口,客户端的互联网流量会泄漏到常规网络,直到命令重新启动。本文描述了如何在 LAN 和无线接口上实现 VPN kill switch,以便在 openvpn
命令处于活动状态时,客户端只能从隧道访问互联网,而在命令尚未运行或直到隧道自动恢复(在发生故障或连接不良的情况下)时被阻止。
本文还描述了每次 openvpn
命令运行时如何从目录中随机选择一个配置文件,从而增加连接到哪个 VPN 服务器的随机性。
在 OpenWRT 路由器上实施上述概念将使任何网络在免受 ISP 跟踪或日志记录方面更加安全。本文主要介绍了基本的配置和一些脚本,这些脚本可以在 OpenWRT 路由器上实现这种行为。
设置 OpenWRT
本文假设读者拥有一个刚刷机或重置的 OpenWRT 路由器,并已完成初始配置。在 http://192.168.1.1/cgi-bin/luci/admin/system/admin/password 设置主管理员密码后,可以通过 ssh root@192.168.1.1
访问路由器终端。首先,必须执行以下命令将 LAN 接口设置为 192.168.2.1
。
uci set network.lan.proto='static'
uci set network.lan.ipaddr='192.168.2.1'
uci set network.lan.netmask='255.255.255.0'
uci commit network
/etc/init.d/network restart
此时,ssh
连接将断开,因为 LAN 接口已更改,现在可以通过 ssh root@192.168.2.1
访问终端。以下命令将设置一个新接口,用于无线设备,IP 地址为 192.168.3.1
。
uci set network.wifi24='interface'
uci set network.wifi24.proto='static'
uci set network.wifi24.ipaddr='192.168.3.1'
uci set network.wifi24.netmask='255.255.255.0'
uci set dhcp.wifi24='dhcp'
uci set dhcp.wifi24.interface='wifi24'
uci add_list firewall.@zone[0].network='wifi24'
uci commit dhcp
uci commit network
uci commit firewall
/etc/init.d/firewall restart
/etc/init.d/network restart
此新接口可以在 luci 中的 http://192.168.2.1/cgi-bin/luci/admin/network/wireless 与无线电关联。在继续下一步之前,应在此 LAN 设备和无线设备上验证互联网访问。
以下命令将在路由器上安装 openvpn
opkg update
opkg install openvpn-openssl
这是本文所需的唯一软件包,我们可以继续下一步,为 openvpn
命令创建的隧道 tun0
创建一个名为 ovpn
的接口。在终端中执行以下命令来创建接口。
uci set network.ovpn='interface'
uci set network.ovpn.proto='none'
uci set network.ovpn.ifname='tun0'
uci commit network
uci add_list firewall.@zone[1].network='ovpn'
uci commit firewall
/etc/init.d/firewall restart
/etc/init.d/network restart
可以在 http://192.168.2.1/cgi-bin/luci/admin/network/network 验证当前不活动的接口的创建。
防止 DNS 泄露
尽管主要流量通过隧道路由,但仍会使用 WAN 的 DNS 地址,从而将 DNS 信息泄露到本地网络。以下配置可以避免这种情况。
uci set network.wan.peerdns='0'
uci set network.ovpn.peerdns='0'
uci commit network
/etc/init.d/network restart
设置文件夹和文件
需要存在一个文件夹,其中包含所有 *.ovpn 配置文件,脚本将从中随机选择一个传递给 openvpn
命令,还需要一个文件来存储 VPN 提供商的凭据。执行以下命令来创建文件夹和文件。
mkdir /etc/openvpn/configs
touch /etc/openvpn/credentials
此 configs 文件夹必须用 VPN 提供商的 ovpn
配置文件填充,并且可以按以下方式用凭据填充
echo "<username of vpn provider>" >> /etc/openvpn/credentials
echo "<password of vpn provider>" >> /etc/openvpn/credentials
测试 VPN 隧道和配置
此时,您可以通过使用任何配置文件执行以下测试命令来测试设置。
openvpn –config /etc/openvpn/configs/<random-config-file>
--dev tun0 –auth-user-pass /etc/openvpn/credentials
现在应该成功执行,并且应该创建一个新的隧道 tun0
。可以使用新的路由器终端来验证通过隧道的 ping。
ping -I tun0 8.8.8.8
在继续下一步之前,应从 LAN 和无线设备验证互联网连接。正如读者将注意到的,所有流量现在都受到隧道的保护,但当 openvpn
命令退出时,流量将回退到 wan
网络。我们现在准备实现“kill switch”,即当 openvpn
命令不活动时,阻止 LAN 和无线设备的互联网访问。
设置 Kill Switch 脚本
必须在路由器上创建一个新的路由表,将来自 LAN 和无线接口的流量路由到 openvpn
命令创建的隧道,并且可以通过脚本动态修改此新表上的路由来实现 kill switch。
创建新的路由表
您可以修改文件 /etc/iproute2/rt_tables 来添加一个我们将称之为 custom
的新表。
cat /etc/iproute2/rt_tables
完成修改后,表应如下所示。添加的行是 40 custom
。
#
# reserved values
#
128 prelocal
255 local
254 main
253 default
40 custom
0 unspec
#
# local
#
创建 Kill Switch 脚本
创建一个名为 /etc/openvpn/kill-switch-setup.sh 的新文件,内容如下
#!/bin/sh
ip route flush table custom
interface_cidr=192.168.2.0/24
interface_name=br-lan
interface_gateway=192.168.2.1
ip route flush $interface_cidr
ip rule add from $interface_cidr lookup custom
ip rule add from all to $interface_cidr lookup custom
ip route add $interface_cidr dev $interface_name scope link
src $interface_gateway table custom
ip route add default via $interface_gateway table custom
interface_cidr=192.168.3.0/24
interface_name=wifi24
interface_gateway=192.168.3.1
ip route flush $interface_cidr
ip rule add from $interface_cidr lookup custom
ip rule add from all to $interface_cidr lookup custom
ip route add $interface_cidr dev $interface_name scope link
src $interface_gateway table custom
ip route add default via $interface_gateway table custom
使文件可执行
chmod +x /etc/openvpn/kill-switch-setup.sh
执行此脚本 ./kill-switch-setup.sh
将阻止 LAN 和 wifi 接口的互联网访问,直到自定义表由本文稍后描述的脚本更新,但仍然可以访问 ssh
和 luci 接口。可以通过执行脚本来测试脚本的功能,要恢复互联网,可以使用以下命令重新启动网络
/etc/init.d/network restart
如上命令所示,每次重启网络时,ip 规则和路由都会恢复到原始状态,互联网也会随之恢复。如果您由于故障无法访问 ssh 或互联网,重新启动路由器将解决问题。
创建 OpenVPN 命令“Up”脚本
下面的脚本 up.sh 将在 openvpn
命令成功时执行,通过将其传递给 openvpn
参数 --up
。这将使用 ip 路由更新自定义路由,将流量路由到创建的隧道。不应直接从命令行执行此脚本,因为 openvpn
命令本身将执行它并传递所需的参数。
创建一个名为 /etc/openvpn/up.sh 的新文件,内容如下
!/bin/sh
wanstrifconfig=$(ip -4 -o addr show wan)
wan_cidr=$(echo $wanstrifconfig |
grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\/[0-9]\{1,\}')
wan_interface_name=wan
vpn_table_name=custom
vpn_gateway=$route_vpn_gateway
vpn_local=$ifconfig_local
vpn_mask=$ifconfig_netmask
remote_ip=$trusted_ip
device_name=$dev
router_gateway=$route_net_gateway
vpn_interface_cidr=`awk -v val="$vpn_gateway|$vpn_mask" '
function count1s(N){
c = 0
for(i=0; i<8; ++i) if(and(2**i, N)) ++c
return c
}
function subnetmaskToPrefix(input) {
split(input, inputParts, "|")
split(inputParts[2], subnetParts, ".")
split(inputParts[1], mainParts, ".")
if (subnetParts[1] == 0 ) {
mainParts[1] = 0
}
if (subnetParts[2] == 0 ) {
mainParts[2] = 0
}
if (subnetParts[3] == 0 ) {
mainParts[3] = 0
}
if (subnetParts[4] == 0 ) {
mainParts[4] = 0
}
printf "%d.%d.%d.%d/%d", mainParts[1], mainParts[2],
mainParts[3], mainParts[4], count1s(subnetParts[1]) + count1s }
BEGIN {
subnetmaskToPrefix(val)
}'`
ip route add 0.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
ip route add 128.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
ip route add $vpn_interface_cidr dev $device_name scope link src
$vpn_local table $vpn_table_name
ip route add $remote_ip via $router_gateway dev wan table $vpn_table_name
ip route add $wan_cidr dev $wan_interface_name table $vpn_table_name
ip route flush cache
使文件可执行
chmod +x /etc/openvpn/up.sh
创建 OpenVPN 命令“Down”脚本
下面的脚本 down.sh 将在 openvpn
命令因断开连接或 ping 失败而退出时执行,通过将其传递给参数 --down
。这将从自定义路由表中删除 up.sh 命令创建的路由,并且互联网将从接口上阻止。不应直接从命令行执行此脚本,因为 openvpn
命令本身将执行它并传递所需的参数。
创建一个名为 /etc/openvpn/down.sh 的新文件,内容如下
!/bin/sh
wanstrifconfig=$(ip -4 -o addr show wan)
wan_cidr=$(echo $wanstrifconfig |
grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\/[0-9]\{1,\}')
wan_interface_name=wan
vpn_table_name=custom
vpn_gateway=$route_vpn_gateway
vpn_local=$ifconfig_local
vpn_mask=$ifconfig_netmask
remote_ip=$trusted_ip
device_name=$dev
router_gateway=$route_net_gateway
vpn_interface_cidr=`awk -v val="$vpn_gateway|$vpn_mask" '
function count1s(N){
c = 0
for(i=0; i<8; ++i) if(and(2**i, N)) ++c
return c
}
function subnetmaskToPrefix(input) {
split(input, inputParts, "|")
split(inputParts[2], subnetParts, ".")
split(inputParts[1], mainParts, ".")
if (subnetParts[1] == 0 ) {
mainParts[1] = 0
}
if (subnetParts[2] == 0 ) {
mainParts[2] = 0
}
if (subnetParts[3] == 0 ) {
mainParts[3] = 0
}
if (subnetParts[4] == 0 ) {
mainParts[4] = 0
}
printf "%d.%d.%d.%d/%d", mainParts[1], mainParts[2], mainParts[3],
mainParts[4], count1s(subnetParts[1]) + count1s }
BEGIN {
subnetmaskToPrefix(val)
}'`
ip route flush 0.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
ip route flush 128.0.0.0/1 via $vpn_gateway dev $device_name table $vpn_table_name
ip route flush $vpn_interface_cidr dev $device_name scope link src
$vpn_local table $vpn_table_name
ip route flush $remote_ip via $router_gateway dev wan table $vpn_table_name
ip route flush $wan_cidr dev $wan_interface_name table $vpn_table_name
ip route flush cache
使文件可执行
chmod +x /etc/openvpn/down.sh
设置 OpenVPN 客户端启动脚本
由于 openvpn
命令在执行时会更新内核路由表,因此无法实现 kill switch,因此必须传递额外的参数,阻止它自行向路由表添加任何路由,并要求它执行上面描述的 up.sh 和 down.sh 命令来将路由添加到自定义路由表中。下面的脚本 start-openvpn-client.sh 还从 /etc/openvpn/configs 文件夹中选择一个随机的 *.ovpn 配置文件,并使用所需的参数执行 openvpn
命令来启用 kill switch 设置。
创建 OpenVPN 随机化客户端启动脚本
创建一个名为 /etc/openvpn/start-openvpn-client.sh 的新文件,内容如下
dir='/etc/openvpn/configs'
n_files=`/bin/ls -1 "$dir" | wc -l | cut -f1`
rand_num=`awk "BEGIN{srand();print int($n_files * rand()) + 1;}"`
file=`/bin/ls -1 "$dir" | sed -ne "${rand_num}p"`
path=`cd $dir && echo "$PWD/$file"` # Converts to full path.
echo "Chosen file ${path}"
echo "${path}" > /tmp/openvpn-server.log
openvpn --config ${path} --auth-user-pass /etc/openvpn/credentials
--up /etc/openvpn/up.sh --down-pre --down /etc/openvpn/down.sh --route-noexec
--dev tun0 --ping 10 --ping-exit 60 --persist-local-ip --script-security 2
使文件可执行
chmod +x /etc/openvpn/start-openvpn-client .sh
现在可以通过执行 ./start-openvpn-client.sh 直接测试此脚本,并通过 ping 隧道来验证。由于此脚本不影响主路由表,因此可以启动和退出而不会破坏任何内容。如果在执行 ./kill-switch-setup.sh 之后运行此脚本,则启用了 kill switch 的 LAN 和无线接口的隧道互联网将激活。在进行下一步之前,需要验证此脚本的所有效果——即隧道互联网在脚本运行时在 LAN 和无线设备上工作,并在脚本退出时被阻止。
创建入口点脚本
下面的脚本 start.sh 将在 LAN 和无线接口上激活 kill switch,并启用一个自愈 openvpn
命令,该命令在退出时自行重新启动。此脚本旨在在路由器启动并激活 WAN 和 LAN 等接口后自动运行。
创建一个名为 /etc/openvpn/start.sh 的新文件,内容如下
sh /etc/openvpn/kill-switch-setup.sh
sh /etc/openvpn/start-openvpn-client.sh
exec sh /etc/openvpn/start-openvpn-client.sh
使文件可执行
chmod +x /etc/openvpn/start.sh
要手动测试此命令,请重启路由器,执行 ./start.sh,并验证隧道 ping 和 LAN 及无线设备上的受保护互联网访问。
将入口脚本添加到路由器启动
如上所述,start.sh 仅应在 WAN 和 LAN 接口激活后运行,因此需要创建一个新的 hotplug
脚本。
创建一个名为 /etc/hotplug.d/iface/99-ifup-wan-lan 的新文件,内容如下
!/bin/sh
if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "wan" ]
then
rm /tmp/wanstate
echo started >> /tmp/wanstate
wanstateret=`cat /tmp/wanstate`
lanstateret=`cat /tmp/lanstate`
vpnstateret=`cat /tmp/vpnstate`
wanstarted=`echo "$wanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
lanstarted=`echo "$lanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
vpnstarted=`echo "$vpnstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
st=1
if [ $wanstarted -eq $st ] && [ $lanstarted -eq $st ] && [ $vpnstarted -eq 0 ]
then
echo started >> /tmp/vpnstate
(sh /etc/openvpn/start.sh >/dev/null 2>&1 )&
fi
fi
if [ "${ACTION}" == "ifup" ] && [ "${DEVICE}" = "br-lan" ]
then
rm /tmp/lanstate
echo started >> /tmp/lanstate
wanstateret=`cat /tmp/wanstate`
lanstateret=`cat /tmp/lanstate`
vpnstateret=`cat /tmp/vpnstate`
wanstarted=`echo "$wanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
lanstarted=`echo "$lanstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
vpnstarted=`echo "$vpnstateret" started | awk '{ print ($1 == $2) ? 1 : 0 }'`
st=1
if [ $wanstarted -eq $st ] && [ $lanstarted -eq $st ] && [ $vpnstarted -eq 0 ]
then
echo started >> /tmp/vpnstate
(sh /etc/openvpn/start.sh >/dev/null 2>&1 )&
fi
fi
exit 0
添加此文件后,重新启动路由器将自动激活 **kill switch VPN 模式**。
进一步修改
由于主路由表不受此方法的影响,因此可以添加其他 WAN 接口并启用接口的拆分隧道。某些接口可以通过添加额外的路由表(如 custom_lan
或 custom_wireless
)和修改脚本来使用与其他人不同的隧道。希望读者能发挥一点想象力,并以上述脚本为参考。
其他说明
此方法已在 OpenWRT 版本 22.03 上测试过稳定性。但它有望在未来的 OpenWRT 版本以及 19.07 版本上也能正常工作。如果我有机会在 19.07 上进行测试,我会在此处发布。祝您浏览愉快!
历史
- 2023 年 2 月 14 日:初始版本