多个 OpenVPN 客户端和带有 bind.so 的绑定接口 - 不起作用

多个 OpenVPN 客户端和带有 bind.so 的绑定接口 - 不起作用

编辑
在按照@MariusMatutiae 提供的介绍链接后,我通过编写脚本来生成路由表和规则解决了该问题policy routing


我试图在运行 Minibian 的 Raspberry Pi 上运行两个 OpenVPN 客户端,并使用 bind.so 将特定应用程序 (get_iplayer) 绑定到其中一个 VPN,如所述这里这里。我最初按照指南这里,按照那里的指示安装iproute、下载和编译。bind.so

VPN
我正在使用 Private Internet Access 提供的配置文件。

一个 VPN 指向他们的瑞士服务器,使用 udp,我设置了选项dev tun0,因为我希望这是所有流量都通过的主隧道,除了我通过使用 明确声明的流量bind.so。这个隧道工作正常,所有流量似乎都通过它。

第二个 VPN 指向英国伦敦的服务器,使用 TCP,并设置dev tun1为充当第二条隧道。运行时,此隧道似乎运行正常通过它自己。我可以正确运行 get_iplayer。

当我同时运行两个实例时,就会出现问题。tun1即使我尝试使用 bind.so 和LD_PRELOAD上述链接中解释的方法,似乎也没有流量通过接口。

绑定
据我所知,我已经正确编译了 bind.so,并将其复制到/usr/lib等。令人沮丧的是,我确实让它工作了一次但我不知道这是怎么发生的。

命令
我一直在使用它ip route来查找网关地址;我确信我使用的是正确的 IP 地址。例如:

$ ip route
0.0.0.0/1 via 10.30.1.17 dev tun1 
0.0.0.0/1 via 10.198.1.5 dev tun0 
default via 192.168.1.254 dev eth0 
10.30.1.1 via 10.30.1.17 dev tun1 
10.30.1.17 dev tun1  proto kernel  scope link  src 10.30.1.18 
10.198.1.1 via 10.198.1.5 dev tun0 
10.198.1.5 dev tun0  proto kernel  scope link  src 10.198.1.6 
104.238.169.140 via 192.168.1.254 dev eth0 
128.0.0.0/1 via 10.30.1.17 dev tun1 
128.0.0.0/1 via 10.198.1.5 dev tun0 
179.43.177.66 via 192.168.1.254 dev eth0 
192.168.1.0/24 dev eth0  proto kernel  scope link  src 192.168.1.84 

然后运行:

BIND_ADDR="10.30.1.17" LD_PRELOAD=/usr/lib/bind.so get_iplayer --type=tv

导致没有连接,并且英国 VPN 日志中没有任何内容。

停止瑞士 VPN 并运行相同的 get_iplayer 命令将导致连接和信息被下载。ip route结果如下:

$ ip route
0.0.0.0/1 via 10.30.1.17 dev tun1 
default via 192.168.1.254 dev eth0 
10.30.1.1 via 10.30.1.17 dev tun1 
10.30.1.17 dev tun1  proto kernel  scope link  src 10.30.1.18
104.238.169.119 via 192.168.1.254 dev eth0 
128.0.0.0/1 via 10.30.1.17 dev tun1 
192.168.1.0/24 dev eth0  proto kernel  scope link  src 192.168.1.84

因此,据我所知,在打开或关闭不同的 VPN 时,路由不会发生 IP 地址变化或任何奇怪的事情发生。

我不明白为什么它bind.so似乎没有效果。终端上没有任何输出来显示它是否成功,如果它输出了任何东西(终端中的输出是命令,即 get_iplayer),我也不确定在哪里可以找到日志。

显然,我可以运行 cron 作业/脚本来打开和关闭 VPN,以便我成功运行/通过英国 VPN 运行 get_iplayer,但我更愿意保持两个 VPN 都打开,让我的所有流量都通过界面,tun0并且只tun1在需要时使用 get_player bind.so

有人能帮忙吗?如果我们无法解决这个问题,那么为 get_iplayer 进程编写特定的路由表或规则会很有帮助。

谢谢。

答案1

