О tinc

2018-05-02

Непонятно почему, заинтересовался я VPNами. Оказывается, VPNом называют всё что ни попадя. Совершенно разные технологии, созданные для разных целей.

Путаницы вносит ещё наверное то, что в Android VPNом называют довесок к сетевым настройкам. Приложение, которое может гонять через себя трафик любого другого приложения. Как оно гоняет трафик, через настоящий VPN, через прокси, через /dev/astral — не важно. В Android всё это будет называться VPN.

Typical VPN Vision

Начнём с простого. HTTP proxy. Старый добрый Squid. Или даже маленький его карманный «аналог» Polipo. Как правило это кэширующие прокси. Это значит, что если много пользователей будут запрашивать одну и ту же страницу, эта страница может быть сохранена в кэше и выдана из кэша. Можно сэкономить на трафике. Существенно, кстати. Поэтому раньше провайдеры частенько принудительно заруливали клиентов на прокси. Это называлось transparent proxy.

Но с HTTPS так не выйдет. Потому что тут есть сертификаты, и клиент проверяет идентичность сервера. Поэтому HTTPS трафик пропускается через прокси с помощью метода CONNECT. Получается, что браузер разговаривает с прокси по протоколу HTTP (или HTTPS), и говорит ему: «А соедини-ка меня с тем вот сервером на таком-то порту». И дальше шлёт через прокси произвольный трафик на этот порт. Как правило, прокси сконфигурированы пускать клиентов только на 443 порт.

Ребята пошли дальше и подумали, что вовсе не нужен HTTP, чтобы общаться с прокси. И придумали специальный протокол под названием SOCKS. Это именно протокол общения с прокси. И в текущей его версии под номером пять можно даже попросить прокси перенаправлять UDP датаграммы на нужный сервер.

SOCKS изначально придумывался как способ договориться с межсетевым экраном. Мол, пусти меня туда, потому что. Можно рассматривать это как некий аналог UPnP, только не ограниченный локальной сетью.

SOCKS оказался достаточно удобным, чтобы направить трафик конкретного приложения (которое умеет быть SOCKS клиентом) через некоторое другое приложение-прокси, чтобы это прокси сделало с этим трафиком что-нибудь хитрое. Именно таким образом организуется вход в туннель Shadowsocks или Тёмный Интернет Tor. Обратите внимание, что соответствующие прокси вы запускаете локально, максимум, в той же локальной сети. А вот вход в них, перенаправление, например, трафика браузера, делается через SOCKS.

Можете побаловаться вот такой командой, если у вас есть ssh доступ к какому-нибудь серверу:

$ ssh -D 1080 -f -C -q -N user@server

У вас получится маленький прокси «для бедных» на localhost:1080, который будет гонять трафик по ssh через сервер, к которому вы подключились. Можете направить на него трафик того же браузера и посмотреть, будет ли тормозить. В принципе, с помощью программок, называемых общим словом proxifier, можно заслать в SOCKS прокси трафик почти любого приложения, даже которое не знает, что такое SOCKS.

SOCKS прокси — это лишь точка входа в какой-то туннель. И запускать прокси где-то там — не очень хорошая идея. Сам по себе SOCKS не занимается шифрованием данных. И даже пароли аутентификации в SOCKS5 идут открытым текстом.

SOCKS Proxy Usage

И вот тут наконец-то появляется слово VPN. Virtual Private Network. Задача состояла в том, чтобы обеспечить подключение удалённых сотрудников к локальной (частной) сети компании. Или связь нескольких удалённых филиалов. Это — бизнес. Серьёзные ребята вообще прокладывают свой личный кабель между офисами. А те, кто не может, вынуждены пользоваться публичным Интернетом. А чтобы нехорошие конкуренты не подслушали, нужно шифрование.

VPNов такого рода весьма много. Большинство — проприетарные. vpnc, OpenConnect — придумали Cisco, и подключаться там нужно к их железкам. pptp придумали Microsoft, и он лучше всего поддерживался в Windows. Единственный свободный — OpenVPN.

Все эти VPNы работают схожим образом. По принципу старых добрых аналоговых модемов. Клиент явно подключается к серверу, создаётся соединение point-to-point. А дальше весь (или не весь, как настроен клиент) трафик идёт через это соединение. Соединение разорвалось? Надо переподключаться. И всё такое.

