Распределенная природа мессенджера Tox
Пока правообладатели собираются заблокировать централизованный Telegram, сообщество пользователей распределенного мессенджера Tox растет. Сегодня, согласно статистике сайта www.toxstats.com, Россия занимает второе место после США по количеству пользователей отставая всего на какие-то 30–50 узлов.
В данной публикации я бы хотел рассказать про распределенную природу данного мессенджера, общие принципы работы DHT-сети Tox, а так же как »догнать и перегнать Америку» по количеству нод.
Каждая нода Tox определяется IP адресом, номером порта и публичным 256-и битным ключом. Существует два условных вида нод:
- Bootstrap-нода — нода, которая обслуживает работу сети, но не взаимодействует напрямую с пользователем.
- Клиент Tox — нода, которая помимо обслуживания работы сети, выполняет какую-либо дополнительную работу (бот, мессенджер). При этом клиент и нода используют разные пары ключей.
Публичный ключ ноды используется для шифрования пакетов передаваемых этой ноде. Пакеты расшифровываются на стороне ноды при помощи 256-и битного приватного ключа. Для передачи DHT-пакетов используется протокол UDP.
Для «входа» в сеть Tox достаточно иметь связность с любой нодой Tox, которая уже находится в сети. Обычно для этого используется список из известных bootstrap-нод в сети интернет. Дополнительно библиотека libtoxcore использует отправку пакетов на широковещательные адреса, что позволяет подключиться к сети Tox не имея выхода в интернет (при условии, что в вашем сегменте сети есть необходимая нода). И даже без выхода в интернет две и более ноды Tox образуют изолированную сеть Tox, позволяющую взаимодействовать локальным клиентам.
Поскольку каждая нода Tox имеет уникальный идентификатор в виде публичного ключа, то отношения между двумя любыми нодами можно выразить искусственной метрикой на базе этих ключей. Этой метрикой является расстояние между ключами. Расстояние вычисляется как сложение по модулю 2 (побитовый XOR) между двумя ключами и интерпретируется как беззнаковое целое.
Из свойств операции XOR вытекает, что нулевое расстояние может быть только между одним и тем же ключом (ключом ноды), а половина всего пространства ключей по отношению к ключу ноды будет иметь максимально возможное расстояние — с некоторой условностью ноды с такими ключами можно считать «недостижимыми» и взаимодействие с подобными нодами будет достаточно редким событием.
Процесс входа в сеть Tox начинается с отправки пакета »Nodes request» известной bootstrap-ноде (одной или нескольким), где содержимое пакета представляет собой разыскиваемый публичный ключ. Нода, получившая пакет «Nodes request», ищет среди известных ей публичных ключей (кроме своего собственного и запрашиваемого) ключи с наименьшим расстоянием от разыскиваемого и отвечает пакетом »Nodes response», содержащим от 1 до 4 найденных ключей и соответствующих им нод (IP/порт). Итеративно повторяя запросы «Nodes request» к нодам из ответа клиент-нода может найти другую ноду с минимальным расстоянием от искомого ключа (параллельно получая информацию о существующих промежуточных нодах).
Если в качестве искомого ключа клиент-нода будет указывать свой собственный ключ, то найденные ноды с минимальным расстоянием между ключами будут ее «соседями». Если в качестве искомого ключа клиент-нода будет указывать ключ запрашиваемой ноды, но найденные ноды буду соседями опрашиваемой ноды — так работает подсчет статистики сети.
Живость каждой известной ноды проверяется периодической отправкой пакета »Ping request», на который получатель должен ответить пакетом »Ping response». Дополнительно с некоторой периодичностью клиент-нода отправляет случайной (из известных) ноде пакет «Nodes Request» для получения информации об изменениях в DHT-сети. Неотвечающие ноды удаляются из списка известных по истечении таймаута.
Необходимость частой отправки пакета «Ping request» для большого списка известных нод приводит к неоправданной нагрузке на сеть. В то же самое время сохранение информации только о ближайших соседях будет приводить к увеличению времени поиска неизвестной ноды. Для достижения баланса в библиотеке libtoxcore введено понятие индекса ключа — это индекс первого отличающегося бита ключа по отношению к ключу ноды. Для каждого нового ключа вычисляется его индекс и для каждого индекса ядро сохраняет до 8 ключей вытесняя ключи с наибольшим расстоянием. Дополнительно ядро хранит 8 ключей ближайших соседей с тем же алгоритмом вытеснения.
В текущей реализации libtoxcore длина индекса ограничена 128 «корзинами», что при определенном везении позволяет каждой ноде хранить информацию о 1024 нодах (до некоторого времени в прошлом, пока сеть была очень-очень маленькой, использовалось 32 корзины и 190 нод соответственно). При минимальном размере пакета в 82 байта («Ping request») и опросе каждой ноды раз в 60 секунд, получаем трафик в ~22Kbit при максимальной загрузке индекса.
Из правила вычисления индекса корзины так же следует, что чем меньше расстояние между двумя ключами, тем больший индекс будет иметь ключ и тем меньше будет вероятность встретить такой ключ. В реализации библиотеки libtoxcore ключи с индексом более 127 или становятся «соседями» или попадают в 128-ю корзину в зависимости от расстояния.
Таким образом каждая нода сети Tox поддерживает эффективную связность не только с соседями, но и с нодами на «дальних рубежах» соблюдая баланс между нагрузкой на сеть и временем поиска любой другой ноды.
В отличии от DHT-нод, вся информация о которых известна или может быть получена любым клиентом DHT-сети Tox, клиентские приложения скрыты от стороннего наблюдателя — простого знания ToxID контакта (содержащего его публичный ключ) недостаточно для того, чтобы установить местонахождение этого контакта. Для соединения двух приложений Tox используется механизм луковой маршрутизации («onion routing»).
Механизм установления связи между двумя клиентами можно представить следующим образом. Два клиента (A и Z) анонсируют свой публичный ключ на ближайших (для своего публичного ключа) нодах через три случайные промежуточные DHT-ноды, каждая из которых знает только своих соседей по маршрутизации пакета, но не может прочитать содержимое пакета.
Второй клиент (Z), желающий соединиться с первым (A), так же через три случайные DHT-ноды делает запрос на установку соединения на ближайшую к искомому ключу (A) ноду, которая знает об анонсе первого клиента и маршруте, по которому требуется передать запрос от второго клиента.
Первый клиент (A) в случае принятия запроса на установку соединения, проделывает обратную операцию к ближайшей DHT-ноде второго клиента (Z).
После обмена информацией о метоположении друг-друга и временных ключах клиенты могут соединиться напрямую.
Если клиенты не желают делиться информацией о своем местоположении даже со своими контактами, они могут использовать ноды поддерживающие TCP-relay через прокси типа Tor.
Особенностью TCP-релеев является то, что они пытаются использовать «широко-известные» («well-known») прты: 443 (HTTPS) и 3389 (RDP), что затрудняет их отслеживание и идентификацию.
Для защиты распределенной сети от большинства известных угроз можно использовать простое эмпирическое правило — чем больше в сети благонадежных узлов, тем сеть устойчивее к возможным атакам. При чем для некоторых видов атак нейтрализация каждого благонадежного узла требует десяток-другой атакующих.
Исходя из описания выше, если вы пользуетесь любым клиентом Tox, то вы уже являетесь нодой сети (при этом ваша нода никак не связана с вашим ToxID за исключением того, что оба находятся на одном хосте). Если же вы пока не пользуетесь Tox, но желаете помочь проекту, вы можете установить bootstrap-ноду на подконтрольных вам серверах и компьютерах — она не потребляет много трафика или вычислительных ресурсов (в отличии от старых добрых времен Folding@home).
Детальное описание процесса сборки и установки ноды можно найти по ссылке «Вливаемся в tox-сообщество или установка ноды за 5 минут». Однако я постарался максимально упростить данный процесс собрав пакет tox-easy-bootstrap для большинства популярных дистрибутивов Linux.
Для установки пакета tox-easy-bootstrap перейдите по ссылке на репозитории проекта, выберете свой дистрибутив и следуйте простым инструкциям по добавлению репозитория и установке пакета в вашу систему.
Установщик пакета автоматически получит актуальный список публичных bootstrap-нод, создаст конфигурационный файл /etc/tox-bootstrapd.conf и запустит демона tox-bootstrapd под отдельным системным пользователем. Раз в неделю по cron специальный скрипт будет обновлять список публичных bootstrap-нод в конфигурационном файле, по этому вам не нужно будет беспокоиться о его актуальности в случае перезагрузки сервера — «поставил и забыл».
Для случаев, если на сервере используется «нормально закрытый» firewall вам может потребоваться внести разрешающие правила для входящего трафика на UDP:33445 и TCP:3389,33445 (порт TCP:443 не используется, т.к. процесс запускается под непривилегированным пользователем) — во избежании потенциальных «диверсий» эту часть я автоматизировать не стал:
-A INPUT -p tcp --dport 3389 -j ACCEPT
-A INPUT -p tcp --dport 33445 -j ACCEPT
-A INPUT -p udp --dport 33445 -j ACCEPT
Конфигурационный файл пакета /etc/tox-easy-bootstrap.conf позволяет изменить настройки по умолчанию (которые подходят для большинства случаев) и, например, «перелинковать» несколько своих частных нод на случай недоступности всех публичных — как было описано выше, достаточно связности с любой одной нодой в сети для выхода в сеть второй ноды.
Вопрос о том, публиковать ли данные о своей ноде в списке публичных нод остается на ваше личное усмотрение — с технической точки зрения частная нода ни чем не отличается от публичной.
Протокол Tox позволяет не только писать ботов, обмениваться сообщениями, файлами или совершать аудио-видео вызовы, но и использовать его в качестве базового протокола для других сетевых задач.
Так, например, проекты tuntox и toxvpn используют протокол Tox для организации P2P соединения хостов за NAT пополняя список Full-Mesh VPN решений.
Существуют экспериментальные проекты toxmail для организации почтового сообщения, toxscreen для получения доступа к удаленному рабочему столу, toxshare для файлообмена между доверенным кругом лиц.
Для экспериментов по написанию новых приложений вы можете использовать обертки (поддерживаемые и не очень) для других языков и платформ: python, rust, go, node.js — практически неограниченный простор для новых идей и проектов.