Я пытаюсь настроить VPN (используя OpenVPN) таким образом, чтобы весь трафик, и только трафик, к / от определенных процессов проходил через VPN; другие процессы должны продолжать использовать физическое устройство напрямую. Насколько я понимаю, способ сделать это в Linux - использовать сетевые пространства имен.
Если я использую OpenVPN нормально (то есть направляя весь трафик от клиента через VPN), он работает нормально. В частности, я запускаю OpenVPN так:
# openvpn --config destination.ovpn --auth-user-pass credentials.txt
(Отредактированная версия destination.ovpn находится в конце этого вопроса.)
Я застрял на следующем шаге, написав сценарии, которые ограничивают туннельное устройство пространствами имен. Я пытался:
Помещение туннельного устройства непосредственно в пространство имен с помощью
# 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
), попадает в черную дыру.Предполагая, что « вы можете [по-прежнему] назначать только виртуальные интерфейсы Ethernet (veth) для сетевого пространства имен » (что, если это правда, получает награду этого года за самые нелепо ненужные ограничения API), создавая пару veth и мост, и помещая один конец пары veth в пространство имен. Это даже не сводится к падению движения на полу: это не позволит мне проложить туннель к мосту! [EDIT: Это , как представляется , так как только ди устройство может быть введено в мосты. В отличие от невозможности поместить произвольные устройства в пространство имен сети, это действительно имеет смысл, поскольку мосты являются концепцией уровня 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
Сценарии в конце этого вопроса предназначены для ветеринарного подхода. Сценарии для прямого подхода могут быть найдены в истории редактирования. Переменные в сценариях, которые, кажется, используются без предварительной установки их, устанавливаются в среде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 h m 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>
grep veth /proc/modules
ничего не перечисляет, но я не знаю, насколько это убедительно. Экземпляры Linode не имеют установленного ядра внутри раздела ОС, поэтому я не уверен, что смог бы загрузить отсутствующий модуль в любом случае.lsmod
Производит ли вообще какой-либо вывод? Есть ли каталог/lib/modules
?lsmod: command not found
, Существует/lib/modules
, но в нем нет никаких модулей , просто набор каталогов для ядра, содержащих пустыеmodules.dep
файлы. Я покопаюсь в помощи Линоде и выясню, так ли это должно быть.Ответы:
Вы можете запустить ссылку OpenVPN внутри пространства имен, а затем выполнить каждую команду, которую хотите использовать, эту ссылку OpenVPN внутри пространства имен. Подробности о том, как это сделать (не моя работа) здесь:
http://www.naju.se/articles/openvpn-netns.html
Я попробовал это, и это работает; Идея состоит в том, чтобы предоставить собственный сценарий для выполнения фаз up и route соединения OpenVPN внутри определенного пространства имен вместо глобального. Я цитирую приведенную выше ссылку на случай, если в будущем она отключится:
Единственная загвоздка в том, что вам нужно быть пользователем root для вызова
ip netns exec ...
и, возможно, вы не хотите, чтобы ваше приложение запускалось как root. Решение простое:источник
Оказывается, вы можете поместить туннельный интерфейс в пространство имен сети. Вся моя проблема заключалась в ошибке при поднятии интерфейса:
Проблема в «ссылке на область», которую я неправильно понял, поскольку она влияет только на маршрутизацию. Это заставляет ядро устанавливать адрес источника всех пакетов, отправляемых в туннель
0.0.0.0
; предположительно, сервер OpenVPN затем отбросит их как недействительные согласно RFC1122; даже если это не так, пункт назначения, очевидно, не сможет ответить.Все работало правильно в отсутствие сетевых пространств имен, потому что встроенный скрипт конфигурации сети openvpn не допустил этой ошибки. И без «ссылки на область действия», мой оригинальный сценарий работает также.
(Вы спросите, как я это обнаружил?
strace
Запустив процесс openvpn, установите в hexdump все, что он читает из туннельного дескриптора, а затем вручную декодируйте заголовки пакетов.)источник
Ошибка при попытке создания устройств veth вызвана изменением
ip
интерпретации аргументов командной строки.Правильный вызов
ip
для создания пары устройств veth(
name
вместоdev
)Теперь, как вывести трафик из пространства имен в VPN-туннель? Поскольку в вашем распоряжении только устройства настройки, «хост» должен маршрутизировать. Т.е. создайте пару veth и поместите одну в пространство имен. Подключите другой через маршрутизацию к туннелю. Таким образом, включите переадресацию, а затем добавьте необходимые маршруты.
Для примера предположим, что
eth0
это ваш основной интерфейс,tun0
ваш интерфейс VPN-туннеля иveth0
/veth1
пара интерфейсов которогоveth1
находятся в пространстве имен. В пространстве имен вы добавляете только маршрут по умолчанию дляveth1
.На хосте вам нужно использовать политику маршрутизации, см. Здесь, например. Что тебе необходимо сделать:
Добавить / добавить запись как
к
/etc/iproute2/rt_tables
. Этим вы можете назвать (еще не созданную) таблицу по имени.Затем используйте следующие утверждения:
Я не могу попробовать это здесь с такой настройкой, как ваша, но это должно делать именно то, что вы хотите. Вы можете увеличить это с помощью правил фильтрации пакетов, так что ни vpn, ни "гостевая" сеть не нарушаются.
NB. Перемещение
tun0
в пространство имен в первую очередь выглядит правильным решением. Но, как и ты, я не получил это на работу. Политика маршрутизации выглядит следующим правильным решением. Решение Mahendra применимо, если вы знаете сети VPN, и все другие приложения никогда не получат доступ к этим сетям. Но ваше начальное состояние («весь трафик и только трафик к / от определенных процессов проходит через VPN») звучит так, как будто последнее не может быть гарантировано.источник
Если сети, к которым вы получаете доступ через VPN, известны, вы можете отредактировать таблицу маршрутизации, чтобы добиться желаемого.
Обратите внимание на ваш текущий маршрут по умолчанию.
# ip route | grep default default via 192.168.43.1 dev wlo1 proto static metric 1024
Выполните VPN, и это представит запись маршрутизации.
Удалите текущий маршрут по умолчанию (который добавлен 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
Добавьте пользовательские маршруты в сети, которые находятся в VPN для маршрутизации через tun0.
# ip route add <net1>/16 dev tun0
# ip route add <net2>/24 dev tun0
Теперь все соединения net1 и net2 будут проходить через VPN, а сброс будет происходить напрямую (через wlo1 в этом примере).
источник