bind.so不对这个问题负责,而是由路由引起的:每个路由表中只能有一个默认网关,但每个 VPN 实例都会尝试设置网关(以及一般的路由),使其最适合自己,从而导致表中出现如下行:

  0.0.0.0/1 via 10.30.1.17 dev tun1 
  0.0.0.0/1 via 10.198.1.5 dev tun0 

内核要如何处理这个问题?它应该通过 tun0 还是通过 tun1 进行路由?答案是:它实际上并没有路由,你的电脑无法连接。

解决方法:

  1. 设置两个路由表,并制定规则指示内核何时使用其中任何一个。这称为policy or source routing,您可以找到它的简要介绍这里。这很棘手,不是因为策略路由本身(这很容易),但因为你必须自己设置 OpenVPN 的两个实例之一的路由,所以要确保将路由规则添加到其他路由表。但这是可以做到的,因为OpenVPN 提供了声明

--route-nopull

与 --client 或 --pull 一起使用时,接受服务器推送的选项,但路由除外。

它可以让你根据需要设置路由。

  1. 设置网络命名空间OpenVPN 实例以及使用它的程序。您可以找到网络命名空间的介绍这里。这也需要一些工作,但主要是死记硬背。如果您愿意,我编写的以下脚本(非常简单!)可以立即使用。

编辑

由于您正在通过 ssh 会话工作,因此我不得不稍微修改一下我的脚本。

你可以从

     newnsssh NameOfNNS start

但你仍然会发现自己处于默认网络命名空间(简称 NNS)内。在访问它之前,最好打开一个终端:

     xterm &

这将打开一个图形终端假如您通过以下方式连接到 RPI

    ssh -Y me@rpi

假设$DISPLAY环境变量设置为:

  export DISPLAY=localhost:10.0

打开后xterm,进入它并发出以下命令:

   sudo ip netns exec NameOfNNS bash

新的提示符位于新的 NNS 中;要检查,

    ip netns identify $$

如果没有返回任何内容,则表示您处于默认NNS,否则您将看到 NameOfNNS。您现在可以启动 OpenVPN

      openvpn --config /path/to/config/file &
      su YourName

然后你就大功告成了。现在,在这个 xterm 内,所有程序都将由这个 OpenVPN 实例路由,而在这个 xterm 之外启动的所有程序都将在 OpenVPN 之外启动,或者通过另一个 OpenVPN 实例启动(如果你碰巧制定了另一个实例)。

完成后,只需关闭xterm,然后在 ssh 会话中,

    newnsssh NameOfNNS stop

就这样。

#!/bin/bash

#
# This script will setup an internal network 10.173.N.0/24; if this causes
# any conflict, change the statement below.

export IP_BASE=10.173

# It will open an xterm window in the new network namespace; if anything
# else is required, change the statement below.


# The script will temporarily activate ip forwarding for you. If you
# do not wish to retain this feature, you will have to issue, at the 
# end of this session, the command
# echo 0 > /proc/sys/net/ipv4/ip_forward 
# yourself. 

export WHEREIS=/usr/bin/whereis

# First of all, check that the script is run by root:

[ "root" != "$USER" ] && exec sudo $0 "$@"

