65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2023 年 2 月 14 日

CPOL

7分钟阅读

viewsIcon

10009

downloadIcon

56

用于实现真正随机化和启用断网开关的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.shdown.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_lancustom_wireless)和修改脚本来使用与其他人不同的隧道。希望读者能发挥一点想象力,并以上述脚本为参考。

其他说明

此方法已在 OpenWRT 版本 22.03 上测试过稳定性。但它有望在未来的 OpenWRT 版本以及 19.07 版本上也能正常工作。如果我有机会在 19.07 上进行测试,我会在此处发布。祝您浏览愉快!

历史

  • 2023 年 2 月 14 日:初始版本
© . All rights reserved.