Подавать весь трафик через OpenVPN только для определенного сетевого пространства имен

Я пытаюсь настроить VPN (используя OpenVPN), чтобы весь трафик, и только трафик, в / из определенных процессов проходил через VPN; другие процессы должны продолжать использовать физическое устройство напрямую. Насколько я понимаю, способ сделать это в Linux – это пространство имен.

Если я обычно использую OpenVPN (т. Е. Перенаправляет весь трафик с клиента через VPN), он работает нормально. В частности, я запускаю OpenVPN следующим образом:

# openvpn --config destination.ovpn --auth-user-pass credentials.txt 

(Исправленная версия destination.ovpn находится в конце этого вопроса.)

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

  1. Помещение туннельного устройства непосредственно в пространство имен с помощью

     # ip netns add tns0 # ip link set dev tun0 netns tns0 # ip netns exec tns0 ( ... commands to bring up tun0 as usual ... ) 

    Эти команды выполняются успешно, но трафик, генерируемый внутри пространства имен (например, с ip netns exec tns0 traceroute -n 8.8.8.8 ), попадает в черную дыру.

  2. Исходя из предположения, что « вы [все еще] можете назначать виртуальные интерфейсы Ethernet (veth) для сетевого пространства имен » (который, если это правда, принимает награду в этом году за самые смехотворно ненужные ограничения API), создавая пару veth и мост, и помещая один конец пары veth в пространство имен. Это даже не доходит до падения трафика на полу: это не позволит мне поместить туннель в мост! [РЕДАКТИРОВАТЬ: Это похоже на то, что в мосты можно вводить только кранные устройства. В отличие от неспособности помещать произвольные устройства в пространство имен в сети, это действительно имеет смысл, что с мостами, являющимися концепцией уровня Ethernet; к сожалению, мой провайдер VPN не поддерживает OpenVPN в режиме крана, поэтому мне нужно обходное решение.]

     # ip addr add dev tun0 local 0.0.0.0/0 scope link # ip link set tun0 up # ip link add name teo0 type veth peer name tei0 # ip link set teo0 up # brctl addbr tbr0 # brctl addif tbr0 teo0 # brctl addif tbr0 tun0 can't add tun0 to bridge tbr0: Invalid argument 

Сценарии в конце этого вопроса предназначены для подхода veth. Сценарии прямого подхода можно найти в истории редактирования. Переменные в сценариях, которые, как представляется, используются без их установки, устанавливаются в среде программой openvpn – да, она неаккуратная и использует имена нижнего регистра.

Просьба предложить конкретные рекомендации о том, как заставить это работать. Мне больно известно, что я программирую здесь по кушту груза – кто-нибудь написал полную документацию для этого материала? Я не могу найти ни одного, так что общий обзор кода скриптов также оценивается.

В случае, если это имеет значение:

 # uname -srvm Linux 3.14.5-x86_64-linode42 #1 SMP Thu Jun 5 15:22:13 EDT 2014 x86_64 # openvpn --version | head -1 OpenVPN 2.3.2 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [eurephia] [MH] [IPv6] built on Mar 17 2014 # ip -V ip utility, iproute2-ss140804 # brctl --version bridge-utils, 1.5 

Ядро было создано моим виртуальным хостинг-провайдером ( Linode ) и, хотя скомпилировано с CONFIG_MODULES=y , не имеет реальных модулей – единственная переменная CONFIG_* установленная в m соответствии с /proc/config.gz была CONFIG_XEN_TMEM , и я не на самом деле есть этот модуль (ядро хранится вне моей файловой системы, /lib/modules пуст, а /proc/modules указывает, что он каким-то образом не был магически загружен). Выдержки из /proc/config.gz предоставляются по запросу, но я не хочу вставлять сюда все.

