仅通过 VPN 路由特定流量

仅通过 VPN 路由特定流量

我查阅了以前问过类似问题的人,但还没有得到适合我的情况的直接答案,所以就这样吧..

我在 Linux(Fedora 22)上运行并拥有付费的 VPN 服务,但是我只需要特定程序使用 VPN 进行互联网流量传输,并且可以使用我的标准 ISP 连接进行其他所有操作(即网页浏览等)

我们将简化这个过程并将其限制为最常用的程序,即通过 WINE 运行的魔兽世界。

现在,我已通过网络接口设置了 VPN,这样通过 enp10s0(我计算机上的 eth0 的奇怪名称)的所有流量都可以通过 VPN 服务进行隧道传输,但是,我只需要特定的程序(或具体来说,是这些程序使用的端口)通过 VPN。

我该如何设置隧道,并让它只通过 VPN 路由所需的端口,同时保持其他所有内容不被路由?

答案1

你所要求的不是存在。这就是为什么你对你找到的答案不满意(其中一些可能是我的):所有这些答案都暗示了解决方法,不是一个真正的解决方案,无论简单还是复杂。

让我解释一下。所有操作系统中的路由都是由目标地址决定的:您可能有几条路由,但它们之间的选择不是基于调用连接的应用程序,而只是基于目标地址。句号。

让我举一个不简单的例子。当 VPN 客户端与其服务器建立连接时,仍然可以将连接路由到 VPN 之外的给定站点,例如 example.org。但所有尝试访问该特殊地址的应用程序都将被路由到 VPN 之外:您不能让某些应用程序通过 VPN 访问 example.org,而其他应用程序则通过 VPN 之外。

Linux 内核允许源路由,因此情况变得更加丰富:这意味着您可以拥有两个或多个路由表,并且它们之间的选择基于源地址,而不是目标地址。

举个不简单的例子:我的电脑有两条外线,分别有两个不同的公网 IP。可以通过任一接口联系它,并且重要的是,我对给定连接的回复必须通过连接进入的同一接口:否则,当它们到达发起连接的人时,它们将被视为无关紧要而被丢弃。这就是源路由。

好吧,我们启动的连接怎么样?有些应用允许你指定绑定地址,例如openssh 客户端

-b 绑定地址

使用本地机器上的 bind_address 作为连接的源地址。仅在具有多个地址的系统上有用。

对于他们来说,让一个实例通过 VPN(例如,路由表 1),而另一个实例在 VPN 之外(例如,路由表 2)是没有问题的。但其他应用程序,如 Firefox,不仅众所周知难以绑定到特定的源 IP 地址(而且参见这里这是一种非常聪明的解决方法),但同时也非常卑鄙和恶劣,因为他们会不是允许您同时运行两个副本,每个副本绑定到不同的源地址。换句话说,虽然借助上述技巧,您可以强制一个实例绑定到您选择的源地址,但您不能让另一个版本绑定到另一个源地址。

这解释了我们为什么使用变通方法:它们都基于同一个想法,即它们使用与 PC 其余部分不同的网络堆栈。因此,您可以按复杂程度的近似顺序拥有虚拟机、docker、容器、命名空间。在每个路由表中,您将拥有一个或多个路由表,但您可以拥有每个路由表的多个实例(虚拟机/docker/容器/命名空间),并且您还可以自由混合它们,每个路由表都运行自己的应用程序,例如 Firefox,与其他应用程序分开。

也许您仍然对其中一种解决方法感兴趣?

编辑:

最简单的解决方法是网络命名空间。下面的脚本处理 NNS 的所有必要方面:将其放在一个文件中(您选择您的名字,我通常使用newns,但您可以做任何您喜欢的事情)/usr/local/bin,然后chmod 755 FILE_NAME,您可以按如下方式使用它:

       newns NAMESPACE_NAME start
       newns NAMESPACE_NAME stop

它将xterm为您打开一个(这是因为我喜欢 xterm 工作,但如果您想使用其他任何东西,您可以更改它),它属于新的命名空间。如果您愿意,您可以在 xterm 内部启动您的 vpn,然后启动您的游戏。您可以通过以下命令轻松检查您是否正在使用 VPN:

    wget 216.146.38.70:80 -O - -o /dev/null | cut -d" " -f6 | sed 's/<\/body><\/html>//'

