Хакер - VPN не для всех. Туннелируем трафик отдельных приложений с помощью Linux network namespaces
nopaywall

Содержание статьи
Немного о Linux namespaces
В Linux реализована функция пространства имен (namespace), которая отвечает за изоляцию разных ресурсов системы. Она вовсю применяется в проектах контейнеризации, например таких как Docker. Существует несколько типов пространств имен: pid, net, mnt, user, uts, ipc.
Нас интересует пространство имен для сетей (netns), которое изолирует сетевые ресурсы. Для каждого netns можно назначать: свои интерфейсы, наборы IP-адресов и портов (сокеты), таблицы маршрутизации, правила файрвола и так далее. Есть возможность перемещать интерфейсы из одного netns в другой. Физический интерфейс (например, eth0) может находиться одновременно только в одном netns.
Изначально все интерфейсы и процессы содержатся в исходном (initial) netns, у него нет конкретного имени, и он не отображается в списке. После освобождения какого-либо netns все физические интерфейсы, которые были в нем, возвращаются в initial netns. Освобождение происходит после завершения последнего процесса в этом netns. Например, даже если удалить конкретный netns, но процесс, запущенный в нем, будет активным, физический интерфейс не перенесется обратно в исходный netns до тех пор, пока процесс не будет завершен. Рассмотрим этот вариант позже.
Введение в курс
Именно netns поможет нам в туннелировании трафика отдельных приложений. Для начала нужно немного разобраться в командах управления.
База netns
За контроль netns отвечает утилита ip из пакета iproute2. Для соединения netns между собой можно использовать пару виртуальных интерфейсов veth. Рассмотрим пример создания нескольких netns и их соединения. Для этого выполним
$ sudo ip netns add ns_1 $ sudo ip netns add ns_2
Наличие этих netns смотри командой ip netns list или просто ip netns, так как list — действие по умолчанию. Добавим виртуальную пару при помощи команды
$ sudo ip link add dev virt01 type veth peer name virt02
Управление netns и veth с помощью ipИнтерфейсы добавились, теперь переместим virt01 в netns ns_1, а virt02 — в ns_2.
$ sudo ip link set virt01 netns ns_1 $ sudo ip link set virt02 netns ns_2
Если не появилось сообщений об ошибках, значит, все прошло успешно. Для выполнения команды внутри netns используется команда
$ sudo ip netns exec <имя netns> <команда для выполнения>
Например, посмотреть список доступных интерфейсов внутри netns ns_1 можно при помощи
$ sudo ip netns exec ns_1 ip link
Почитав man, узнаем, что выполнять команды ip внутри netns можно при помощи $ sudo ip -n <имя netns>, а значит, $ sudo ip netns exec ns_1 ip link заменяем на $ sudo ip -n ns_1 link.
Удостоверимся, что интерфейсы перенеслись в нужные netnsПо умолчанию интерфейсы, созданные или перемещенные в netns, пребывают в отключенном состоянии, даже lo.
Добавим нашим интерфейсам virt01 и virt02 по IP-адресу и переведем их в состояние UP. Для этого воспользуемся «прокачанной» командой, подсмотренной в man:
$ sudo ip -n ns_1 addr add 10.0.0.1/24 dev virt01 $ sudo ip -n ns_2 addr add 10.0.0.2/24 dev virt02 $ sudo ip -n ns_1 link set dev virt01 up $ sudo ip -n ns_2 link set dev virt02 up $ sudo ip -n ns_1 addr show $ sudo ip -n ns_2 addr show
Соединяем вместе нашу виртуальную паруКак я уже говорил, у каждой netns своя таблица маршрутизации, проверим это.
Действительно, таблицы отличаютсяДля проверки связи между ns_1 и ns_2 воспользуемся командой ping.
$ sudo ip netns exec ns_1 ping -c 4 10.0.0.2
Соединение установлено успешноКак ты помнишь, адрес 10.0.0.2 принадлежит интерфейсу virt02, который находится в ns_2. Похожим способом netns соединяют с физическим eth0.
Выполнение команд и запуск процессов внутри netns
Как ты помнишь, команды внутри netns выполняются при помощи
$ sudo ip netns exec <имя netns> <команда>
Чтобы не писать все это каждый раз, запустим bash!
$ sudo ip netns exec ns_1 bash
После этого все команды будут исполняться внутри netns — заодно и проверим ситуацию с файрволом. Чтобы вернуться, пиши exit или CTRL + D.
Список правил пустУ меня всегда присутствуют правила в файрволе, так что это точно отдельный набор.
Перейдем к примеру с возвращением физического интерфейса в initial netns. Для этого проверим текущее расположение enp0s3 и перенесем его в ns_2.
$ sudo ip link set dev enp0s3 netns ns_2
Как видно, enp0s3 больше нет в init netnsПоднимем его в netns ns_2 и запустим WireShark от имени пользователя (eakj) там же.
$ sudo ip -n ns_2 link set dev enp0s3 up $ sudo ip -n ns_2 link $ sudo ip netns exec ns_2 sudo -u eakj wireshark 2>/dev/null &
WireShark запущен в netns ns_2Теперь удалим netns ns_2 и посмотрим, вернется ли enp0s3 в initial netns:
$ sudo ip netns del ns_2
enp0s3 не освободится, пока процесс WireShark не будет завершен, даже несмотря на удаление ns_2Закроем WireShark и посмотрим, даст ли это результат.
После завершения процесса enp0s3 вернулся в initial netnsЭтого должно быть достаточно, чтобы понять, как работают netns и как ими управлять. Перейдем к OpenVPN.
Разбираемся с OpenVPN
Не так давно я писал о том, как поднять собственный OpenVPN на арендованном сервере. Если ты следовал гайду, то у тебя уже настроен клиентский файл, который можно использовать. Конфигурационные файлы других VPN-провайдеров тоже подойдут, так как никаких изменений на сервере делать не придется.
Изменение клиентского конфига
Познакомимся с новыми директивами OpenVPN.
ifconfig-noexec— запрещает клиенту автоматическое выполнениеifconfigдля добавления IP-адреса интерфейсуtun. Вместо этого передаст нужные параметры в качестве переменных окружения.route-noexec— та же ситуация, что и сifconfig-noexec, только вместо IP он не будет добавлять маршруты. Необходимые параметры передадутся.route-up /полный/путь/к/скриптуотправит на скрипт переменной окружения$script_typeстрокуroute-up.up /полный/путь/к/скрипту— то же, что иroute-up, только передаст строкуupпосле поднятия интерфейса tun/tap. Выполняется до директивыuser, которая должна понизить наши привилегии.down /полный/путь/к/скрипту— то же, что иroute-up, только строкуdown, после удаления интерфейса tun/tap. Выполняется после директивыuser!script-security 2позволит исполнять кастомные скрипты.
Полный список передаваемых переменных окружения можно посмотреть в man openvpn, секция Environmental Variables.
Надеюсь, ты обратил внимание на жирный шрифт. Директива up добавит наш netns, down — удалит. Для добавления и удаления netns нужны права рута. Это не проблема в случае с up, поскольку она исполняется перед user, а та, в свою очередь, понижает права до nobody. А вот с down явно будут проблемы, так как пользователь nobody не сможет удалить netns. Поэтому первым делом в клиентском конфиге закомментируй или удали строчки
user nobody group nobody
и добавь новые директивы. Пример немного измененного скрипта из прошлой статьи:
openvpn_client.conf
client dev tun proto tcp remote 127.0.0.1 1194 resolv-retry infinite nobind #user nobody #group nobody persist-key persist-tun ca /etc/openvpn/client/ca.crt cert /etc/openvpn/client/openvpn-client.crt key /etc/openvpn/client/openvpn-client.key #### на клиенте 1 tls-auth /etc/openvpn/client/ta.key 1 remote-cert-tls server cipher AES-256-GCM verb 3 script-security 2 ifconfig-noexec route-noexec up "/etc/openvpn/client/ovpn_control.sh" route-up "/etc/openvpn/client/ovpn_control.sh" down "/etc/openvpn/client/ovpn_control.sh"
где ovpn_control.sh — скрипт, который будет обрабатывать директивы.
Теперь нужен resolv.conf для netns. Он хранится в /etc/netns/<имя netns>/resolv.conf, поэтому создадим директорию:
$ sudo mkdir -p /etc/netns/<имя netns>
В нашем случае имя — это ovpn. При выполнении скрипта в resolv.conf запишутся DNS-серверы. Если netns не сможет найти /etc/netns/ovpn/resolv.conf, то обратится к /etc/resolv.conf. Поэтому убедись, что там публичные серверы, так как DNS-запросы идут через туннель и адрес твоего роутера может не найтись на стороне сервера.
Пример скрипта — ниже. Можешь заменить в нем DNS1 и DNS2 на свои любимые.
ovpn_control.sh
#!/usr/bin/bash DNS1=208.67.222.222 DNS2=208.67.220.220 case $script_type in up) /usr/sbin/ip netns add ovpn /usr/sbin/ip link set $dev netns ovpn /usr/sbin/ip -n ovpn address add $ifconfig_local/30 dev $dev /usr/sbin/ip -n ovpn link set $dev up mtu $tun_mtu [ ! -d /etc/netns/ovpn ] && mkdir -p /etc/netns/ovpn echo "nameserver $DNS1" > /etc/netns/ovpn/resolv.conf echo "nameserver $DNS2" >> /etc/netns/ovpn/resolv.conf ;; route-up) /usr/sbin/ip -n ovpn route add default dev $dev ;; down) /usr/sbin/ip netns del ovpn ;; esac
Управление клиентом
В прошлой статье для управления клиентом использовались только файлы с расширением .service для systemd. Сейчас рассмотрим и другой вариант: управление при помощи скрипта. Пригодится тем, у кого дистрибутив без systemd.
Пишем скрипт
Ниже приведу свой скрипт и в двух словах объясню, что он делает.
- Принимает на стандартный вход первым аргументом (
$1) одну из строк:start,stop,restartилиexec. - В соответствии со строкой выполняет одну из функций:
- если
start, то в фоновом режиме запускаетopenvpnс конфигом, который приведен ниже, а также пишет лог в/tmp/ovpn_log.txt; - если
stop, то процессopenvpnпрекращается сигналомSIGTERM(kill -15или простоkill). Это даетopenvpnвозможность обработать его и послать переменной окружения$script_typeстрокуdownв другой скрипт (см. выше). Номер процесса берется из PID-файла. Также стоит заглушка для случая с логаутом. Детали — ниже; restartзапускает сначалаstop, потомstart;execв netns выполняет от имени пользователя все, что передано после него.
- если