netns-up.sh

 #! /bin/sh mask2cidr () { local nbits dec nbits=0 for dec in $(echo $1 | sed 's/\./ /g') ; do case "$dec" in (255) nbits=$(($nbits + 8)) ;; (254) nbits=$(($nbits + 7)) ;; (252) nbits=$(($nbits + 6)) ;; (248) nbits=$(($nbits + 5)) ;; (240) nbits=$(($nbits + 4)) ;; (224) nbits=$(($nbits + 3)) ;; (192) nbits=$(($nbits + 2)) ;; (128) nbits=$(($nbits + 1)) ;; (0) ;; (*) echo "Error: $dec is not a valid netmask component" >&2 exit 1 ;; esac done echo "$nbits" } mask2network () { local host mask hm result host="$1." mask="$2." result="" while [ -n "$host" ]; do h="${host%%.*}" m="${mask%%.*}" host="${host#*.}" mask="${mask#*.}" result="$result.$(($h & $m))" done echo "${result#.}" } maybe_config_dns () { local n option servers n=1 servers="" while [ $n -lt 100 ]; do eval option="\$foreign_option_$n" [ -n "$option" ] || break case "$option" in (*DNS*) set -- $option servers="$servers nameserver $3" ;; (*) ;; esac n=$(($n + 1)) done if [ -n "$servers" ]; then cat > /etc/netns/$tun_netns/resolv.conf <<EOF # name servers for $tun_netns $servers EOF fi } config_inside_netns () { local ifconfig_cidr ifconfig_network ifconfig_cidr=$(mask2cidr $ifconfig_netmask) ifconfig_network=$(mask2network $ifconfig_local $ifconfig_netmask) ip link set dev lo up ip addr add dev $tun_vethI \ local $ifconfig_local/$ifconfig_cidr \ broadcast $ifconfig_broadcast \ scope link ip route add default via $route_vpn_gateway dev $tun_vethI ip link set dev $tun_vethI mtu $tun_mtu up } PATH=/sbin:/bin:/usr/sbin:/usr/bin export PATH set -ex # For no good reason, we can't just put the tunnel device in the # subsidiary namespace; we have to create a "virtual Ethernet" # device pair, put one of its ends in the subsidiary namespace, # and put the other end in a "bridge" with the tunnel device. tun_tundv=$dev tun_netns=tns${dev#tun} tun_bridg=tbr${dev#tun} tun_vethI=tei${dev#tun} tun_vethO=teo${dev#tun} case "$tun_netns" in (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;; (*) exit 1;; esac if [ $# -eq 1 ] && [ $1 = "INSIDE_NETNS" ]; then [ $(ip netns identify $$) = $tun_netns ] || exit 1 config_inside_netns else trap "rm -rf /etc/netns/$tun_netns ||: ip netns del $tun_netns ||: ip link del $tun_vethO ||: ip link set $tun_tundv down ||: brctl delbr $tun_bridg ||: " 0 mkdir /etc/netns/$tun_netns maybe_config_dns ip addr add dev $tun_tundv local 0.0.0.0/0 scope link ip link set $tun_tundv mtu $tun_mtu up ip link add name $tun_vethO type veth peer name $tun_vethI ip link set $tun_vethO mtu $tun_mtu up brctl addbr $tun_bridg brctl setfd $tun_bridg 0 #brctl sethello $tun_bridg 0 brctl stp $tun_bridg off brctl addif $tun_bridg $tun_vethO brctl addif $tun_bridg $tun_tundv ip link set $tun_bridg up ip netns add $tun_netns ip link set dev $tun_vethI netns $tun_netns ip netns exec $tun_netns $0 INSIDE_NETNS trap "" 0 fi . #! /bin/sh mask2cidr () { local nbits dec nbits=0 for dec in $(echo $1 | sed 's/\./ /g') ; do case "$dec" in (255) nbits=$(($nbits + 8)) ;; (254) nbits=$(($nbits + 7)) ;; (252) nbits=$(($nbits + 6)) ;; (248) nbits=$(($nbits + 5)) ;; (240) nbits=$(($nbits + 4)) ;; (224) nbits=$(($nbits + 3)) ;; (192) nbits=$(($nbits + 2)) ;; (128) nbits=$(($nbits + 1)) ;; (0) ;; (*) echo "Error: $dec is not a valid netmask component" >&2 exit 1 ;; esac done echo "$nbits" } mask2network () { local host mask hm result host="$1." mask="$2." result="" while [ -n "$host" ]; do h="${host%%.*}" m="${mask%%.*}" host="${host#*.}" mask="${mask#*.}" result="$result.$(($h & $m))" done echo "${result#.}" } maybe_config_dns () { local n option servers n=1 servers="" while [ $n -lt 100 ]; do eval option="\$foreign_option_$n" [ -n "$option" ] || break case "$option" in (*DNS*) set -- $option servers="$servers nameserver $3" ;; (*) ;; esac n=$(($n + 1)) done if [ -n "$servers" ]; then cat > /etc/netns/$tun_netns/resolv.conf <<EOF # name servers for $tun_netns $servers EOF fi } config_inside_netns () { local ifconfig_cidr ifconfig_network ifconfig_cidr=$(mask2cidr $ifconfig_netmask) ifconfig_network=$(mask2network $ifconfig_local $ifconfig_netmask) ip link set dev lo up ip addr add dev $tun_vethI \ local $ifconfig_local/$ifconfig_cidr \ broadcast $ifconfig_broadcast \ scope link ip route add default via $route_vpn_gateway dev $tun_vethI ip link set dev $tun_vethI mtu $tun_mtu up } PATH=/sbin:/bin:/usr/sbin:/usr/bin export PATH set -ex # For no good reason, we can't just put the tunnel device in the # subsidiary namespace; we have to create a "virtual Ethernet" # device pair, put one of its ends in the subsidiary namespace, # and put the other end in a "bridge" with the tunnel device. tun_tundv=$dev tun_netns=tns${dev#tun} tun_bridg=tbr${dev#tun} tun_vethI=tei${dev#tun} tun_vethO=teo${dev#tun} case "$tun_netns" in (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;; (*) exit 1;; esac if [ $# -eq 1 ] && [ $1 = "INSIDE_NETNS" ]; then [ $(ip netns identify $$) = $tun_netns ] || exit 1 config_inside_netns else trap "rm -rf /etc/netns/$tun_netns ||: ip netns del $tun_netns ||: ip link del $tun_vethO ||: ip link set $tun_tundv down ||: brctl delbr $tun_bridg ||: " 0 mkdir /etc/netns/$tun_netns maybe_config_dns ip addr add dev $tun_tundv local 0.0.0.0/0 scope link ip link set $tun_tundv mtu $tun_mtu up ip link add name $tun_vethO type veth peer name $tun_vethI ip link set $tun_vethO mtu $tun_mtu up brctl addbr $tun_bridg brctl setfd $tun_bridg 0 #brctl sethello $tun_bridg 0 brctl stp $tun_bridg off brctl addif $tun_bridg $tun_vethO brctl addif $tun_bridg $tun_tundv ip link set $tun_bridg up ip netns add $tun_netns ip link set dev $tun_vethI netns $tun_netns ip netns exec $tun_netns $0 INSIDE_NETNS trap "" 0 fi . #! /bin/sh mask2cidr () { local nbits dec nbits=0 for dec in $(echo $1 | sed 's/\./ /g') ; do case "$dec" in (255) nbits=$(($nbits + 8)) ;; (254) nbits=$(($nbits + 7)) ;; (252) nbits=$(($nbits + 6)) ;; (248) nbits=$(($nbits + 5)) ;; (240) nbits=$(($nbits + 4)) ;; (224) nbits=$(($nbits + 3)) ;; (192) nbits=$(($nbits + 2)) ;; (128) nbits=$(($nbits + 1)) ;; (0) ;; (*) echo "Error: $dec is not a valid netmask component" >&2 exit 1 ;; esac done echo "$nbits" } mask2network () { local host mask hm result host="$1." mask="$2." result="" while [ -n "$host" ]; do h="${host%%.*}" m="${mask%%.*}" host="${host#*.}" mask="${mask#*.}" result="$result.$(($h & $m))" done echo "${result#.}" } maybe_config_dns () { local n option servers n=1 servers="" while [ $n -lt 100 ]; do eval option="\$foreign_option_$n" [ -n "$option" ] || break case "$option" in (*DNS*) set -- $option servers="$servers nameserver $3" ;; (*) ;; esac n=$(($n + 1)) done if [ -n "$servers" ]; then cat > /etc/netns/$tun_netns/resolv.conf <<EOF # name servers for $tun_netns $servers EOF fi } config_inside_netns () { local ifconfig_cidr ifconfig_network ifconfig_cidr=$(mask2cidr $ifconfig_netmask) ifconfig_network=$(mask2network $ifconfig_local $ifconfig_netmask) ip link set dev lo up ip addr add dev $tun_vethI \ local $ifconfig_local/$ifconfig_cidr \ broadcast $ifconfig_broadcast \ scope link ip route add default via $route_vpn_gateway dev $tun_vethI ip link set dev $tun_vethI mtu $tun_mtu up } PATH=/sbin:/bin:/usr/sbin:/usr/bin export PATH set -ex # For no good reason, we can't just put the tunnel device in the # subsidiary namespace; we have to create a "virtual Ethernet" # device pair, put one of its ends in the subsidiary namespace, # and put the other end in a "bridge" with the tunnel device. tun_tundv=$dev tun_netns=tns${dev#tun} tun_bridg=tbr${dev#tun} tun_vethI=tei${dev#tun} tun_vethO=teo${dev#tun} case "$tun_netns" in (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;; (*) exit 1;; esac if [ $# -eq 1 ] && [ $1 = "INSIDE_NETNS" ]; then [ $(ip netns identify $$) = $tun_netns ] || exit 1 config_inside_netns else trap "rm -rf /etc/netns/$tun_netns ||: ip netns del $tun_netns ||: ip link del $tun_vethO ||: ip link set $tun_tundv down ||: brctl delbr $tun_bridg ||: " 0 mkdir /etc/netns/$tun_netns maybe_config_dns ip addr add dev $tun_tundv local 0.0.0.0/0 scope link ip link set $tun_tundv mtu $tun_mtu up ip link add name $tun_vethO type veth peer name $tun_vethI ip link set $tun_vethO mtu $tun_mtu up brctl addbr $tun_bridg brctl setfd $tun_bridg 0 #brctl sethello $tun_bridg 0 brctl stp $tun_bridg off brctl addif $tun_bridg $tun_vethO brctl addif $tun_bridg $tun_tundv ip link set $tun_bridg up ip netns add $tun_netns ip link set dev $tun_vethI netns $tun_netns ip netns exec $tun_netns $0 INSIDE_NETNS trap "" 0 fi 