А как же связь двух филиалов? Нет, не так. Как же связь двух датацентров? Есть у нас такая задача на одном проекте. Есть несколько географически распределённых датацентров. В каждом сидит несколько серверов. И серверам в разных датацентрах нужно интенсивно общаться. БД там реплицировать. За эталонными данными, чтобы тут закэшировать, сходить. Нужно.

Обычно для такого рода вещей используют туннели. Давным давно я использовал GRE. Просто на машрутизаторе появляется ещё один интерфейс. И нужные пакеты мы пропихиваем через него. И они магическим образом вылезают через подобный интерфейс на другом маршрутизаторе. Хоть пройдя полинтернета между ними. Туннель. И никакого специального соединения не требуется, ибо это всего лишь ещё одна обёртка пакетов перед отправкой.

На самом деле можно даже без специальных протоколов. IP можно заворачивать в IP. IPv4 в IPv6 и наоборот. Это то, что называется ipip, ipipv6, ipip6 и тому подобное. Все эти прелести давно поддерживаются ядром Linux.

Но эти туннели — без шифрования. Просто обёртка пакетов. Можно добавить шифрование IPsec. Если разберётесь, как настроить. И почему-то с этого момента эти самые туннели снова начинают называть VPN.

Но самая крутая штука из этой серии шифрованных туннелей — WireGuard. Написано с нуля. Модные шифры. Работает на уровне модулей ядра, и использует шифрование ядра. Вроде как поддерживается в Android и OpenWrt. Отлично, но дополнительные модули ядра не загрузишь в контейнер OpenVZ. И все эти туннели, как правило, требуют, чтобы оба конца туннеля имели статический адрес, что не всегда возможно.

В общем, встречайте, Tinc. Чудесная штука, ведущая родословную аж с 1998 года. (OpenVPN — с 2001). Шифрованный (aka VPN) многоконечный туннель (aka mesh сеть).

Network

Допустим, у нас есть два датацентра. В одном имеется сеть 10.10.1.0/24 и некий шлюз с публичным интернет адресом 1.2.3.4. В другом имеется сеть 10.10.2.0/24 и некий шлюз с публичным интернет адресом 3.4.5.6. Мы хотим, чтобы сервера в одной сети успешно могли подключаться к серверам в другой сети, и наоборот. Как всегда в таких случаях, сети в двух датацентрах не должны пересекаться.

Демон, который делает tinc, конечно же называется tincd. И у него очень интересная конфигурация. Один демон может обслуживать несколько различных mesh сетей. А для каждой сети нужна отдельная папочка конфигурации. Вот и создадим /etc/tinc/vpntun/hosts. «vpntun» — это название нашей сети. Теоретически, оно может быть любым, но по умолчанию tincd создаст tun интерфейс с этим именем, и лучше, чтобы это было одно слово, без дефисов и подчёркиваний.

В подкаталоге «hosts» нужно сложить файлы хостов. Каждый демон tincd должен знать всех других демонов tincd в той же mesh сети «в лицо». Иначе он откажется иметь с ними дело. Поэтому на наших обоих гейтвеях должно лежать по два файла. Это составляет некоторую сложность в конфигурации tinc. Файлы хостов нужно распространить по всем хостам.

Пусть один файл называется netA:

Address = 1.2.3.4

Subnet = 10.10.1.0/24

-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

А другой называется netB:

Address = 3.4.5.6

Subnet = 10.10.2.0/24

ConnectTo = netA

-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

В этих файлах описана вся конфигурация нашей сети.

Address — это публичный статический адрес, по которому можно подключиться к данному tincd. Если, конечно, он есть. Если нет, ну что ж, значит, этот tincd будет работать в «клиентском» режиме, сам принимать подключения не будет, но может подключаться в другим серверам. Для подключения нужно открыть порт 655 (по умолчанию), и tcp, и udp.

Subnet — сети (можно указать несколько), обслуживаемые данным tincd. Это очень важный параметр. Дело в том, что после того, как пакет будет направлен ОС в tun интерфейс, tincd должен решить, на какой узел mesh сети он должен его направить. Для этого и используются эти сведения. Маршрутизация. Если вы хотите «выйти» из mesh сети в интернеты, вам нужно объявить, что один из узлов «обслуживает» сеть 0.0.0.0/0.

