Pull to refresh

Изолирование приложения с IP-адресом из VPN другой страны на примере Steam

Reading time 7 min
Views 67K
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-коннекта на домашнюю заначенную машину. Это даже будет работать (за последствия не ручаюсь).

Практические действия



Подготовка:
  1. Скопировать SSH ключ к root'у на сервер (если ключа нет, сгенерировать: sudo ssh-keygen). Я обычно делаю ключ локальному root'у, чтобы не путать его со своим ключом. Ключ копируется командой sudo ssh-copy-id root@server.
  2. Проверить, что у SSH на удалённом сервере разрешено использовать туннели. В файле /etc/ssh/sshd_config переменная PermitTunnel должна быть раскомментирована и выставлена в yes.
  3. Выбрать произвольное число (echo $RANDOM). Это будет наш номер туннеля. Запомним его (или возьмём другое любимое число, например, 42).
  4. Настроим удалённый сервер. Я пишу для 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 -A 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 для трансляции наших пакетов из туннеля, и выключаем его как только интерфейс пропадает.
  5. Разрешим маршрутизацию на сервере. /etc/sysctl.d/enable_routing.conf
    net.ipv4.conf.all.forwarding = 1
    


Мы практически закончили. Осталось только запустить steam. Всё ниженаписанное на локальной машине.
  1. Выключить все предыдущие копии steam'а, включая иконку в трее
  2. Установить соединение с сервером: sudo ssh -w 42:42 root@server. Опция -w говорит создать tun42 локально и связать его (создав) с удалённым tun42.
  3. В соседней консоли:
    xhost +
    sudo ip net add steam
    sudo ip link set netns steam dev tun42
    sudo ip net exec steam ip addr add  100.64.42.2/24 dev tun42
    sudo ip net exec steam ip link set up dev tun42
    sudo ip net exec steam ip route add default via 100.64.42.1
    sudo ip net exec steam 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'а
Tags:
Hubs:
+99
Comments 84
Comments Comments 84

Articles