http://habrahabr.ru/post/123140/
Abstract: Изоляция приложения на уровне сети использованием network namespaces Линукса. Организация SSH-туннелей.
Традиционно, большая часть статьи будет посвящена теории, а скучные скрипты — в конце статьи. В качестве субъекта для экспериментов будет использоваться Steam, хотя написанное применимо к любому приложению, включая веб-браузеры.
Вместо вступления. Я просто покажу эту картинку:
147%… Что-то мне это напоминает. Впрочем, хабр не для политики.
Цена на игры в Стиме зависит от региона. Регион — от IP'шника. Есть желание иметь цены в рублях, а не в евро.
Для этого мы используем VPN через SSH с использованием tun-устройств, плюс network namespaces для изоляции приложения от всех остальных сетевых устройств.
Network namespaces
Традиционно, приложение, запускающееся даже с правами пользователя, имеет полный доступ в сеть. Оно может использовать любой сетевой адрес, существующий в системе для отправки пакетов.
Более того, большинство десктопных приложений вообще ничего не понимает в интерфейсах, так как предполагают, что у системы есть только один сетевой интерфейс и не даёт возможности указать, каким из интерфейсов надо пользоваться. Серверное ПО обычно имеет эту опцию (какой адрес использовать в качестве адреса отправителя), но для десктопов это непозволительная роскошь.
Если у нас есть несколько интерфейсов (один из которых относится к VPN), то нет штатных методов сказать стиму, что надо использовать его, а не eth0/wlan0. Точнее, мы можем «завернуть» весь трафик в VPN, но это не всегда желательно. Как минимум — рост latency и снижение скорости (даже если VPN ведёт на супербыстрый сервер, увеличение latency, оверхед от туннеля и фиксированная ширина локального канала ставят TCP в положение, когда приходится резать скорость). Как максимум — одно дело «покупать через русский VPN», другое дело — пускать туда весь трафик. Меня совсем не прельщает использование VPN
для получения защиты роскомнадзором от оппозиции и вольнодумства.
В этих условиях возникает большое желание оставить один на один конкретное приложение и заданный сетевой интерфейс. Один. Сконфигурированный для нужд только этого приложения.
Для решения этой задачи в Linux, уже довольно давно (аж с 2007 года) существует технология, называемая network namespaces, то есть пространства имён для сетей. Суть технологии: над сетевыми интерфейсами создаётся подобие «каталогов», в каждом каталоге может быть несколько сетевых интерфейсов и приложений. Приложение, оказавшееся в заданном сетевом пространстве имён, может использовать (и видит) только те сетевые интерфейсы, которые отнесены к этому пространству.
Картинка ниже поясняет происходящее:
Подобное изобилие конфигураций, конечно, не совсем десктоп, но зато позволит объяснить происходящее в чистом виде.
Разные namespace'ы выделены разными цветами. Указанные интерфейсы доступны только из указанных неймспейсов и никак иначе. Например, красный namespace имеет доступ только к tap1, veth1 и lo, синий — к eth1, eth2, lo и veth0, зелёный — к tun0 и lo. За пределами namespace'ов остаются eth0, собственный интерфейс br1, tap0 и lo.
Заметим, в каждом namespace'е свой lo! Если в синем namespace'е на lo будет слушать mysql, то из зелёного (и любого другого, кроме синего) namespace'а получить к нему доступ не удастся. Пожалуй, это самая приятная особенность. Вторая особенность — мы можем использовать один и тот же IP адрес в разных пространствах имён на разных интерфейсах, и нам за это ничего не будет. Разумеется, таблица маршрутизации у каждого пространства имён — своя.
Внимательный читатель почуял дух виртуализации. Разумеется, да. Network namespaces (вместе с остальными технологиями такого уровня) являются важной частью LXC (контейнерной виртуализации в линуксе). Но, в отличие от openvz и многих других схожих технологий, компоненты LXC настолько общие, что позволяют использовать их как самостоятельные инструменты. И это правильный unix-way: каждый делает только одну вещь, но хорошо; хотя пуристы могут сказать, что «пихать всё в ядро — плохо».
Наиболее интересной картинка получается, если оставить приложение с tun/tap интерфейсом, который ведёт за пределы текущей сети (на картинке выше — зелёный namespace c одиноким tun'ом). Если tun приземляется где-то за пределами компьютера (например, на vpn-сервере), то приложение не будет иметь никакой возможности понять, какие сетевые настройки реально у компьютера. Если VPN, например, ведёт с Кипра в Россию, то любое запущенное в зелёном namespace'е приложение будет получать адрес из России и выходить в сеть так, как будто оно находится в России. Собственно, это нам и требуется для того, чтобы Steam поверил, что у нас русский IP и согласился продавать игры за пол-цены.
С определением страны у Стима всё несколько странно. Без VPN Стим иногда показывает мне цены в евро, иногда в рублях, и понять закономерности я не смог. Русский VPN снял все вопросы — цены всегда в рублях.
Краткая подсказка по работе с network namespaces (практические советы ближе к концу статьи):
ip netns
— список network namespaces (во всех командах ip netns можно сокращать до ip net)
ip netns add/delete foo
— создать/удалить network namespace с именем foo
ip netns exec foo /usr/bin/bin
— запустить программу в заданном network namespace (заметим, перетаскивать уже запущенные приложения нельзя)
ip link set eth99 netns foo
— засунуть интерфейс в заданное пространство
И несколько трюков:
ip netns exec foo ip link
— список интерфейсов внутри пространства foo
ip netns exec foo tcpdump -ni eth99
— tcpdump на нём. Внимание, из-за специфичной работы exec'а, вывод на экран появится только после нажатия Ctrl-C. Если раздражает — см ниже.
sudo ip netns exec foo login -f username
— запустить логин внутри namespace'а. Залогинившийся пользователь будет работать уже в заданном пространстве имён с отлично сконфигурированными настройками терминала/лидера группы и т.д.
Организация VPN'а через SSH
Я не люблю openvpn, openswan и прочие приложения за избыточность. Я признаю их необходимость в некоторых ситуациях, но когда могу, я стараюсь использовать SSH. Причин несколько:
1) SSH есть из коробки на любом сервере.
2) SSH использует TCP, а не gre/udp, и если надо подключиться из экзотического места с хреновым WiFi, то ssh на 22 и 443 портах наше всё. Если 22ой порт ещё могут закрыть, то 443 — точно нет, т.к. его использует HTTPS (и в случае его блокировки негодующие хомячки снесут всё на своём пути к гуглу/фейсбуку и т.д.)
3) SSH-клиент есть на любой машине в пределах достягаемости, то есть я могу поднять нужный мне туннель с любой unix-машины. В 3-5 шагов — с windows.
4) Настройки степени туннелирования легко регулировать. Если мне нужны только socks-proxy, то я не буду изобретать l2-туннели.
5) Без дополнительного шаманства можно иметь несколько параллельных подключений, причём в любой комбинации — к одному серверу с разных клиентов, к разным серверам с одного клиента и т.д. Совсем
долбанутые на голову увлечённые могут вообще связывать в единый L2-сегмент континенты и сети посредством одинокой замухрышки, сидящей на 3g где-то в пятой точке мира за хардкорным натом с закрытыми портами. И это даже будет работать (в той степени, в которой 3g в пятой точке мира будет способно переварить пролетающие через L3 бродкасты/мультикасты).
Хотя главная причина ещё проще: SSH — штатный инструментарий, и он умеет всё, что нужно. Зачем ещё что-то?
Итак, теория SSH-туннелей
TUN-интерфейсы
TUN-интерфейс устроен очень просто: он создаёт в системе виртуальный сетевой интерфейс (tun0, tun1 и т.д.), который другим своим «концом» смотрит в fd (файловый дескриптор) у той программы, которая его создала. Что делать с трафиком из fd программа решает сама. А приложения в системе сами решают, что делать с сетевым интерфейсом.
В случае с SSH мы говорим SSH-клиенту создать tun-интерфейс со своей стороны, SSH-серверу со своей стороны, и соединить их. Получается, что трафик с одной стороны (с клиента) вошёл, с другой стороны (с сервера) вышел. И наоборот. Чем не туннель? Чем не vpn? С учётом, что SSH ещё и шифровать трафик умеет, получаем готовый VPN. Внутри SSH это называются channel, и он их мультиплексирует, но нас это особо и не волнует.
Вот простенький рисунок того, что происходит:
Заметим, нам требуется кооперация со стороны SSH-сервера. Разрешение создавать туннели на сервере мы обсудим в секции с ниже Там же будут подробности настройки NAT'а, адресации и прочих вещей, чтобы стрелка Freedom заработала.
Примечание на полях: помимо tun-интерфейсов бывают ещё tap-интерфейсы. Они позволяют объединять L2-сегменты. Это ад, ужас, содомия и угар, но если кому-то очень хочется, то он может попробовать объединить пару сетей из разных дата-центров посредством SSH-коннекта на домашнюю заначенную машину. Это даже будет работать (за последствия не ручаюсь).
Практические действия
Подготовка:- Скопировать SSH ключ к root'у на сервер (если ключа нет, сгенерировать:
sudo ssh-keygen
). Я обычно делаю ключ локальному root'у, чтобы не путать его со своим ключом. Ключ копируется командой sudo ssh-copy-id root@server
.
- Проверить, что у SSH на удалённом сервере разрешено использовать туннели. В файле
/etc/ssh/sshd_config
переменная PermitTunnel
должна быть раскомментирована и выставлена в yes
.
- Выбрать произвольное число (echo $RANDOM). Это будет наш номер туннеля. Запомним его (или возьмём другое любимое число, например, 42).
- Настроим удалённый сервер. Я пишу для debian/ubuntu, процесс для других дистрибутивов/ОС — см в документации по настройке сети к ним. В файле /etc/networking/interfaces создаём следующие строчки:
allow-hotplug tun42
auto tun42
iface tun42 inet static
address 100.64.42.1
netmask 255.255.255.0
pre-up iptables -D POSTROUTING -t nat -s 100.64.42.0/24 -j MASQUERADE
post-down iptables -D POSTROUTING -t nat -s 100.64.42.0/24 -j MASQUERADE
Это позволит нам автоматически получать сконфигурированный интерфейс каждый раз, когда на сервере появится интерфейс tun42. Заодно мы включаем NAT для трансляции наших пакетов из туннеля, и выключаем его как только интерфейс пропадает.
- Разрешим маршрутизацию на сервере.
/etc/sysctl.d/enable_routing.conf
net.ipv4.conf.all.forwarding = 1
Мы практически закончили. Осталось только запустить steam. Всё ниженаписанное на локальной машине.
- Выключить все предыдущие копии steam'а, включая иконку в трее
- Установить соединение с сервером:
sudo ssh -w 42:42 root@server
. Опция -w говорит создать tun42 локально и связать его (создав) с удалённым tun42.
- В соседней консоли:
xhost +
sudo ip net add steam
sudo ip net exec ip addr add 100.64.42.2/24 dev tun42
sudo ip net exec ip link set up dev tun42
sudo ip net exec ip route add default via 100.64.42.1
sudo ip net exec login -f $USER
export DISPLAY=:0
steam
xhost +
разрешает подключаться к вашему X-серверу кому угодно (будте осторожны). Параноики могут изучить man xhost для указания более точных правил.
ip net add
и ip net exec
— это сокращения от ip netns add
и ip netns exec
— создать network namespace, и запустить новую сессию пользователя из под которого мы работаем. export DISPLAY=:0
говорит «использовать первый X-сервер. По умолчанию эта переменная сбрасывается при логине, а без неё steam не сможет подключиться к X-серверу
Собственнно, всё. На выходе имеем русские цены в Стиме и русскую цензуру. К счастью, защищать она будет только steam, а браузер в соседнем окне будет пользоваться не очень быстрым, но весьма свбодным кипрским интернетом.
В следующих частях:
- Как запустить 32-битный steam на x86_64 машине
- Как изолировать steam полностью, так, чтобы он работал, но при этом ни разу не запускался от рута или основного пользователя. Любовь и нанависть с пульсадуио, dri, методы определения нехватающих библиотек, отладка самого Steam'а