它会返回您的公共 IP。在 xterm 中设置 VPN 后,您可以检查您的公共 IP 在其他窗口中是否不同。您最多可以打开 254 个 xterm,使用 254 个不同的 NNS 和不同的连接。

#!/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.

export XTERM=/usr/bin/xterm

# 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. 

 ###############################################################################

 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
 IERROR3=0
 export IP=$($WHEREIS -b ip | /usr/bin/awk '{print $2}')
 if [ $? != 0 ]; then
    echo "please install the iproute2 package"
    IERROR1=1
 fi

 export IPTABLES=$($WHEREIS -b iptables | /usr/bin/awk '{print $2}')
 if [ $? != 0 ]; then
    echo "please install the iptables package"
    IERROR2=1
 fi

 XTERM1=$($WHEREIS -b $XTERM | /usr/bin/awk '{print $2}')
 if [ $? != 0 ]; then
    echo "please install the $XTERM package"
    IERROR3=1
 fi
 if [ IERROR1 == 1 -o IERROR2 == 1 -o IERROR3 == 1 ]; then
    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; then determine 
 # how many running namespaces already exist, for the purpose
 # of creating a unique network between the bridge interface (to 
 # be built later) and the new namespace interface. 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

    # Find a free subnet

    ICOUNTER=0
    while true; do
            let ICOUNTER=ICOUNTER+1
            ip addr show | grep IP_BASE.$ICOUNTER.1 2>&1 1> /dev/null
            if [ ! $? == 0 -a $ICOUNTER -lt 255 ]; then
                    export Nns=$ICOUNTER
                    break
            elif [ ! $? == 0 -a $ICOUNTER -gt 254 ]; then
                    echo "Too many open network namespaces"
                    exit 1
            fi
    done
    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
    $IP netns exec $1 su -c $XTERM $SUDO_USER &
    $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. 

    /bin/kill -TERM $(cat $PID) 2> /dev/null 1> /dev/null
    /bin/rm $PID
    /bin/rmdir $OUTDIR
    $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

如果你愿意,你甚至可以在新的网络命名空间内启动整个桌面,方法是

            sudo startx -- :2 

然后您可以使用 ++Alt搜索它,其中 Fn 是 F1、F2、.... 之一CtrlFn

我需要补充一点:命名空间内的 DNS 处理有点问题,请耐心等待。

答案2

我知道问题被标记了fedora,但是我刚刚newns从@MariusMatutiae 的优秀答案中获得了在 Ubuntu 18.04 上运行的脚本 - 所以我想我应该记下我的笔记,因为我发现有几个步骤并不简单。

正如答案中所说,newns一开始在 Ubuntu 18.04 上运行良好;然而,人们很快就会意识到,在运行命令并出现newns MYNS start新问题之后,xterm仅有的仍然xterm具有网络连接(更确切地说,DNS解析,因为我得到了ping: google.com: Name or service not known;我通过IP尝试的某些操作似乎仍然工作正常),而系统的其余部分不是- 并且这种情况持续,直到您使用 关闭newns MYNS start

