我查阅了以前问过类似问题的人,但还没有得到适合我的情况的直接答案,所以就这样吧..
我在 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 stop
newns
这里另一个相关的注意事项可能是,我已将要连接的 VPN 存储在 Gnome 的网络管理器中。但是,似乎您只能openvpn
从中调用命令xterm
行newns
(如果您尝试通过网络管理器桌面 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-terminal
在newns
脚本中进行更改似乎也有效)