ConnectTo — к какому узлу подключаться. Mesh сеть надо с чего-то начать. И в данном случае сеть B подключается к сети A. Если сетей больше двух, вероятно, имеет смысл настроить так, чтобы все сети подключались к некоторому «центральному» узлу. Дальнейший трафик, согласно идеологии tinc, может идти и напрямую между ближайшими tincd демонами. Но для начала они должны как-то все узнать друг о друге.

А дальше идёт публичный ключ. Тут как в ssh, аутентификация узлов происходит по RSA ключу. Публичные известны всем. А приватные каждый узел хранит у себя. Сгенерить ключи просто. Команда tincd -n vpntun -K4096 создаст файлы /etc/tinc/vpntun/rsa_key.priv и /etc/tinc/vpntun/rsa_key.pub. А ещё допишет публичный ключ в «свой» файл в каталоге hosts.

Какой хост «свой» задаётся в следующем файле конфигурации /etc/tinc/vpntun/tinc.conf. Тут достаточно написать кто мы есть и какую версию IP будем использовать.

Name = netA
AddressFamily = ipv4

Соответственно, на другом хосте вместо «netA» должно быть «netB».

И этого ещё недостаточно. Нужно настроить tun интерфейс, и запульнуть в него нужные маршруты. Делается это двумя скриптами, которые tincd запускает, когда создаёт или гасит интерфейс. Эти файлы должны быть исполняемыми (chmod +x tinc-*).

Файл /etc/tinc/vpntun/tinc-up:

#!/bin/sh

ip link set $INTERFACE up
ip addr add 10.10.1.1/16 dev $INTERFACE

ip route add 10.10.0.0/16 dev $INTERFACE

Что мы тут делаем? Мы подымаем интерфейс. Это понятно. И назначаем этому интерфейсу такой же адрес, что на уже существующем eth1. Это возможно, пока маски на eth1 и vpntun различаются, а они различаются. На самом деле без разницы, какой IP вы навесите на интерфейс туннеля. И лучше навесить адрес из локальной сети, чтобы не запутаться. Однако иметь на интерфейсе туннеля некий уникальный адрес имеет смысл, когда вы подключаете к VPN один единственный хост. Тогда tinc нет нужды знать, в какой локальной сети этот хост сейчас находится, он просто будет слать пакеты на этот единственный уникальный туннельный адрес.

И добавляем маршрут. Если мы знаем, что все наши последующие датацентры будут в сетях 10.10.2.0/24, 10.10.3.0/24 и так далее, мы можем просто зарулить всю сеть 10.10.0.0/16 в tinc. Тогда локалка будет локально. А остальные датацентры будут где-то там, за tinc. Очень удобно.

На другом tincd tinc-up будет таким же, только изменится адрес интерфейса. Он будет 10.10.2.1/16.

Файл /etc/tinc/vpntun/tinc-down:

#!/bin/sh

ip addr del 10.10.1.1/16 dev $INTERFACE
ip link set $INTERFACE down

ip route del 10.10.0.0/16

Если tinс погашен, все следы нужно замести. Хотя не обязательно.

Ну вроде и всё. Много файлов? Ну зато они все на месте и выполняют свою функцию. Как сисадмин говорю, что конфигурация tinc очень удобна. Единственная сложность: синхронизировать содержимое hosts на все узлы. Вероятно, имеет смысл эти файлы вообще держать где-то в системе контроля версий, и рассылать через Ansible. Таки там зашита топология сети.

После создания всех файлов можно запустить tincd в дебаге:

$ sudo tincd -n vpntun -D -d3

И посмотеть, что выйдет. Завершить демона можно по Ctrl + \.

Если всё хорошо, то заставляем эту нашу сеть vpntun подыматься самостоятельно при старте. В файл /etc/tinc/nets.boot нужно добавить строчку vpntun. Ну и убедиться, что сервис tinc стартует при запуске системы.

Tinc Logo

Итак, у нас есть разные задачи. Направить трафик одного приложения (или даже отдельных запросов) куда-нибудь туда, чтобы что-нибудь обойти. Подключить один хост к закрытой сети где-то там. Соединить несколько сетей безопасным образом. И для этого у нас есть прокси, VPN, туннели. А ещё есть tinc, который не просто зашифрованный туннель из одной точки в другую, а волшебный телепорт для передачи пакетов в несколько связанных точек.