if [ $# != 2 ]; then 
    echo "Usage $0 name action"
    echo "where name is the network namespace name,"
    echo " and action is one of start| stop| reload."
    exit 1
 fi

 # Do we have all it takes?

 IERROR1=0
 IERROR2=0

 export IP=$($WHEREIS -b ip | /usr/bin/awk '{print $2}')
 export IPTABLES=$($WHEREIS -b iptables | /usr/bin/awk '{print $2}')

 if [ x$IP = x ] ; then
    echo "please install the iproute2 package"
    IERROR1=1
 fi

 if [ x$IPTABLES = x ] ; then
    echo "please install the iptables package"
    IERROR2=1
 fi


 if [[ $IERROR1 == 0 && $IERROR2 == 0 ]] 
    then
    :   
 else
    exit 1
 fi


 prelim() {

 # Perform some preliminary setup. First, clear the proposed 
 # namespace name of blank characters; then create a directory
 # for logging info, and a pid file in it. Lastly, 
 # enable IPv4 forwarding. 

    VAR=$1
    export NNSNAME=${VAR//[[:space:]]}

    export OUTDIR=/var/log/newns/$NNSNAME

    if [ ! -d $OUTDIR ]; then
        /bin/mkdir -p $OUTDIR
    fi
    export PID=$OUTDIR/pid$NNSNAME


    ICOUNTER=1
    export Nns=$ICOUNTER 
    if [ $Nns == 1 ]; then
        echo 1 > /proc/sys/net/ipv4/ip_forward
    fi
}

start_nns() {

# Check whether a namespace with the same name already exists. 

$IP netns list | /bin/grep $1 2> /dev/null
if [ $? == 0 ]; then 
    echo "Network namespace $1 already exists,"
    echo "please choose another name"
    exit 1
fi

# Here we take care of DNS

/bin/mkdir -p /etc/netns/$1
echo "nameserver 8.8.8.8" > /etc/netns/$1/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/netns/$1/resolv.conf

# The following creates the new namespace, the veth interfaces, and
# the bridge between veth1 and a new virtual interface, tap0.
# It also assigns an IP address to the bridge, and brings everything up

$IP netns add $1
$IP link add veth-a$1 type veth peer name veth-b$1
$IP link set veth-a$1 up
$IP tuntap add tap$1 mode tap user root
$IP link set tap$1 up
$IP link add br$1 type bridge
$IP link set tap$1 master br$1
$IP link set veth-a$1 master br$1
$IP addr add $IP_BASE.$Nns.1/24 dev br$1
$IP link set br$1 up

# We need to enable NAT on the default namespace

$IPTABLES -t nat -A POSTROUTING -j MASQUERADE

# This assigns the other end of the tunnel, veth2, to the new 
# namespace, gives it an IP address in the same net as the bridge above, 
# brings up this and the (essential) lo interface, sets up the 
# routing table by assigning the bridge interface in the default namespace
# as the default gateway, creates a new terminal in the new namespace and 
# stores its pid for the purpose of tearing it cleanly, later. 

$IP link set veth-b$1 netns $1
$IP netns exec $1 $IP addr add $IP_BASE.$Nns.2/24 dev veth-b$1
$IP netns exec $1 $IP link set veth-b$1 up
$IP netns exec $1 $IP link set dev lo up
$IP netns exec $1 $IP route add default via $IP_BASE.$Nns.1 
ln -s /proc/1/ns/net /var/run/netns/default 2> /dev/null
#   $IP netns exec $1 bash & $IP netns exec $1 echo "$!" > $PID
}

stop_nns() {

# Check that the namespace to be torn down really exists

$IP netns list | /bin/grep $1 2>&1 1> /dev/null
if [ ! $? == 0 ]; then 
    echo "Network namespace $1 does not exist,"
    echo "please choose another name"
    exit 1
fi

# This kills the terminal in the separate namespace, 
# removes the file and the directory where it is stored, and tears down
# all virtual interfaces (veth1, tap0, the bridge, veth2 is automatically
# torn down when veth1 is), and the NAT rule of iptables. 


rm /var/run/netns/default
$IP link set br$1 down
$IP link del br$1
$IP netns del $1
$IP link set veth-a$1 down
$IP link del veth-a$1
$IP link set tap$1 down
$IP link del tap$1
$IPTABLES -t nat -D POSTROUTING -j MASQUERADE
/bin/rm /etc/netns/$1/resolv.conf
    /bin/rmdir /etc/netns/$1
}


case $2 in
  start)
    prelim "$1"
    start_nns $NNSNAME
    ;;
  stop)
    prelim "$1"
    stop_nns $NNSNAME
    ;;
  reload)
    prelim "$1"
    stop_nns $NNSNAME
    prelim "$1"
    start_nns $NNSNAME
    ;;
 *) 
 # This removes the absolute path from the command name

    NAME1=$0
    NAMESHORT=${NAME1##*/}

    echo "Usage:" $NAMESHORT "name action,"
    echo "where name is the name of the network namespace,"
    echo "and action is one of start|stop|reload"
    ;;
esac

相关内容