INFO
При завершении сессии система, видимо, убивает процессы сигналом SIGKILL(kill -9), и у OpenVPN уже нет шансов послать скрипту команду down и, соответственно, удалить netns. Если ты будешь делать логаут с запущенным OpenVPN, то после захода обратно netns может быть поднят, но интерфейса tunуже не будет. В этом случае нужно будет выполнить
$ sudo ./openvpn_netns.sh restart Далее — сам скрипт. Подправь x_user, conf_file, log_file, pid_file, netns_name, как тебе понравится.
openvpn_netns.sh
#!/usr/bin/bash x_user=eakj conf_file=/etc/openvpn/client/openvpn_client.conf log_file=/tmp/ovpn_log.txt pid_file=/var/run/ovpn.pid netns_name=ovpn die() { printf "%s\n" "$@" exit 1 } usage() { printf "%s\n\t%s\n" "USAGE:" "$0 start|stop|restart|exec" exit 1 } start_() { [ -f /var/run/netns/$netns_name ] && die "[E:] netns $netns_name is already up. If it shouldn't be, try restart" if [ -f "$conf_file" ]; then printf "%s\n" "[S:] starting with $conf_file" openvpn --writepid "$pid_file" --log "$log_file" --config "$conf_file" &>/dev/null & else die "[E:] $conf_file not found" fi } stop_() { if [ -f "$pid_file" ]; then _PID=$(cat "$pid_file") ## PID-файл нашли, теперь проверим, существует ли процесс ## с номером, указанным в файле if ps -ef | grep "$_PID" | grep -v grep &>/dev/null; then printf "%s\n" "[S:] stopping openvpn process with PID $_PID" kill $_PID else printf "%s\n" "[E:] $pid_file file exists, but actual process id not found" fi [ -f "$log_file" ] && rm -f "$log_file" &>/dev/null [ -f "$pid_file" ] && rm -f "$pid_file" &>/dev/null fi ## netns может остаться, если ты разлогинился с включенным OpenVPN. ## Проверим, существует ли процесс, но уже не по PID-файлу ## (его может не быть), а спарсим название конфиг-файла, ## с которым он должен был быть запущен. ## Если процесс не найден, но netns каким-то чудом существует, ## скорей всего, OpenVPN был убит -9 сигналом if ! ps -ef | grep -iE "openvpn.+$(basename $conf_file)$" &>/dev/null && [ -f "/var/run/netns/$netns_name" ]; then ip netns del $netns_name die "[W:] probably openvpn was killed with SIGKILL, deleting $netns_name netns" fi } (( $UID != 0 )) && die "[E:] must run as root" (( $# < 1 )) && usage case "$1" in start) start_ ;; stop) stop_ ;; restart) "$0" stop sleep 5 "$0" start ;; exec) shift ip netns exec $netns_name sudo -u $x_user "$@" ;; *) usage ;; esac
Рассмотрим примеры использования. Подключимся к серверу, проверим наличие netns и выполним несколько команд. Если не создается netns, то смотри /tmp/ovpn_log.txt, подсказка, скорее всего, там.
$ sudo ./openvpn_netns.sh start $ ip netns $ sudo ./openvpn_netns.sh exec ping -c 4 8.8.8.8 $ sudo ./openvpn_netns.sh exec curl ifconfig.co $ sudo ./openvpn_netns.sh exec firefox &>/dev/null &
Firefox успешно запустился в netnsКстати, будь внимателен при выполнении команд от рута и переводе их в фоновый режим (& в конце команды). В этом случае команда сработала без проблем, так как я недавно вводил пароль. Если бы прошло больше времени и я бы попытался запустить таким образом firefox, то в итоге в фоновый режим ушел бы запрос от sudo ввести пароль. Пришлось бы пользоваться командами bg, fg и CTRL-Z. Чтобы упростить себе жизнь, добавь следующую строчку в /etc/sudoers, где eakj — имя пользователя, конечно.
eakj ALL=NOPASSWD:/путь/к/openvpn_netns.sh
Это позволит выполнять скрипт с помощью sudo, не запрашивая пароля.
Пробуем остановить openvpn и удалить netns.
$ sudo ./openvpn_netns.sh stop $ ip netns
Остановка OpenVPN и netns успешнаФайлы .service для systemd
В качестве шаблона я брал файлы .service из блога Остина Адамса. Приступим к модификации.
Для начала нужно создать директорию /etc/systemd/system/openvpn-client@.service.d/ и записать туда netns.conf. Опять же, в Ubuntu это, скорее всего, будет просто /etc/systemd/system/openvpn@.service.d/.
$ sudo mkdir /etc/systemd/system/openvpn-client@.service.d/ $ sudo tee /etc/systemd/system/openvpn-client@.service.d/netns.conf << EOF [Unit] Requires=netns@%i.service After=netns@%i.service [Service] ## Needed to call setns() as ip netns does CapabilityBoundingSet=CAP_SYS_ADMIN EOF
Теперь создадим файл netns@.service в /etc/systemd/system/. Благодаря конфигу, который добавили ранее, этот сервис будет стартовать после первого запуска systemctl start openvpn-client@openvpn-client.conf. Он создаст netns с таким же именем, как и конфиг для OpenVPN.
$ sudo tee /etc/systemd/system/netns@.service << EOF [Unit] Description=network namespace %I [Service] Type=oneshot ExecStart=/usr/sbin/ip netns add %I ExecStop=/usr/sbin/ip netns del %I RemainAfterExit=yes EOF
Посмотреть статус можно при помощи команды
$ systemctl status netns@openvpn_client
А убедиться в том, что он создал netns, — командой $ ip netns. Также тут есть один нюанс: после остановки сервиса openvpn командой
$ sudo systemctl stop openvpn-client@openvpn_client.conf
сервис netns@openvpn_client не остановится, а значит, и netns продолжит жить.
Также придется немного подправить ovpn_control.sh.
- Добавить распознавание имени конфиг-файлов.
- Убрать строчку создания netns, так как это уже будет делать
netns@.service. - Поскольку имя netns теперь зависит от имени клиентского конфига OpenVPN, нужно создавать новые директории для
resolv.conf. - Удалить
down, так какsystemdего все равно не вызывает.
#!/usr/bin/bash DNS1=208.67.222.222 DNS2=208.67.220.220 basename="$(basename "$config")" ns="${basename%.conf}" case $script_type in up) /usr/sbin/ip link set $dev netns $ns /usr/sbin/ip -n $ns address add $ifconfig_local/30 dev $dev /usr/sbin/ip -n $ns link set $dev up mtu $tun_mtu [ ! -f /etc/netns/$ns/resolv.conf ] && mkdir -p /etc/netns/$ns echo "nameserver $DNS1" > /etc/netns/$ns/resolv.conf echo "nameserver $DNS2" >> /etc/netns/$ns/resolv.conf ;; route-up) /usr/sbin/ip -n $ns route add default dev $dev ;; esac
Стоит помнить, что этот скрипт уже не подойдет для использования с openvpn_netns.sh, поэтому рекомендую сохранить ovpn_control.sh для systemd под другим названием, а также не забыть подправить его в openvpn_client.conf. Посмотрим, как файлы .service работают на практике.
Запускаем клиентский конфиг через systemd.
$ sudo systemctl start openvpn-client@openvpn_client $ sudo systemctl status openvpn-client@openvpn_client $ ip netns
Запуск сервиса openvpn_client прошел успешноПроверим, как дела у сервиса netns@openvpn_client:
$ systemctl status netns@openvpn_client.service
Но уже по предыдущему скриншоту можно сказать, что все хорошо, так как сервис виден в списке $ ip netns.
netns@openvpn_client стартанул без проблемПисать для systemd отдельный скрипт выполнения команд не вижу смысла, так как это однострочник. $ sudo ip netns exec openvpn_client ping -c 2 8.8.8.8 $ sudo ip netns exec openvpn_client curl ifconfig.co $ sudo ip netns exec openvpn_client sudo -u eakj firefox &>/dev/null &
ОднострочникиОстановим клиент:
$ sudo systemctl stop openvpn-client@openvpn_client.service
А затем убедимся, что netns не удалился (ip netns). Чтобы удалить, используй команду
$ sudo systemctl stop netns@openvpn_client.service
netns остается поднятым после остановки клиентского OpenVPN-сервисаЗаключение
Linux network namespaces — это мощный инструмент, который с легкостью позволяет изолировать сетевые ресурсы системы и может оказаться полезным не только для крупных проектов, но и дома. И описанный в статье трюк с VPN — это лишь одно из возможных применений.
Читайте ещё больше платных статей бесплатно: https://t.me/nopaywall