netns-down.sh

 #! /bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin export PATH set -ex tun_netns=tns${dev#tun} tun_bridg=tbr${dev#tun} case "$tun_netns" in (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;; (*) exit 1;; esac [ -d /etc/netns/$tun_netns ] || exit 1 pids=$(ip netns pids $tun_netns) if [ -n "$pids" ]; then kill $pids sleep 5 pids=$(ip netns pids $tun_netns) if [ -n "$pids" ]; then kill -9 $pids fi fi # this automatically cleans up the the routes and the veth device pair ip netns delete "$tun_netns" rm -rf /etc/netns/$tun_netns # the bridge and the tunnel device must be torn down separately ip link set $dev down brctl delbr $tun_bridg 

destination.ovpn

 client auth-user-pass ping 5 dev tun resolv-retry infinite nobind persist-key persist-tun ns-cert-type server verb 3 route-metric 1 proto tcp ping-exit 90 remote [REDACTED] <ca> [REDACTED] </ca> <cert> [REDACTED] </cert> <key> [REDACTED] </key> 

  • Автоматический запуск клиента PPTP VPN: необходимо перезапустить сетевой интерфейс
  • Маскарад не работает иногда
  • Не удается загрузить модуль
  • Как установить / использовать sshuttle на Fedora?
  • Импортировать файлы конфигурации VPN в NetworkManager из командной строки
  • Недокументированный код выхода pppd
  • Каков наилучший способ установки общесистемного сертификата с веб-сайта?
  • Нужна помощь в маршрутизации
  • 4 Solutions collect form web for “Подавать весь трафик через OpenVPN только для определенного сетевого пространства имен”

    Оказывается, вы можете поместить туннельный интерфейс в пространство имен в сети. Вся моя проблема сводилась к ошибке в создании интерфейса:

     ip addr add dev $tun_tundv \ local $ifconfig_local/$ifconfig_cidr \ broadcast $ifconfig_broadcast \ scope link 

    Проблема заключается в «ссылке на область видимости», которую я неправильно понял, так как это влияет только на маршрутизацию. Это заставляет ядро ​​устанавливать исходный адрес всех пакетов, отправленных в туннель, до 0.0.0.0 ; предположительно, сервер OpenVPN затем отбросит их как недопустимые для RFC1122; даже если это не так, пункт назначения, очевидно, не сможет ответить.

    Все работало корректно в отсутствие пространств имен в сети, потому что скрипт конфигурации открытой скрипты openvpn не допустил этой ошибки. И без «ссылки на область видимости» мой оригинальный скрипт также работает.

    (Как я узнал об этом, вы спрашиваете? strace в процессе openvpn, установите hexdump все, что он читает из дескриптора туннеля, а затем вручную декодирует заголовки пакетов.)

    Вы можете запустить ссылку OpenVPN внутри пространства имен, а затем запустить каждую команду, которую вы хотите использовать в этой области OpenVPN. Подробности о том, как это сделать (а не моя работа) здесь:

    http://www.naju.se/articles/openvpn-netns.html

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

    Сначала создайте сценарий –up для OpenVPN. Этот скрипт создаст интерфейс туннеля VPN внутри сетевого пространства имен, называемого vpn, вместо пространства имен по умолчанию.

     $ cat > netns-up << EOF #!/bin/sh case $script_type in up) ip netns add vpn ip netns exec vpn ip link set dev lo up mkdir -p /etc/netns/vpn echo "nameserver 8.8.8.8" > /etc/netns/vpn/resolv.conf ip link set dev "$1" up netns vpn mtu "$2" ip netns exec vpn ip addr add dev "$1" \ "$4/${ifconfig_netmask:-30}" \ ${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} test -n "$ifconfig_ipv6_local" && \ ip netns exec vpn ip addr add dev "$1" \ "$ifconfig_ipv6_local"/112 ;; route-up) ip netns exec vpn ip route add default via "$route_vpn_gateway" test -n "$ifconfig_ipv6_remote" && \ ip netns exec vpn ip route add default via \ "$ifconfig_ipv6_remote" ;; down) ip netns delete vpn ;; esac EOF 

    Затем запустите OpenVPN и скажите ему использовать наш скрипт –up вместо выполнения ifconfig и route.

     openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up 

    Теперь вы можете запускать программы для туннелирования следующим образом:

     ip netns exec vpn command 

    Единственный улов в том, что вам нужно быть root, чтобы вызывать ip netns exec ... и, возможно, вы не хотите, чтобы ваше приложение выполнялось как root. Решение прост:

      sudo ip netns exec vpn sudo -u $ (whoami) команда 

    Ошибка при попытке создать veth-устройства вызвана изменением того, как ip интерпретирует аргументы командной строки.

    Правильный вызов ip для создания пары veth-устройств

     ip link add name veth0 type veth peer name veth1 

    ( name instad dev )

    Теперь, как получить трафик из пространства имен в туннель VPN? Поскольку у вас есть только туннельные устройства в вашем распоряжении, «хозяин» должен маршрутизировать. Т.е. создать пару veth и поместить ее в пространство имен. Подключите другой маршрутизатор к туннелю. Таким образом, включите переадресацию, а затем добавьте необходимые маршруты.

    Для примера предположим, что eth0 – ваш основной интерфейс, tun0 – ваш интерфейс туннеля VPN, а veth0 / veth1 – пара интерфейсов, из которых veth1 находится в пространстве имен. В пространстве имен вы добавляете только маршрут по умолчанию для veth1 .

    На хосте вам нужно использовать политическую маршрутизацию, см. Здесь, например. Что тебе необходимо сделать:

    Добавить / добавить запись, например

     1 vpn 

    в /etc/iproute2/rt_tables . Таким образом вы можете вызвать таблицу (еще не созданную) по имени.

    Затем используйте следующие утверждения:

     ip rule add iif veth0 priority 1000 table vpn ip rule add iif tun0 priority 1001 table vpn ip route add default via <ip-addr-of-tun0> table vpn ip route add <ns-network> via <ip-addr-of-veth0> table vpn 

    Я не могу попробовать это здесь с такой настройкой, как ваша, но это должно делать именно то, что вы хотите. Вы можете увеличить это по правилам фильтра пакетов, чтобы не было нарушено ни vpn, ни «гостевая» сеть.

    NB tun0 в пространство имен в первую очередь выглядит правильно. Но, как и вы, я не получил этого, чтобы работать. Маршрутизация политики выглядит следующим образом. Решение Mahendra применимо, если вы знаете сети, стоящие за VPN, и все другие приложения никогда не будут обращаться к этим сетям. Но ваше начальное условие («весь трафик, и только трафик, в / из определенных процессов проходит через VPN») звучит так, как будто последнее не может быть гарантировано.

    Если сети, к которым вы обращаетесь через VPN, известны, вы можете отредактировать свою таблицу маршрутизации, чтобы достичь того, чего хотите.

    1. Обратите внимание на текущий маршрут по умолчанию.

      # ip route | grep default default via 192.168.43.1 dev wlo1 proto static metric 1024

    2. Выполните VPN, и это приведет к записи маршрутизации.

    3. Удалите текущий маршрут по умолчанию (который добавляется VPN), где в качестве предыдущего маршрута по умолчанию будет использоваться первая запись по умолчанию в таблице.

      # ip route | grep default default dev tun0 scope link default via 192.168.43.1 dev wlo1 proto static metric 1024

      # ip route del default dev tun0 scope link

    4. Добавьте настраиваемые маршруты в сети, которые находятся в VPN для маршрутизации через tun0.

      # ip route add <net1>/16 dev tun0

      # ip route add <net2>/24 dev tun0

    5. Добавьте обе записи сервера имен (в resolv.conf), а также для VPN и прямого подключения.

    Теперь все соединения net1 и net2 будут проходить через VPN, и сброс будет идти напрямую (через wlo1 в этом примере).

    Linux и Unix - лучшая ОС в мире.