Маршрутизировать только определенный трафик через VPN

11

Я искал предыдущих людей, которые задавали похожие вопросы, но еще не получил прямого ответа, который будет работать для моей данной ситуации, так что давайте ...

Я работаю в Linux (Fedora 22) и у меня есть VPN-сервис, за который я плачу, однако мне нужны только определенные программы, чтобы использовать VPN для их интернет-трафика, и я могу использовать мое стандартное ISP-соединение для всего остального (например, просмотр веб-страниц, так далее)

Мы сделаем это простым и ограничим его наиболее используемой программой World of Warcraft, которая запускается через WINE.

Теперь у меня есть настройка VPN через сетевой интерфейс, так что весь мой трафик через enp10s0 (странное название моих компьютеров для eth0) может быть туннелирован через службу VPN, однако мне нужны только определенные программы (или порты, которые эти программы используют, чтобы быть конкретным) пройти через VPN.

Как настроить туннель, и он должен только маршрутизировать порты, необходимые через VPN, сохраняя все остальное без маршрутизации?

Джош Рэймонд
источник
Можете ли вы объяснить, как другие ответы не решают эту проблему? Что уникального в вашей настройке?
Пол
1
Почти у каждого из них есть свой способ достижения этой цели, и никто из них не делает это вообще простым. Некоторые используют отдельное пространство имен для приложения, некоторые используют туннельный интерфейс, некоторые делают это прямо через openvpn в терминале, но ни один из них, которые я обнаружил, не дал мне какого-либо дешифруемого метода выполнения любого из них.
Джош Рэймонд
Пожалуйста, смотрите мой Edit
MariusMatutiae

Ответы:

17

То, что вы просите, не существует. Вот почему вы не удовлетворены найденными вами ответами (некоторые из них, возможно, мои): все они предложили обходные пути , а не подлинное решение, ни простое, ни сложное.

Позволь мне объяснить. Маршрутизация во всех ОС определяется адресом назначения: у вас вполне может быть несколько маршрутов, но выбор между ними зависит не от приложения, вызывающего соединение, а просто от адреса назначения. Полная остановка.

Позвольте мне привести вам нетривиальный пример. Когда VPN-клиент установил соединение со своим сервером, все еще возможно направить соединение на определенный сайт, например, example.org, за пределы VPN. Но все приложения, пытающиеся достичь этого специального адреса, будут маршрутизироваться за пределы VPN: некоторые приложения не могут отправляться на example.org через VPN, в то время как другие приложения выходят за пределы VPN.

Ситуация становится богаче с ядром Linux, которое допускает маршрутизацию источника: это означает, что вы можете иметь две или более таблиц маршрутизации, и выбор между ними зависит от адреса источника, а не от адреса назначения.

Нетривиальный пример: мой компьютер имеет две внешние линии с двумя разными публичными IP-адресами. С ним можно связаться через любой интерфейс, и важно, чтобы мои ответы на данное соединение проходили через тот же интерфейс, через который было установлено соединение: в противном случае они будут отброшены как неактуальные, когда они достигнут того, кто инициировал соединение. Это источник маршрутизации.

Справедливо, как насчет связей, которые мы начинаем? Некоторые приложения позволяют указывать адрес привязки, например, клиент openssh :

-b bind_address

Используйте bind_address на локальном компьютере в качестве адреса источника соединения. Полезно только в системах с более чем одним адресом.

Для них нет проблем с тем, чтобы один экземпляр проходил через VPN (скажем, таблица маршрутизации 1), тогда как другой экземпляр будет выходить за пределы VPN (скажем, таблица маршрутизации 2). Но другие приложения, такие как Firefox, не только общеизвестно сложно связать с конкретным IP-адресом источника (но см. Здесь очень умный обходной путь), но также являются злыми и неприятными в том смысле, что они не позволят вам иметь две копии себя работает одновременно, каждый привязан к другому адресу источника. Другими словами, в то время как благодаря упомянутой выше уловке вы можете обязать один экземпляр связываться с выбранным вами адресом источника, тогда у вас не будет другой версии, привязывающей его к другому адресу источника.

Это объясняет, почему мы используем обходные пути: все они основаны на одной и той же идее, что они работают с отдельным сетевым стеком, чем остальной компьютер. Таким образом, в порядке уменьшения сложности вы можете иметь виртуальные машины, докеры, контейнеры, пространства имен. В каждой из них у вас будет одна или несколько таблиц маршрутизации, но у вас может быть несколько экземпляров каждой (VM / dockers / container / namespaces), и вы также можете свободно добавлять их, каждая из которых запускает свое собственное приложение, такое как 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. После настройки VPN в xterm вы можете проверить, отличается ли ваш публичный IP-адрес в других ваших окнах. Вы можете открыть до 254 xterms, с 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+ Ctrl+ Fn, где Fn - один из F1, F2, ....-

Мне нужно добавить одно предупреждение: обработка DNS внутри пространств имен немного глючит, наберитесь терпения.

MariusMatutiae
источник
1
И поэтому у меня наконец есть очень простое, но подробное объяснение того, почему то, что я пытаюсь сделать, не является ни легким, ни обычным делом! Спасибо! Прочитав это, я считаю, что обходной путь был бы уместным, так как я хочу, чтобы только трафик от конкретной программы я хотел маршрутизировать, и я всегда хотел бы, чтобы эта программа была маршрутизирована. Пример: я хочу, чтобы VideoGameA маршрутизировалась через VPN, но я не хочу, чтобы другие программы проходили через него. Есть ли простой способ привязки определенных портов только через интерфейс VPN? Если это так, как бы я правильно настроить и подключиться к указанному интерфейсу?
Джош Рэймонд
@JoshRaymond Хорошо. Чтобы выбрать самый простой обходной путь, вы должны предоставить свою таблицу маршрутизации с помощью VPN и сообщить мне, использует ли VideoGameA порты UDP вообще.
MariusMatutiae
Он использует как TCP, так и UDP порты 443, 3724 и 1119. Маршрут будет опубликован в следующем комментарии
Джош Рэймонд
$ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 0.0.0.0 0.0.0.0 U 50 0 0 ppp0 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 enp10s0 1.0.0.1 0.0.0.0 255.255.255.255 UH 0 0 0 ppp0 192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 enp10s0 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 enp10s0 199.168.112.120 192.168.1.1 255.255.255.255 UGH 100 0 0 enp10s0
Джош Рэймонд
Мне интересно, почему в ответе @MariusMatutiae создает кран и мост? Кажется, что он работает нормально только с помощью устройств veth.
Ян Келлинг