问题是,我通过 Wi-Fi 从 Ubuntu 18.04 连接到互联网,而 Wi-Fi 路由器(或 Ubuntu?)将 IPv6 和 IPv4 地址分配给 Wi-Fi 网络适配器(ifconfig告诉我,在我的 Ubuntu 中调用wlp2s0)。所以起初我以为这是 IPv6 导致的问题,但似乎不是 - 因为我最终发现仅为选定的应用程序使用 VPN 连接( 进一步来说https://superuser.com/a/1262250),其中批评意见如下:

由于我的默认接口是无线接口,因此我在 iptables 中使用 wl+(可能匹配 wlan0、wlp3s0 等)作为传出接口;如果您使用有线接口,您可能应该使用 en+(或 br+ 作为桥接接口)

因此,我在原文中做了这样的替换newns

@@ -134,7 +134,8 @@
 
     # We need to enable NAT on the default namespace
 
-    $IPTABLES -t nat -A POSTROUTING -j MASQUERADE
+    #$IPTABLES -t nat -A POSTROUTING -j MASQUERADE
+    $IPTABLES -t nat -A POSTROUTING -s $IP_BASE.$Nns.1/24 -o wl+ -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,
@@ -181,7 +182,8 @@
     $IP link del veth-a$1
     $IP link set tap$1 down
     $IP link del tap$1
-    $IPTABLES -t nat -D POSTROUTING -j MASQUERADE
+    #$IPTABLES -t nat -D POSTROUTING -j MASQUERADE
+    $IPTABLES -t nat -D POSTROUTING -s $IP_BASE.$Nns.1/24 -o wl+ -j MASQUERADE
     /bin/rm /etc/netns/$1/resolv.conf
     /bin/rmdir /etc/netns/$1

如果只替换该-A POSTROUTING行(而不更改该-D POSTROUTING行),则在启动后iptables: No chain/target/match by that name.尝试运行时您将得到结果。newns MYNS stopnewns

这里另一个相关的注意事项可能是,我已将要连接的 VPN 存储在 Gnome 的网络管理器中。但是,似乎您只能openvpn从中调用命令xtermnewns(如果您尝试通过网络管理器桌面 GUI 为系统的其余部分启动 VPN,希望它xterm保持“直接”/非 VPN 连接,那么从任何地方都无法解析任何网络连接,直到您从网络管理器关闭 VPN)。

因此,问题就在这里:网络管理器实际上将 VPN 设置保存为“网络连接” /etc/NetworkManager/system-connections(https://askubuntu.com/questions/27168/config-import-on-network-manager-openvpn),我不知道它将相关密码保存在哪里;而且这肯定不能直接由命令行openvpn客户端使用。幸运的是,我保存了原始.ovpn文件(我已将其导入网络管理器以在那里创建 VPN 连接),所以我必须在xterm启动时执行以下操作newns

sudo openvpn --config /path/to/myfile.ovpn

... 当我输入用户名和密码时,系统会要求我输入用户名和密码 - 当输入成功后,openvpn系统会阻止我。

这是另一个问题 - 因为如果openvpn阻塞,那么我无法启动任何其他程序,xterm :)因此一个想法是openvpn在后台运行(作为“服务”),但是,仅附加 & 符号实际上sudo在后台运行,并且您无法输入用户名和密码(假设您sudo之前缓存了密码):

user@PC:~$ sudo openvpn --config /path/to/file.ovpn &
[6] 3113
user@PC:~$ Thu Aug 20 20:29:51 2020 OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on May 14 2019
Thu Aug 20 20:29:51 2020 library versions: OpenSSL 1.1.1  11 Sep 2018, LZO 2.08
Enter Auth Username: RemoteUser

[6]+  Stopped                 sudo openvpn --config /path/to/file.ovpn
RemoteUser: command not found

...而且你也不能把整个东西包装起来bash -c

user@PC:~$ sudo bash -c 'openvpn --config /path/to/file.ovpn &'
user@PC:~$ Thu Aug 20 20:35:05 2020 OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on May 14 2019
Thu Aug 20 20:35:05 2020 library versions: OpenSSL 1.1.1  11 Sep 2018, LZO 2.08

Broadcast message from root@MyPCName (Thu 2020-08-20 20:35:05 CEST):

Password entry required for 'Enter Auth Username:' (PID 3261).
Please enter password with the systemd-tty-ask-password-agent tool!

...所以实际上我要做的是这样的:

  • 运行newns MYNS start- 它启动一个xterm
  • 从那里xterm,第一次运行xterm &- 获取另一个xterm具有相同(“newns”)网络设置
  • 现在在第一次xterm运行中sudo openvpn --config /path/to/file.ovpn它会被阻塞,但现在没问题了,因为我们可以:
  • 切换到第二个xterm,确认ifconfig已建立 openvpn 隧道,然后wget -qO- ifconfig.co在该终端中拥有 VPN 公共 IP - 然后您可以使用要通过 VPN 运行的程序运行任何网络

所以一切都很好 - 对我来说唯一的问题是,我无法gnome-terminal从第二个运行我更喜欢的xterm- 相反,我可以,但随后它连接到系统网络管理器并显然使用它的网络,而不是生成它的父级的网络xterm。 (编辑:在此处发布有关此内容的信息https://unix.stackexchange.com/questions/605485/possible-to-start-gnome-terminal-from-xterm-inheriting-xterms-network-settings- 事实证明,mate-terminal &从此开始xterm保留 VPN 网络配置 - 并且在此窗口中打开的后续选项卡也是如此mate-terminal...并且export XTERM=/usr/bin/mate-terminalnewns脚本中进行更改似乎也有效)

相关内容