Оригинал
1 Введение
Данный документ представляет собой текстовую спецификацию протокола Tox и всех вспомогательных модулей, необходимых для его реализации. Цель этого документа - дать достаточно инструкций, чтобы обеспечить полную и правильную реализацию протокола.
Все типы данных определены до их первого использования, и дано их двоичное представление в протоколе. Представления протокола являются нормативными и должны быть реализованы именно так, как указано. Для некоторых типов предлагаются человекочитаемые представления. Реализация может выбрать отсутствие такого представления или другое представление. Реализация может выбрать любое представление в памяти для указанных типов, если они могут быть закодированы в указанное представление протокола и декодированы из него.
Двоичные форматы указываются в таблицах с длиной, типом и содержимым . Если применимо, используются конкретные типы перечисления, поэтому в некоторых случаях типы могут быть необъяснимыми. Длина может быть либо фиксированным числом в байтах (например, 32), либо числом в битах (например, 7 бит), либо выбранной длины (например, 4 | 16), либо открытым диапазоном (например, [0, 100]). Открытые диапазоны обозначаются [n,], что означает минимальную длину n без указанной максимальной длины.
1.1 Цели и модель угроз
(TODO: это просто место для вставки; необходимо дать более техническое описание)
Этот раздел должен дать представление о целях и нецелях Tox, чтобы читатель:
1.1.1 Что делает Tox
1.1.1.1 Аутентификация
Адрес пользователя является его открытым ключом, таким образом, "добавить друга" фактически означает проверить ключ. Недостатком является то, что после компрометации (раскрытия секретного ключа) вам придется генерировать новую личность.
1.1.1.2 Сквозное шифрование
Все сообщения шифруются ключами, полученными с помощью DH, поэтому ключи известны только отправителю и получателю и никогда не передаются по сети.
1.1.1.2.1 Полная прямая секретность
Достигается использованием эфемерных ключей (TODO: как они используются в текущем протоколе? решена ли проблема?).
1.1.1.3 Надежность
Токс должен быть полностью распределенной сетью, которая не зависит от какой-либо одной точки отказа. Это достигается за счет использования DHT и прямых (P2P) соединений между пирами в сети. Точка входа по-прежнему необходима. Процесс подключения к сети называется bootstrapping. Bootstrapping может и должен осуществляться с использованием нескольких точек входа.
Tox должен работать под (почти) любым видом NAT и брандмауэра; это достигается за счет использования пробивания дыр, UPnP и TCP-реле.
Устойчивость к основным DoS и другим атакам.
1.1.1.4 Zero-conf (простота в использовании)
Конечный пользователь должен иметь возможность использовать мессенджер, который просто работает. Безопасность, которую слишком сложно использовать, бесполезна.
1.1.2 Чего нет в Tox
1.1.2.1 Анонимность
Если не используется режим TCP, участники общаются друг с другом напрямую. Одной из причин этого является то, что передача видео в реальном времени с помощью чего-либо, кроме прямого соединения, является довольно дорогостоящей (с точки зрения пропускной способности), а также означает задержки.
Ваш IP-адрес доступен узлам из вашего списка друзей. Они могут связать ваш ID непосредственно с вашим IP-адресом.
Для поиска друзей используются временные узлы DHT и луковые туннели, так что ваш ID не может быть связан с вашим IP только на основе общедоступных данных (TODO: например, данные, хранящиеся в DHT? что еще открыто?); Хотя противник, перехватывающий трафик в достаточно большом сегменте сети, (вероятно) способен выполнить некоторую статистическую атаку; (TODO: какую проблему это должно решить? решает ли это? поскольку мы не заботимся об анонимности, я могу думать только о предотвращении некоторых целевых атак типа "отказ в обслуживании").
В случае, когда анонимность является проблемой, Tox может быть проксирован через HTTP или SOCKS5 прокси. Для анонимности необходимо использовать режим только TCP.
1.2 Целые числа
Протокол использует четыре ограниченных беззнаковых целочисленных типа. Ограниченные означают, что у них есть верхняя граница, за которой инкремент не определен. Целочисленные типы поддерживают модульную арифметику, поэтому переполнение обращается в ноль. Беззнаковые типы означают, что их нижняя граница равна 0. Знаковые целочисленные типы не используются. Двоичное кодирование всех целочисленных типов представляет собой последовательность байтов фиксированной ширины с кодировкой целого числа в Big Endian, если не указано иное.
1.3 Строки
Строка - это структура данных, используемая для человекочитаемого текста. Строки представляют собой последовательности глифов. Глиф состоит из одной точки кода Юникода не нулевой ширины и нуля или более точек кода Юникода нулевой ширины. Человекочитаемое представление строки начинается и заканчивается кавычками (") и содержит все человекочитаемые глифы полностью. Управляющие символы представлены изоморфно человекочитаемому представлению. То есть каждый управляющий символ имеет ровно одно человекочитаемое представление, и существует отображение от человекочитаемого представления к управляющему символу. Поэтому использование управляющих символов Юникода (U+240x) не допускается без дополнительного маркера.
2 Crypto
Модуль Crypto содержит все функции и типы данных, связанные с криптографией. Сюда входят генерация случайных чисел, шифрование и дешифрование, генерация ключей, операции с одноразовыми номерами и генерация случайных одноразовыхномеров.
2.1 Ключ
Крипточисло - это большое целое число фиксированного размера без знака (положительное). Его двоичное кодирование - это целое число с большим конечным числом в точном соответствии с размером закодированного байта. Человекочитаемое кодирование - это число base-16, закодированное как String. Реализация NaCl libsodium предоставляет функции sodium_bin2hex и sodium_hex2bin для помощи в реализации человекочитаемой кодировки. Кодирование в памяти этих крипточисел в NaCl уже удовлетворяет двоичной кодировке, поэтому для приложений, непосредственно использующих эти API, двоичное кодирование и декодирование является идентификационной функцией.
Tox использует четыре вида крипточисел:
2.1.1 Пара ключей
Пара ключей - это пара секретного и открытого ключей. Новая пара ключей генерируется с помощью функции crypto_box_keypair криптобиблиотеки NaCl. Два отдельных вызова функции генерации пары ключей должны возвращать разные пары ключей. Подробности см. в документации NaCl.
Открытый ключ может быть вычислен из секретного ключа с помощью функции NaCl crypto_scalarmult_base, которая вычисляет скалярное произведение элемента стандартной группы и секретного ключа. Подробности см. в документации NaCl.
2.1.2 Комбинированный ключ
Комбинированный ключ вычисляется из секретного и открытого ключей с помощью функции NaCl crypto_box_beforenm. Если даны две пары ключей KP1 (SK1, PK1) и KP2 (SK2, PK2), то комбинированный ключ, вычисленный из (SK1, PK2), равен ключу, вычисленному из (SK2, PK1). Это позволяет осуществлять симметричное шифрование, поскольку одноранговые сети могут получить один и тот же общий ключ из своего секретного ключа и открытого ключа однорангового сети.
В протоколе Tox пакеты шифруются с помощью открытого ключа получателя и секретного ключа отправителя. Получатель расшифровывает пакеты, используя секретный ключ получателя и открытый ключ отправителя.
Тот факт, что для шифрования и расшифровки пакетов с обеих сторон используется один и тот же ключ, означает, что отправляемые пакеты могут быть повторно переданы обратно отправителю, если этому ничто не препятствует.
Генерация общего ключа является наиболее ресурсоемкой частью шифрования/дешифрования, что означает, что использование ресурсов может быть значительно сокращено путем сохранения общих ключей и их повторного использования в дальнейшем, насколько это возможно.
2.1.3 Nonce
Случайный nonce генерируется с помощью криптографически безопасного генератора случайных чисел из библиотеки NaCl randombytes.
Значение nonce увеличивается путем интерпретации его как числа Big Endian и добавления 1. Если nonce имеет максимальное значение, то значение после инкремента равно 0.
В большинстве частей протокола используются случайные несы. Это предотвращает ассоциацию новых несов с предыдущими. Если бы множество различных пакетов можно было связать вместе из-за способа генерации несов, это могло бы привести, например, к связыванию пакетов DHT и onion announce вместе. Это внесло бы изъян в систему, поскольку недруги могли бы связать вместе ключи DHT и долгосрочные ключи некоторых людей.
2.2 Box
Протокол Tox различает два типа текста: Обычный текст и Шифрованный текст. Шифрованный текст может передаваться по ненадежным каналам передачи данных. Обычный текст может быть чувствительным или нечувствительным. Чувствительный открытый текст должен быть преобразован в шифрованный текст с помощью функции шифрования, прежде чем его можно будет передавать по ненадежным каналам передачи данных.
Функция шифрования принимает комбинированный ключ, кодовый ключ, открытый текст и возвращает зашифрованный текст. Для выполнения шифрования она использует crypto_box_afternm. Смысл предложения "шифрование с помощью секретного ключа, открытого ключа и nonce" заключается в следующем: вычислить комбинированный ключ из секретного ключа и открытого ключа, а затем использовать функцию шифрования для преобразования.
Функция расшифровки принимает комбинированный ключ, nonce и зашифрованный текст и возвращает либо открытый текст, либо ошибку. Она использует crypto_box_open_afternm из библиотеки NaCl. Поскольку шифр симметричный, функция шифрования может также выполнять дешифрование, но не будет выполнять аутентификацию сообщения, поэтому реализация должна быть осторожной, чтобы использовать правильные функции.
crypto_box использует симметричное шифрование xsalsa20 и аутентификацию poly1305.
Функции create и handle request являются функциями шифрования и расшифровки для типа пакетов DHT, используемых для отправки данных непосредственно другим узлам DHT. Честно говоря, они должны быть в модуле DHT, но, похоже, они лучше подходят здесь. TODO: Что именно представляют собой эти функции?
3 Информация об узлах
3.1 Транспортный протокол
Транспортный протокол - это протокол транспортного уровня, расположенный непосредственно под самим протоколом Tox. Tox поддерживает два транспортных протокола: UDP и TCP. Двоичное представление транспортного протокола - это один бит: 0 для UDP, 1 для TCP. Если бит закодирован как отдельное значение, он хранится в наименьшем значащем бите байта. Если за ним следуют другие данные, упакованные в биты, он занимает ровно один бит.
Человекочитаемое представление для UDP - UDP, а для TCP - TCP.
3.2 Адрес хоста
Адрес хоста - это либо IPv4, либо IPv6 адрес. Двоичное представление адреса IPv4 - это Big Endian 32-битное беззнаковое целое число (4 байта). Для адреса IPv6 это Big Endian 128-битное беззнаковое целое число (16 байт). Двоичное представление адреса хоста - это 7-битное беззнаковое целое число, определяющее семейство адресов (2 для IPv4, 10 для IPv6), за которым следует сам адрес.
Таким образом, при упаковке вместе с транспортным протоколом первый бит упакованного байта - это протокол, а следующие 7 бит - семейство адресов.
3.3 Номер порта
Номер порта - это 16-битное число. Его двоичное представление - большое конечное 16-битное беззнаковое целое число (2 байта).
Формат упакованных узлов - это способ хранения информации об узлах в небольшом, но удобном для разбора формате. Чтобы сохранить более одного узла, просто добавьте еще один узел к предыдущему: [packed node 1][packed node 2][...].
В формате упакованного узла первый байт (старший бит протокола, младшие 7 бит семейства адресов) называется IP-типом. Следующая таблица носит информативный характер и может быть использована для упрощения реализации.
Число 130 используется для IPv4 TCP реле, а 138 используется для обозначения IPv6 TCP реле.
Причина этих чисел в том, что в Linux номера для IPv4 и IPv6 (определяемые AF_INET и AF_INET6) равны 2 и 10. Номера TCP - это просто номера UDP + 128.
4 Пакет протокола
Пакет протокола - это элемент протокола Tox верхнего уровня. Все другие типы пакетов обернуты в пакеты протокола. Он состоит из типа пакета и полезной нагрузки. Двоичное представление типа пакета - это один байт (8 бит). Полезная нагрузка представляет собой произвольную последовательность байтов.
Эти пакеты верхнего уровня могут передаваться различными способами, наиболее распространенным из которых является передача по сети с помощью UDP или TCP. Сам протокол не предписывает транспортных методов, и реализация может свободно реализовать дополнительные транспортные средства, такие как WebRTC, IRC или pipes.
В остальной части документа различные виды протокольных пакетов указываются с указанием типа пакета и полезной нагрузки. Вид пакета не повторяется в описании полезной нагрузки (TODO: на самом деле в основном повторяется, но позже не будет повторяться).
Внутри полезной нагрузки протокольных пакетов другие типы пакетов могут указывать дополнительные виды пакетов. Например, внутри пакета Crypto Data (0x1b) модуль Messenger определяет свои протоколы для обмена сообщениями, передачи файлов и т.д. Протокольные пакеты верхнего уровня сами по себе не шифруются, хотя их полезная нагрузка может быть зашифрована.
4.1 Виды пакетов
Ниже приведен исчерпывающий список названий видов пакетов верхнего уровня и их количество. Их полезная нагрузка указана в специальных разделах. Каждый раздел называется по имени вида пакета, который он описывает, после чего в круглых скобках указывается значение байта, например, Ping Request (0x00).
5 DHT
DHT - это самоорганизующийся комплекс всех узлов сети Tox. Узел в сети Tox также называется "узел Tox". Когда мы говорим о " peers", мы имеем в виду любой узел, который не является локальным узлом (субъектом). Этот модуль заботится о поиске IP и порта узлов и установлении маршрута к ним напрямую через UDP, используя при необходимости пробивку дыр. DHT работает только на UDP и поэтому используется только в том случае, если UDP работает.
Каждый узел в Tox DHT имеет эфемерную пару ключей, называемую парой ключей DHT, состоящую из секретного ключа DHT и открытого ключа DHT. Открытый ключ DHT служит в качестве адреса узла. Пара ключей DHT обновляется каждый раз при закрытии или перезапуске экземпляра tox. Реализация может обновлять ключ чаще, но это приведет к отключению всех пиров.
Открытый ключ DHT друга можно найти с помощью модуля onion. Когда открытый ключ DHT друга известен, DHT используется для его поиска и прямого соединения с ним по UDP.
5.1 Дистанции (Distance)
The Distance - это целое положительное число. Его человекочитаемое представление - это число base-16. Distance (тип) - это упорядоченный моноид с ассоциативным бинарным оператором + и элементом тождества 0.
DHT использует метрику для определения расстояния между двумя узлами. Тип Distance является со-доменом этой метрики. В настоящее время метрика, используемая Tox DHT, представляет собой XOR открытых ключей узлов: distance(x, y) = x XOR y. Для этих вычислений открытые ключи интерпретируются как большие целые числа (см. Крипто-числа).
Когда мы говорим о "близком узле", мы имеем в виду, что его расстояние до рассматриваемого узла мало по сравнению с расстоянием до других узлов.
Реализация не обязана предоставлять тип Distance, поэтому он не имеет определенного двоичного представления. Например, вместо того, чтобы вычислять Distance и сравнивать его с другим Distance, реализация может выбрать реализацию Distance в виде пары открытых ключей и определить упорядочение по Distance без вычисления полного интегрального значения. Это работает, потому что как только решение об упорядочении может быть принято в самых значимых битах, последующие биты не будут влиять на это решение.
XOR является корректной метрикой, т.е. удовлетворяет необходимым условиям:
Неотрицательность distance(x, y) >= 0: Поскольку открытые ключи являются крипточислами, которые по определению положительны, их XOR обязательно положителен.
Идентичность неотрицательных чисел distance(x, y) == 0 iff x == y: XOR двух целых чисел равен нулю, если они равны.
Симметрия distance(x, y) == distance(y, x): XOR - симметричная операция.
Субаддитивность distance(x, z) <= distance(x, y) + distance(y, z): следует из ассоциативности, так как x XOR z = x XOR (y XOR y) XOR z = distance(x, y) XOR distance(y, z), что не больше distance(x, y) + distance(y, z).
Кроме того, XOR обладает другими полезными свойствами:
Однонаправленность: для ключа x и расстояния d существует один и только один ключ y такой, что distance(x, y) = d.
Из этого следует, что повторные поиски, скорее всего, будут проходить по одному и тому же пути, поэтому кэширование имеет смысл.
Источник: maymounkov-kademlia
Пример: Даны три узла с ключами 2, 5 и 6:
2 XOR 5 = 7
2 XOR 6 = 4
5 XOR 2 = 7
5 XOR 6 = 3
6 XOR 2 = 4
6 XOR 5 = 3
Ближайшим узлом от 2 и 5 является 6. Ближайшим узлом от 6 является 5 с удалением 3. Этот пример показывает, что ключ, близкий с точки зрения целочисленного сложения, не обязательно будет близким с точки зрения XOR.
5.2 K-buckets
K-buckets - это структура данных для эффективного хранения набора узлов, близких к определенному ключу, называемому базовым ключом. Базовый ключ является постоянным на протяжении всего времени существования экземпляра k-buckets.
k-buckets - это карта из небольших целых чисел 0 <= n < 256 в набор до k инфо узлов. Набор называется bucket. k называется размером bucket. По умолчанию размер bucket равен 8.
Указанное выше число n - это индекс bucket. Это положительное целое число с диапазоном [0, 255], т.е. диапазон 8-битного беззнакового целого числа.
Элемент bucket - это элемент представляет собой упорядоченное множество, и записи сортируются по расстоянию от базового ключа. Таким образом, первый (наименьший) элемент набора является самым близким к базовому ключу в этом наборе, последний (наибольший) элемент - самый удаленный.
5.2.1 Bucket Index
Индекс bucket можно вычислить с помощью следующей функции: bucketIndex(baseKey, nodeKey) = 255 - log_2(distance(baseKey, nodeKey)). Эта функция не определена, когда baseKey == nodeKey, что означает, что k-bucket никогда не будет содержать Node Info о базовом узле.
Таким образом, каждый k-bucket содержит только Node Infos, для ключей которых выполняется следующее: если узел с ключом nodeKey находится в k-bucket с индексом n, то bucketIndex(baseKey, nodeKey) == n. Таким образом, n'th k-bucket состоит из узлов, для которых расстояние до базового узла лежит в диапазоне [2^n, 2^(n+1) - 1].
Индекс bucket можно эффективно вычислить, определив первый бит, в котором отличаются два ключа, начиная со старшего бита. Так, если локальный ключ DHT начинается, например, с 0x80, а ключ узла с bucket начинается с 0x40, то индекс bucket для этого узла равен 0. Если второй бит отличается, то индекс bucket равен 1. Если ключи почти одинаковы и отличается только последний бит, то индекс bucket равен 255.
5.2.2 Обновление k-buckets
TODO: это отличается от политики kademlia по наименьшему времени выселения; почему было выбрано существующее решение, как оно влияет на безопасность, производительность и устойчивость к отравлениям? В оригинальной статье утверждается, что предпочтение старых живых узлов приводит к лучшей стойкости и устойчивости к базовым DDoS-атакам;
Любая операция обновления или поиска на экземпляре k-buckets, в которой участвует один узел, требует, чтобы мы сначала вычислили индекс bucket для этого узла. Обновление с участием Node Info с nodeKey == baseKey не имеет никакого эффекта. Если обновление приводит к появлению пустого элемента, этот элемент удаляется из карты. Блок заполнен, если в нем содержится максимальное количество записей, заданное размером блока.
Узел является жизнеспособным для записи, если bucket не заполнен или открытый ключ узла имеет меньшее расстояние от базового ключа, чем текущая запись с наибольшим расстоянием. Если узел жизнеспособен, а bucket заполнено, запись с наибольшим расстоянием от базового ключа удаляется, чтобы размер bucket не превышал максимальный настроенный размер bucket
Добавление узла, ключ которого уже существует, приводит к обновлению информации об узле в bucket. Удаление узла, для которого не существует информации об узле в k-bucket не имеет никакого эффекта. Таким образом, удаление узла дважды разрешено и имеет тот же эффект, что и однократное удаление.
5.3 Состояние узла DHT
Каждый узел DHT содержит пару ключей, называемую парой ключей DHT.
Узел DHT также хранит набор Node Infos узлов, которые близки к его собственному открытому ключу DHT. Для этого используется структура данных k-buckets, а в качестве базового ключа используется локальный открытый ключ DHT.
5.4 Самоорганизация
Самоорганизация в DHT происходит путем подключения каждого пира DHT к произвольному числу пиров, ближайших к его собственному открытому ключу DHT, и некоторых, находящихся дальше.
Если каждый пир в сети знает пиров с открытым ключом DHT, ближайшим к его открытому ключу DHT, то для поиска конкретного пира с открытым ключом X пиру достаточно рекурсивно запросить пиров в DHT об известных пирах, имеющих открытые ключи DHT, ближайшие к X. В конечном итоге пир найдет пиров в DHT, которые ближе всего к этому пиру, и, если этот пир находится в сети, он найдет их.
5.5 Пакет DHT
Пакет DHT содержит открытый ключ DHT отправителя, шифрующий код Nonce и зашифрованную полезную нагрузку. Полезная нагрузка шифруется с помощью секретного ключа DHT отправителя, открытого ключа DHT получателя и nonce, который отправляется вместе с пакетом. Пакеты DHT отправляются внутри пакетов протокола с изменяющимся типом пакета.
Длина зашифрованной полезной нагрузки составляет не менее 16 байт, поскольку шифрование включает MAC из 16 байт. Таким образом, 16-байтовая полезная нагрузка была бы пустым сообщением. Протокол DHT никогда не отправляет пустые сообщения, поэтому в действительности минимальный размер Ping-пакета составляет 27 байт.
5.6 Службы RPC
Служба RPC DHT состоит из пакета запроса и пакета ответа. Пакет DHT RPC содержит полезную нагрузку и идентификатор запроса. Этот идентификатор представляет собой 64-битное беззнаковое целое число, которое помогает идентифицировать ответ для данного запроса. Идентификатор запроса в ответном пакете должен быть равен идентификатору запроса в запросе, на который он отвечает.
Пакеты DHT RPC шифруются и передаются внутри пакетов DHT.
Минимальный размер полезной нагрузки равен 0, но в действительности наименьший разумный размер полезной нагрузки равен 1. Поскольку в обоих направлениях связи используется один и тот же симметричный ключ, зашифрованный Запрос будет действительным зашифрованным Ответом, если они содержат один и тот же открытый текст.
Части протокола, использующие пакеты RPC, должны позаботиться о том, чтобы полезная нагрузка запроса не была действительной полезной нагрузкой ответа. Например, пакеты Ping несут булевый флаг, который указывает, соответствует ли полезная нагрузка Запросу или Ответу.
Идентификатор запроса обеспечивает некоторую устойчивость к атакам повторного воспроизведения. Если бы не было идентификатора запроса, злоумышленнику было бы легко воспроизвести старые ответы и таким образом предоставить узлам устаревшую информацию. Точное значение Request ID будет указано позже в разделе DHT.
5.6.1 Служба Ping
Служба Ping используется для периодической проверки того, жив ли еще другой узел.
Полезная нагрузка Ping-пакета состоит только из булева значения, говорящего о том, является ли это запросом или ответом.
Однобайтовое булево значение внутри зашифрованной полезной нагрузки добавляется для того, чтобы предотвратить создание пирами действительного Ping-ответа из Ping-запроса без расшифровки пакета и шифрования нового. Поскольку используется симметричное шифрование, зашифрованный Ping Response будет по байтам равен Ping Request без байта отличия.
5.6.1.1 Запрос Ping (0x00)
Запрос Ping - это пакет Ping с флагом ответа, установленным на False. Когда запрос Ping Request получен и успешно расшифрован, создается пакет Ping Response и отправляется обратно запрашивающему.
5.6.1.2 Ping Response (0x01)
Ping Response - это Ping-пакет с флагом ответа, установленным на True.
5.6.2 Служба узлов
Служба Nodes Service используется для запроса у другого узла DHT до 4 известных им узлов, которые находятся ближе всего к запрашиваемому узлу.
RPC-служба DHT Nodes использует формат упакованных узлов.
5.6.2.1 Запрос узлов (0x02)
Открытый ключ DHT, отправленный в запросе, ищет отправитель.
Узел IPv4 имеет размер 39 байт, узел IPv6 - 51 байт, поэтому максимальный размер составляет 51 * 4 = 204 байта.
Ответы узлов должны содержать 4 ближайших узла, которые отправитель ответа имеет в своем списке известных узлов.
5.7 Принципы работы DHT
Только протокол UDP (IP Type 2 и 10) используется в модуле DHT при отправке узлов с упакованным форматом узлов. Это связано с тем, что протокол TCP используется для отправки ретрансляционной информации TCP, а DHT - только UDP.
Это сделано для увеличения скорости поиска пиров. Toxcore также хранит 8 узлов (они должны быть такими же или меньше, чем узлы, которые Toxcore хранит для каждого индекса в своем списке close list, чтобы убедиться, что все найденные ближайшие пиры будут знать искомый узел), ближайших к каждому из открытых ключей в своем списке друзей DHT (или списке открытых ключей DHT, которые он активно пытается найти и подключиться к ним). Toxcore пингует каждый узел в списках каждые 60 секунд, чтобы проверить, жив ли он. Он не хранит себя ни в одном из списков и не посылает никаких запросов самому себе. Узлы могут находиться более чем в одном списке, например, если открытый ключ DHT пира очень близок к открытому ключу DHT друга, которого ищут. Он также посылает запросы get node на случайный узел (случайность делает его непредсказуемым, предсказуемость или знание того, какой узел будет пинговаться следующим, может облегчить некоторые атаки, нарушающие работу сети, поскольку добавляет возможный вектор атаки) в каждом из этих списков узлов каждые 20 секунд, при этом публичным ключом поиска является его публичный ключ для ближайшего узла, а публичными ключами поиска - те, которые находятся в списке друзей DHT. Узлы удаляются после 122 секунд отсутствия ответа. Узлы добавляются в списки после получения от них правильного ответа ping или пакета send node. Если узел уже присутствует в списке, он обновляется, если изменился его IP-адрес. Узел может быть добавлен в список, только если список не полон или если открытый ключ DHT узла ближе, чем открытый ключ DHT хотя бы одного из узлов в списке к открытому ключу, поиск которого ведется в этом списке. Когда узел добавляется в полный список, он заменяет самый дальний узел.
Если увеличить число узлов до 32, это увеличит количество пакетов, необходимых для проверки того, жив ли каждый из них, что увеличит использование полосы пропускания, но повысит надежность. Если бы количество узлов было уменьшено, надежность снизилась бы вместе с использованием полосы пропускания. Причина такой зависимости между надежностью и количеством узлов заключается в том, что если мы предположим, что не все узлы имеют открытые UDP порты или находятся за NAT, это означает, что каждый из этих узлов должен иметь возможность хранить определенное количество узлов за NAT, чтобы другие могли найти эти узлы за NAT. Например, если 7/8 узлов находятся за NAT, использование 8 узлов будет недостаточно, потому что вероятность того, что некоторые из этих узлов невозможно будет найти в сети, будет слишком высока. Если бы таймауты пингов и задержки между пингами были выше, это уменьшило бы использование полосы пропускания, но увеличило бы количество отключенных узлов, которые все еще хранятся в списках. Уменьшение этих задержек привело бы к обратному результату.
Если увеличить число узлов ближайших к каждому открытому ключу от 8, до 16, это увеличит использование полосы пропускания, может повысить эффективность атак на симметричных NAT и надежность. Уменьшение этого числа будет иметь противоположный эффект.
При получении пакета send node, toxcore проверяет, может ли каждый из полученных узлов быть добавлен в любой из списков. Если узел может быть добавлен, toxcore посылает ему ping-пакет, если не может - игнорирует. При получении пакета get node, toxcore найдет 4 узла в своих списках узлов, наиболее близких к открытому ключу в пакете, и отправит их в ответе send node. Таймауты и количество узлов в списках для toxcore были выбраны исключительно по ощущениям и, вероятно, не являются лучшими значениями. Это также относится к поведению, которое является простым и должно быть улучшено, чтобы сделать сеть более устойчивой к атакам sybil.
5.8 Пакеты запроса DHT
Пакеты запросов DHT - это пакеты, которые могут быть отправлены через один узел DHT другому узлу, который он знает. Они используются для отправки зашифрованных данных друзьям, с которыми мы не обязательно связаны напрямую в DHT.
Узел DHT, получивший пакет запроса DHT, проверяет, является ли открытый ключ получателя его открытым ключом DHT, и если да, то расшифровывает и обрабатывает пакет. Если нет, они проверят, знают ли они этот открытый ключ DHT (если он есть в их списке близких узлов). Если нет, они отбрасывают пакет. Если да, то они повторно отправят точно такой же пакет этому узлу DHT.
Зашифрованное сообщение шифруется с помощью открытого ключа DHT получателя, закрытого ключа DHT отправителя и nonce (случайно сгенерированные 24 байта).
Пакеты запросов DHT используются для пакетов DHTPK (см. лук) и пакетов NAT ping.
5.8.1 Пакеты NAT ping
Инкапсулируются в пакет запроса DHT.
Пакеты NAT ping используются для проверки, находится ли друг, с которым мы не связаны напрямую, в сети и готов ли он
5.8.1.1 Запрос NAT ping
Полученный NAT ping запрос необходимо проверить, не от друга ли он.
Проверка осуществляется путем сравнения открытого ключа из пакета запроса DHT, в который был инкапсулирован данный NAT ping запрос, со списком открытых ключей собственных друзей.
Если NAT ping запрос...
... от своего друга, ответ NAT ping с тем же случайным числом, что и запрос, должен быть послан обратно через узлы, которые знают друга, пославшего запрос.
Если нет узлов, которые знают друга, пакет будет отброшен.
... не от собственного друга, пакет будет сброшен.
5.8.1.2 Ответ NAT ping
5.9 Сквозное сеодинение
Механизм для определения необходимости запуска сквозного сеодинения:
Запрос от 8 ближайших к собственному узлу пиров IP:порт друга.
По крайней мере 4+ узлов возвращают IP:порт друга.
Отправьте запрос DHT ping на каждый из этих IP:портов.
Если ответ не получен, начните Сквозное сеодинение
Числа 8 и 4 используются в toxcore и были выбраны только на основе "ощущения" и могут быть не оптимальными.
Перед началом сквозное сеодинение другу посылается пакет NAT ping через пиров, которые утверждают, что знают друга. Если получен ответ NAT ping с тем же случайным числом, то сквозное сеодинение должно быть начато.
Получение ответа NAT ping означает, что друг находится в сети и активно ищет нас, поскольку это единственный способ узнать узлы, которые знают нас. сквозное сеодинение будет работать только в том случае, если друг активно пытается подключиться к нам.
Запросы NAT ping отправляются каждые 3 секунды в toxcore. Если в течение 6 секунд не получен ответ, сквозное сеодинение прекращается.
Отправка запросов NAT ping через более длительные интервалы может увеличить вероятность того, что другой узел выйдет из сети и пакеты ping, посылаемые в процессе создания сквозного сеодинения будут отправлены "мертвому" аналогу, но потенциально это может снизить использование полосы пропускания. Уменьшение интервалов будет иметь противоположный эффект.
Для "сквозное сеодинения" мы предполагаем, что люди, использующие Tox, используют один из трех типов NAT:
4+ близких к заданному публичному ключу пиров вернули один и тот же IP:порт
4+ близких к данному открытому ключу пиров вернули один и тот же IP, но разный порт
близкие к данному PK пиры вернули разные IP:порты
5.9.1 Cone NAT
Поведение программного обеспечения, выполняющего NAT:
Назначить один целый порт каждому UDP сокету за NAT, любой пакет с любого IP:порта, посланный на этот назначенный порт из Интернета, будет перенаправлен на сокет за ним.
Сквозное соединение с помощью обычных cone NAT достигается просто за счет способа функционирования DHT, т.е. мы посылаем на IP:порт друга DHT запрос ping, и получаем ответ DHT ping.
5.9.2 Restricted Cone NAT
Поведение программного обеспечения, выполняющего NAT:
Назначить один целый порт каждому UDP сокету за NAT. Перенаправлять пакеты только с IP, на которые сокет UDP отправил пакет.
Если 4+ узлов рядом с нами возвращают один и тот же IP:порт друга, это означает, что друг находится на Restricted Cone NAT.
Сквозное соединение можно осуществить, заставив друга отправить пакет на наш публичный IP:порт. Это означает, что сквозное соединение может быть достигнуто легко и что мы должны просто продолжать регулярно посылать пакеты DHT ping на этот IP:порт, пока не получим ответ DHT ping. Это сработает, потому что друг ищет нас в DHT. Найдя нас, друг отправит пакет на наш публичный IP:порт , тем самым установив соединение.
5.9.3 Симметричный NAT
Худший вариант.
Поведение программного обеспечения, выполняющего NAT:
Присваивать новый порт для каждого IP:порта, на который отправляется пакет. Рассматривать каждый новый пир, на который отправляется UDP-пакет, как соединение. Пересылать в сокет пакеты только с назначенного IP:порта этого соединения.
В случае плохой реализации, crash & burn, заставляя пользователей плакать.
Близкие узлы не возвращают один и тот же порт для друга - это означает, что друг находится на симметричном NAT.
Некоторые симметричные NAT открывают порты последовательно, что может привести к тому, что порты, возвращаемые близкими нам узлами, будут, например, 1345, 1347, 1389, 1395.
Сквеозное соединение с помощью симметричных NAT основано на угадывании того, какие порты с большей вероятностью будут использоваться другом, когда он пытается отправить нам ping-запрос. Запросы DHT ping посылаются на эти порты до тех пор, пока не будет получен ответ DHT ping.
Toxcore пробует все порты, рядом с каждым возвращенным портом (например, для 4 портов, перечисленных ранее, он будет пробовать: 1345, 1347, 1389, 1395, 1346, 1348, 1390, 1396, 1344, 1346...), постепенно пробуя порты все дальше от тех, о работе которых сообщили близкие узлы.
Пробуйте до 48 портов каждые 3 секунды, пока соединение не будет установлено.
После 5 попыток удвойте (?)диапазон(?) и начните пробовать порты от 1024, 48 одновременно, включая ранее угаданные порты.
Это, похоже, исправляет ситуацию для некоторых симметричных NAT, скорее всего, потому что многие из них перезапускают счетчик на 1024. - irungentoo
Увеличение количества портов, проверяемых в секунду, ускорит процесс скврзного соединения, но также может привести к DoS NAT из-за большого количества пакетов, отправляемых на разные IP за короткий промежуток времени. Уменьшение количества портов замедляет процесс сквозного соединения
Хотя это работает, но метод может быть улучшен.
5.9.4 Разные IP:порты
Может возникнуть, когда пиры возвращают разные IP и порты.
Есть 2 случая, когда это может произойти:
друг находится за очень строгим NAT, который не может быть пробит.
друг недавно подключился к другому интернет соединению и некоторые пиры все еще имеют устаревшую информацию.
Ничего нельзя сделать, если NAT очень строгий, поэтому рекомендуется использовать наиболее распространенный IP, возвращаемый пирами, и игнорировать другие IP:порты.
5.10 Информация о Bootstrap узлах DHT (0xf0)
Узлы Bootstrap - это обычные узлы Tox со стабильным открытым ключом DHT. Это означает, что открытый ключ DHT не меняется при перезагрузках. Узлы бутстрапа DHT имеют один дополнительный тип запроса: Bootstrap Info. Запрос представляет собой пакет длиной 78 байт, где первый байт - 0xf0. Остальные байты игнорируются.
Формат ответа следующий:
6 Обнаружение локальной сети
Обнаружение локальной сети - это способ обнаружения пиров Tox, находящихся в локальной сети. Если два друга Tox находятся в локальной сети, то наиболее эффективным способом их совместного общения будет использование локальной сети. Если клиент Tox открыт в локальной сети, в которой существует другой клиент Tox, то хорошим поведением будет загрузиться в сеть, используя клиент Tox в локальной сети. Именно этого и добивается обнаружение локальной сети.
Обнаружение локальной сети происходит путем отправки UDP пакета через UDP сокет toxcore на широковещательный адрес интерфейса в IPv4, глобальный широковещательный адрес (255.255.255.255) и многоадресный адрес в IPv6 (FF02::1) на UDP порт Tox по умолчанию (33445).
Пакет LAN Discovery:
Пакеты LAN Discovery содержат открытый ключ DHT отправителя. При получении пакета LAN Discovery отправителю пакета будет отправлен пакет DHT get nodes. Это означает, что экземпляр DHT будет загружать себя к каждому пиру, от которого он получает один из этих пакетов. Благодаря этому механизму клиенты Tox будут автоматически загружаться от других клиентов Tox, работающих в локальной сети.
Когда этот механизм включен, toxcore отправляет эти пакеты каждые 10 секунд, чтобы снизить задержки. Пакеты можно посылать и каждые 60 секунд, но это сделает поиск пиров по сети в 6 раз медленнее.
Поиск по локальной сети позволяет двум друзьям в локальной сети найти друг друга, так как DHT отдает приоритет адресам локальной сети над адресами вне локальной сети для DHT пиров. Успешная отправка запроса get node request/bootstrapping от пира должна также добавить его в список пиров DHT, если мы его ищем. Пир не должен быть немедленно добавлен, если пакет LAN discovery с открытым ключом DHT, который мы пингуем, должен быть послан, и должен быть получен правильный ответ, прежде чем мы сможем сказать, что этот пир найден.
Обнаружение локальной сети - это то, как Tox обрабатывает и заставляет все хорошо работать в локальной сети.
1 Введение
Данный документ представляет собой текстовую спецификацию протокола Tox и всех вспомогательных модулей, необходимых для его реализации. Цель этого документа - дать достаточно инструкций, чтобы обеспечить полную и правильную реализацию протокола.
Все типы данных определены до их первого использования, и дано их двоичное представление в протоколе. Представления протокола являются нормативными и должны быть реализованы именно так, как указано. Для некоторых типов предлагаются человекочитаемые представления. Реализация может выбрать отсутствие такого представления или другое представление. Реализация может выбрать любое представление в памяти для указанных типов, если они могут быть закодированы в указанное представление протокола и декодированы из него.
Двоичные форматы указываются в таблицах с длиной, типом и содержимым . Если применимо, используются конкретные типы перечисления, поэтому в некоторых случаях типы могут быть необъяснимыми. Длина может быть либо фиксированным числом в байтах (например, 32), либо числом в битах (например, 7 бит), либо выбранной длины (например, 4 | 16), либо открытым диапазоном (например, [0, 100]). Открытые диапазоны обозначаются [n,], что означает минимальную длину n без указанной максимальной длины.
1.1 Цели и модель угроз
(TODO: это просто место для вставки; необходимо дать более техническое описание)
Этот раздел должен дать представление о целях и нецелях Tox, чтобы читатель:
- понимал, какие проблемы Tox намерен решить
- мог проверить, решены ли они данной спецификацией
- мог принимать лучшие компромиссы и решения в своей собственной реализации протокола.
1.1.1 Что делает Tox
1.1.1.1 Аутентификация
Адрес пользователя является его открытым ключом, таким образом, "добавить друга" фактически означает проверить ключ. Недостатком является то, что после компрометации (раскрытия секретного ключа) вам придется генерировать новую личность.
1.1.1.2 Сквозное шифрование
Все сообщения шифруются ключами, полученными с помощью DH, поэтому ключи известны только отправителю и получателю и никогда не передаются по сети.
1.1.1.2.1 Полная прямая секретность
Достигается использованием эфемерных ключей (TODO: как они используются в текущем протоколе? решена ли проблема?).
1.1.1.3 Надежность
Токс должен быть полностью распределенной сетью, которая не зависит от какой-либо одной точки отказа. Это достигается за счет использования DHT и прямых (P2P) соединений между пирами в сети. Точка входа по-прежнему необходима. Процесс подключения к сети называется bootstrapping. Bootstrapping может и должен осуществляться с использованием нескольких точек входа.
Tox должен работать под (почти) любым видом NAT и брандмауэра; это достигается за счет использования пробивания дыр, UPnP и TCP-реле.
Устойчивость к основным DoS и другим атакам.
1.1.1.4 Zero-conf (простота в использовании)
Конечный пользователь должен иметь возможность использовать мессенджер, который просто работает. Безопасность, которую слишком сложно использовать, бесполезна.
1.1.2 Чего нет в Tox
1.1.2.1 Анонимность
Если не используется режим TCP, участники общаются друг с другом напрямую. Одной из причин этого является то, что передача видео в реальном времени с помощью чего-либо, кроме прямого соединения, является довольно дорогостоящей (с точки зрения пропускной способности), а также означает задержки.
Ваш IP-адрес доступен узлам из вашего списка друзей. Они могут связать ваш ID непосредственно с вашим IP-адресом.
Для поиска друзей используются временные узлы DHT и луковые туннели, так что ваш ID не может быть связан с вашим IP только на основе общедоступных данных (TODO: например, данные, хранящиеся в DHT? что еще открыто?); Хотя противник, перехватывающий трафик в достаточно большом сегменте сети, (вероятно) способен выполнить некоторую статистическую атаку; (TODO: какую проблему это должно решить? решает ли это? поскольку мы не заботимся об анонимности, я могу думать только о предотвращении некоторых целевых атак типа "отказ в обслуживании").
В случае, когда анонимность является проблемой, Tox может быть проксирован через HTTP или SOCKS5 прокси. Для анонимности необходимо использовать режим только TCP.
1.2 Целые числа
Протокол использует четыре ограниченных беззнаковых целочисленных типа. Ограниченные означают, что у них есть верхняя граница, за которой инкремент не определен. Целочисленные типы поддерживают модульную арифметику, поэтому переполнение обращается в ноль. Беззнаковые типы означают, что их нижняя граница равна 0. Знаковые целочисленные типы не используются. Двоичное кодирование всех целочисленных типов представляет собой последовательность байтов фиксированной ширины с кодировкой целого числа в Big Endian, если не указано иное.
| Type name | C type | Rust type | Length | Upper bound |
|---|---|---|---|---|
| Word8 | uint8_t | u8 | 1 | 255 (0xff) |
| Word16 | uint16_t | u16 | 2 | 65535 (0xffff) |
| Word32 | uint32_t | u32 | 4 | 4294967295 (0xffffffff) |
| Word64 | uint64_t | u64 | 8 | 18446744073709551615 (0xffffffffffffffff) |
1.3 Строки
Строка - это структура данных, используемая для человекочитаемого текста. Строки представляют собой последовательности глифов. Глиф состоит из одной точки кода Юникода не нулевой ширины и нуля или более точек кода Юникода нулевой ширины. Человекочитаемое представление строки начинается и заканчивается кавычками (") и содержит все человекочитаемые глифы полностью. Управляющие символы представлены изоморфно человекочитаемому представлению. То есть каждый управляющий символ имеет ровно одно человекочитаемое представление, и существует отображение от человекочитаемого представления к управляющему символу. Поэтому использование управляющих символов Юникода (U+240x) не допускается без дополнительного маркера.
2 Crypto
Модуль Crypto содержит все функции и типы данных, связанные с криптографией. Сюда входят генерация случайных чисел, шифрование и дешифрование, генерация ключей, операции с одноразовыми номерами и генерация случайных одноразовыхномеров.
2.1 Ключ
Крипточисло - это большое целое число фиксированного размера без знака (положительное). Его двоичное кодирование - это целое число с большим конечным числом в точном соответствии с размером закодированного байта. Человекочитаемое кодирование - это число base-16, закодированное как String. Реализация NaCl libsodium предоставляет функции sodium_bin2hex и sodium_hex2bin для помощи в реализации человекочитаемой кодировки. Кодирование в памяти этих крипточисел в NaCl уже удовлетворяет двоичной кодировке, поэтому для приложений, непосредственно использующих эти API, двоичное кодирование и декодирование является идентификационной функцией.
Tox использует четыре вида крипточисел:
| Type | Bits | Encoded byte size |
|---|---|---|
| Public Key | 256 | 32 |
| Secret Key | 256 | 32 |
| Combined Key | 256 | 32 |
| Nonce | 192 | 24 |
2.1.1 Пара ключей
Пара ключей - это пара секретного и открытого ключей. Новая пара ключей генерируется с помощью функции crypto_box_keypair криптобиблиотеки NaCl. Два отдельных вызова функции генерации пары ключей должны возвращать разные пары ключей. Подробности см. в документации NaCl.
Открытый ключ может быть вычислен из секретного ключа с помощью функции NaCl crypto_scalarmult_base, которая вычисляет скалярное произведение элемента стандартной группы и секретного ключа. Подробности см. в документации NaCl.
2.1.2 Комбинированный ключ
Комбинированный ключ вычисляется из секретного и открытого ключей с помощью функции NaCl crypto_box_beforenm. Если даны две пары ключей KP1 (SK1, PK1) и KP2 (SK2, PK2), то комбинированный ключ, вычисленный из (SK1, PK2), равен ключу, вычисленному из (SK2, PK1). Это позволяет осуществлять симметричное шифрование, поскольку одноранговые сети могут получить один и тот же общий ключ из своего секретного ключа и открытого ключа однорангового сети.
В протоколе Tox пакеты шифруются с помощью открытого ключа получателя и секретного ключа отправителя. Получатель расшифровывает пакеты, используя секретный ключ получателя и открытый ключ отправителя.
Тот факт, что для шифрования и расшифровки пакетов с обеих сторон используется один и тот же ключ, означает, что отправляемые пакеты могут быть повторно переданы обратно отправителю, если этому ничто не препятствует.
Генерация общего ключа является наиболее ресурсоемкой частью шифрования/дешифрования, что означает, что использование ресурсов может быть значительно сокращено путем сохранения общих ключей и их повторного использования в дальнейшем, насколько это возможно.
2.1.3 Nonce
Случайный nonce генерируется с помощью криптографически безопасного генератора случайных чисел из библиотеки NaCl randombytes.
Значение nonce увеличивается путем интерпретации его как числа Big Endian и добавления 1. Если nonce имеет максимальное значение, то значение после инкремента равно 0.
В большинстве частей протокола используются случайные несы. Это предотвращает ассоциацию новых несов с предыдущими. Если бы множество различных пакетов можно было связать вместе из-за способа генерации несов, это могло бы привести, например, к связыванию пакетов DHT и onion announce вместе. Это внесло бы изъян в систему, поскольку недруги могли бы связать вместе ключи DHT и долгосрочные ключи некоторых людей.
2.2 Box
Протокол Tox различает два типа текста: Обычный текст и Шифрованный текст. Шифрованный текст может передаваться по ненадежным каналам передачи данных. Обычный текст может быть чувствительным или нечувствительным. Чувствительный открытый текст должен быть преобразован в шифрованный текст с помощью функции шифрования, прежде чем его можно будет передавать по ненадежным каналам передачи данных.
Функция шифрования принимает комбинированный ключ, кодовый ключ, открытый текст и возвращает зашифрованный текст. Для выполнения шифрования она использует crypto_box_afternm. Смысл предложения "шифрование с помощью секретного ключа, открытого ключа и nonce" заключается в следующем: вычислить комбинированный ключ из секретного ключа и открытого ключа, а затем использовать функцию шифрования для преобразования.
Функция расшифровки принимает комбинированный ключ, nonce и зашифрованный текст и возвращает либо открытый текст, либо ошибку. Она использует crypto_box_open_afternm из библиотеки NaCl. Поскольку шифр симметричный, функция шифрования может также выполнять дешифрование, но не будет выполнять аутентификацию сообщения, поэтому реализация должна быть осторожной, чтобы использовать правильные функции.
crypto_box использует симметричное шифрование xsalsa20 и аутентификацию poly1305.
Функции create и handle request являются функциями шифрования и расшифровки для типа пакетов DHT, используемых для отправки данных непосредственно другим узлам DHT. Честно говоря, они должны быть в модуле DHT, но, похоже, они лучше подходят здесь. TODO: Что именно представляют собой эти функции?
3 Информация об узлах
3.1 Транспортный протокол
Транспортный протокол - это протокол транспортного уровня, расположенный непосредственно под самим протоколом Tox. Tox поддерживает два транспортных протокола: UDP и TCP. Двоичное представление транспортного протокола - это один бит: 0 для UDP, 1 для TCP. Если бит закодирован как отдельное значение, он хранится в наименьшем значащем бите байта. Если за ним следуют другие данные, упакованные в биты, он занимает ровно один бит.
Человекочитаемое представление для UDP - UDP, а для TCP - TCP.
3.2 Адрес хоста
Адрес хоста - это либо IPv4, либо IPv6 адрес. Двоичное представление адреса IPv4 - это Big Endian 32-битное беззнаковое целое число (4 байта). Для адреса IPv6 это Big Endian 128-битное беззнаковое целое число (16 байт). Двоичное представление адреса хоста - это 7-битное беззнаковое целое число, определяющее семейство адресов (2 для IPv4, 10 для IPv6), за которым следует сам адрес.
Таким образом, при упаковке вместе с транспортным протоколом первый бит упакованного байта - это протокол, а следующие 7 бит - семейство адресов.
3.3 Номер порта
Номер порта - это 16-битное число. Его двоичное представление - большое конечное 16-битное беззнаковое целое число (2 байта).
| Length | Type | Contents |
|---|---|---|
| 1 bit | Transport Protocol | UDP = 0, TCP = 1 |
| 7 bit | Address Family | 2 = IPv4, 10 = IPv6 |
| 4 | 16 | IP address | 4 bytes for IPv4, 16 bytes for IPv6 |
| 2 | Port Number | Port number |
| 32 | Public Key | Node ID |
Формат упакованных узлов - это способ хранения информации об узлах в небольшом, но удобном для разбора формате. Чтобы сохранить более одного узла, просто добавьте еще один узел к предыдущему: [packed node 1][packed node 2][...].
В формате упакованного узла первый байт (старший бит протокола, младшие 7 бит семейства адресов) называется IP-типом. Следующая таблица носит информативный характер и может быть использована для упрощения реализации.
| IP Type | Transport Protocol | Address Family |
|---|---|---|
| 2 (0x02) | UDP | IPv4 |
| 10 (0x0a) | UDP | IPv6 |
| 130 (0x82) | TCP | IPv4 |
| 138 (0x8a) | TCP | IPv6 |
Число 130 используется для IPv4 TCP реле, а 138 используется для обозначения IPv6 TCP реле.
Причина этих чисел в том, что в Linux номера для IPv4 и IPv6 (определяемые AF_INET и AF_INET6) равны 2 и 10. Номера TCP - это просто номера UDP + 128.
4 Пакет протокола
Пакет протокола - это элемент протокола Tox верхнего уровня. Все другие типы пакетов обернуты в пакеты протокола. Он состоит из типа пакета и полезной нагрузки. Двоичное представление типа пакета - это один байт (8 бит). Полезная нагрузка представляет собой произвольную последовательность байтов.
| Length | Type | Contents |
|---|---|---|
| 1 | Packet Kind | The packet kind identifier |
| [0,] | Bytes | Payload |
Эти пакеты верхнего уровня могут передаваться различными способами, наиболее распространенным из которых является передача по сети с помощью UDP или TCP. Сам протокол не предписывает транспортных методов, и реализация может свободно реализовать дополнительные транспортные средства, такие как WebRTC, IRC или pipes.
В остальной части документа различные виды протокольных пакетов указываются с указанием типа пакета и полезной нагрузки. Вид пакета не повторяется в описании полезной нагрузки (TODO: на самом деле в основном повторяется, но позже не будет повторяться).
Внутри полезной нагрузки протокольных пакетов другие типы пакетов могут указывать дополнительные виды пакетов. Например, внутри пакета Crypto Data (0x1b) модуль Messenger определяет свои протоколы для обмена сообщениями, передачи файлов и т.д. Протокольные пакеты верхнего уровня сами по себе не шифруются, хотя их полезная нагрузка может быть зашифрована.
4.1 Виды пакетов
Ниже приведен исчерпывающий список названий видов пакетов верхнего уровня и их количество. Их полезная нагрузка указана в специальных разделах. Каждый раздел называется по имени вида пакета, который он описывает, после чего в круглых скобках указывается значение байта, например, Ping Request (0x00).
| Byte value | Packet Kind |
|---|---|
| 0x00 | Ping Request |
| 0x01 | Ping Response |
| 0x02 | Nodes Request |
| 0x04 | Nodes Response |
| 0x18 | Cookie Request |
| 0x19 | Cookie Response |
| 0x1a | Crypto Handshake |
| 0x1b | Crypto Data |
| 0x20 | DHT Request |
| 0x21 | LAN Discovery |
| 0x80 | Onion Request 0 |
| 0x81 | Onion Request 1 |
| 0x82 | Onion Request 2 |
| 0x83 | Announce Request |
| 0x84 | Announce Response |
| 0x85 | Onion Data Request |
| 0x86 | Onion Data Response |
| 0x8c | Onion Response 3 |
| 0x8d | Onion Response 2 |
| 0x8e | Onion Response 1 |
| 0xf0 | Bootstrap Info |
5 DHT
DHT - это самоорганизующийся комплекс всех узлов сети Tox. Узел в сети Tox также называется "узел Tox". Когда мы говорим о " peers", мы имеем в виду любой узел, который не является локальным узлом (субъектом). Этот модуль заботится о поиске IP и порта узлов и установлении маршрута к ним напрямую через UDP, используя при необходимости пробивку дыр. DHT работает только на UDP и поэтому используется только в том случае, если UDP работает.
Каждый узел в Tox DHT имеет эфемерную пару ключей, называемую парой ключей DHT, состоящую из секретного ключа DHT и открытого ключа DHT. Открытый ключ DHT служит в качестве адреса узла. Пара ключей DHT обновляется каждый раз при закрытии или перезапуске экземпляра tox. Реализация может обновлять ключ чаще, но это приведет к отключению всех пиров.
Открытый ключ DHT друга можно найти с помощью модуля onion. Когда открытый ключ DHT друга известен, DHT используется для его поиска и прямого соединения с ним по UDP.
5.1 Дистанции (Distance)
The Distance - это целое положительное число. Его человекочитаемое представление - это число base-16. Distance (тип) - это упорядоченный моноид с ассоциативным бинарным оператором + и элементом тождества 0.
DHT использует метрику для определения расстояния между двумя узлами. Тип Distance является со-доменом этой метрики. В настоящее время метрика, используемая Tox DHT, представляет собой XOR открытых ключей узлов: distance(x, y) = x XOR y. Для этих вычислений открытые ключи интерпретируются как большие целые числа (см. Крипто-числа).
Когда мы говорим о "близком узле", мы имеем в виду, что его расстояние до рассматриваемого узла мало по сравнению с расстоянием до других узлов.
Реализация не обязана предоставлять тип Distance, поэтому он не имеет определенного двоичного представления. Например, вместо того, чтобы вычислять Distance и сравнивать его с другим Distance, реализация может выбрать реализацию Distance в виде пары открытых ключей и определить упорядочение по Distance без вычисления полного интегрального значения. Это работает, потому что как только решение об упорядочении может быть принято в самых значимых битах, последующие биты не будут влиять на это решение.
XOR является корректной метрикой, т.е. удовлетворяет необходимым условиям:
Неотрицательность distance(x, y) >= 0: Поскольку открытые ключи являются крипточислами, которые по определению положительны, их XOR обязательно положителен.
Идентичность неотрицательных чисел distance(x, y) == 0 iff x == y: XOR двух целых чисел равен нулю, если они равны.
Симметрия distance(x, y) == distance(y, x): XOR - симметричная операция.
Субаддитивность distance(x, z) <= distance(x, y) + distance(y, z): следует из ассоциативности, так как x XOR z = x XOR (y XOR y) XOR z = distance(x, y) XOR distance(y, z), что не больше distance(x, y) + distance(y, z).
Кроме того, XOR обладает другими полезными свойствами:
Однонаправленность: для ключа x и расстояния d существует один и только один ключ y такой, что distance(x, y) = d.
Из этого следует, что повторные поиски, скорее всего, будут проходить по одному и тому же пути, поэтому кэширование имеет смысл.
Источник: maymounkov-kademlia
Пример: Даны три узла с ключами 2, 5 и 6:
2 XOR 5 = 7
2 XOR 6 = 4
5 XOR 2 = 7
5 XOR 6 = 3
6 XOR 2 = 4
6 XOR 5 = 3
Ближайшим узлом от 2 и 5 является 6. Ближайшим узлом от 6 является 5 с удалением 3. Этот пример показывает, что ключ, близкий с точки зрения целочисленного сложения, не обязательно будет близким с точки зрения XOR.
5.2 K-buckets
K-buckets - это структура данных для эффективного хранения набора узлов, близких к определенному ключу, называемому базовым ключом. Базовый ключ является постоянным на протяжении всего времени существования экземпляра k-buckets.
k-buckets - это карта из небольших целых чисел 0 <= n < 256 в набор до k инфо узлов. Набор называется bucket. k называется размером bucket. По умолчанию размер bucket равен 8.
Указанное выше число n - это индекс bucket. Это положительное целое число с диапазоном [0, 255], т.е. диапазон 8-битного беззнакового целого числа.
Элемент bucket - это элемент представляет собой упорядоченное множество, и записи сортируются по расстоянию от базового ключа. Таким образом, первый (наименьший) элемент набора является самым близким к базовому ключу в этом наборе, последний (наибольший) элемент - самый удаленный.
5.2.1 Bucket Index
Индекс bucket можно вычислить с помощью следующей функции: bucketIndex(baseKey, nodeKey) = 255 - log_2(distance(baseKey, nodeKey)). Эта функция не определена, когда baseKey == nodeKey, что означает, что k-bucket никогда не будет содержать Node Info о базовом узле.
Таким образом, каждый k-bucket содержит только Node Infos, для ключей которых выполняется следующее: если узел с ключом nodeKey находится в k-bucket с индексом n, то bucketIndex(baseKey, nodeKey) == n. Таким образом, n'th k-bucket состоит из узлов, для которых расстояние до базового узла лежит в диапазоне [2^n, 2^(n+1) - 1].
Индекс bucket можно эффективно вычислить, определив первый бит, в котором отличаются два ключа, начиная со старшего бита. Так, если локальный ключ DHT начинается, например, с 0x80, а ключ узла с bucket начинается с 0x40, то индекс bucket для этого узла равен 0. Если второй бит отличается, то индекс bucket равен 1. Если ключи почти одинаковы и отличается только последний бит, то индекс bucket равен 255.
5.2.2 Обновление k-buckets
TODO: это отличается от политики kademlia по наименьшему времени выселения; почему было выбрано существующее решение, как оно влияет на безопасность, производительность и устойчивость к отравлениям? В оригинальной статье утверждается, что предпочтение старых живых узлов приводит к лучшей стойкости и устойчивости к базовым DDoS-атакам;
Любая операция обновления или поиска на экземпляре k-buckets, в которой участвует один узел, требует, чтобы мы сначала вычислили индекс bucket для этого узла. Обновление с участием Node Info с nodeKey == baseKey не имеет никакого эффекта. Если обновление приводит к появлению пустого элемента, этот элемент удаляется из карты. Блок заполнен, если в нем содержится максимальное количество записей, заданное размером блока.
Узел является жизнеспособным для записи, если bucket не заполнен или открытый ключ узла имеет меньшее расстояние от базового ключа, чем текущая запись с наибольшим расстоянием. Если узел жизнеспособен, а bucket заполнено, запись с наибольшим расстоянием от базового ключа удаляется, чтобы размер bucket не превышал максимальный настроенный размер bucket
Добавление узла, ключ которого уже существует, приводит к обновлению информации об узле в bucket. Удаление узла, для которого не существует информации об узле в k-bucket не имеет никакого эффекта. Таким образом, удаление узла дважды разрешено и имеет тот же эффект, что и однократное удаление.
5.3 Состояние узла DHT
Каждый узел DHT содержит пару ключей, называемую парой ключей DHT.
Узел DHT также хранит набор Node Infos узлов, которые близки к его собственному открытому ключу DHT. Для этого используется структура данных k-buckets, а в качестве базового ключа используется локальный открытый ключ DHT.
5.4 Самоорганизация
Самоорганизация в DHT происходит путем подключения каждого пира DHT к произвольному числу пиров, ближайших к его собственному открытому ключу DHT, и некоторых, находящихся дальше.
Если каждый пир в сети знает пиров с открытым ключом DHT, ближайшим к его открытому ключу DHT, то для поиска конкретного пира с открытым ключом X пиру достаточно рекурсивно запросить пиров в DHT об известных пирах, имеющих открытые ключи DHT, ближайшие к X. В конечном итоге пир найдет пиров в DHT, которые ближе всего к этому пиру, и, если этот пир находится в сети, он найдет их.
5.5 Пакет DHT
Пакет DHT содержит открытый ключ DHT отправителя, шифрующий код Nonce и зашифрованную полезную нагрузку. Полезная нагрузка шифруется с помощью секретного ключа DHT отправителя, открытого ключа DHT получателя и nonce, который отправляется вместе с пакетом. Пакеты DHT отправляются внутри пакетов протокола с изменяющимся типом пакета.
| Length | Type | Contents |
|---|---|---|
| 32 | Public Key | Sender DHT Public Key |
| 24 | Nonce | Random nonce |
| [16,] | Bytes | Encrypted payload |
Длина зашифрованной полезной нагрузки составляет не менее 16 байт, поскольку шифрование включает MAC из 16 байт. Таким образом, 16-байтовая полезная нагрузка была бы пустым сообщением. Протокол DHT никогда не отправляет пустые сообщения, поэтому в действительности минимальный размер Ping-пакета составляет 27 байт.
5.6 Службы RPC
Служба RPC DHT состоит из пакета запроса и пакета ответа. Пакет DHT RPC содержит полезную нагрузку и идентификатор запроса. Этот идентификатор представляет собой 64-битное беззнаковое целое число, которое помогает идентифицировать ответ для данного запроса. Идентификатор запроса в ответном пакете должен быть равен идентификатору запроса в запросе, на который он отвечает.
Пакеты DHT RPC шифруются и передаются внутри пакетов DHT.
| Length | Type | Contents |
|---|---|---|
| [0,] | Bytes | Payload |
| 8 | uint64_t | Request ID |
Минимальный размер полезной нагрузки равен 0, но в действительности наименьший разумный размер полезной нагрузки равен 1. Поскольку в обоих направлениях связи используется один и тот же симметричный ключ, зашифрованный Запрос будет действительным зашифрованным Ответом, если они содержат один и тот же открытый текст.
Части протокола, использующие пакеты RPC, должны позаботиться о том, чтобы полезная нагрузка запроса не была действительной полезной нагрузкой ответа. Например, пакеты Ping несут булевый флаг, который указывает, соответствует ли полезная нагрузка Запросу или Ответу.
Идентификатор запроса обеспечивает некоторую устойчивость к атакам повторного воспроизведения. Если бы не было идентификатора запроса, злоумышленнику было бы легко воспроизвести старые ответы и таким образом предоставить узлам устаревшую информацию. Точное значение Request ID будет указано позже в разделе DHT.
5.6.1 Служба Ping
Служба Ping используется для периодической проверки того, жив ли еще другой узел.
Полезная нагрузка Ping-пакета состоит только из булева значения, говорящего о том, является ли это запросом или ответом.
Однобайтовое булево значение внутри зашифрованной полезной нагрузки добавляется для того, чтобы предотвратить создание пирами действительного Ping-ответа из Ping-запроса без расшифровки пакета и шифрования нового. Поскольку используется симметричное шифрование, зашифрованный Ping Response будет по байтам равен Ping Request без байта отличия.
| Length | Type | Contents |
|---|---|---|
| 1 | Bool | Response flag: 0x00 for Request, 0x01 for Response |
5.6.1.1 Запрос Ping (0x00)
Запрос Ping - это пакет Ping с флагом ответа, установленным на False. Когда запрос Ping Request получен и успешно расшифрован, создается пакет Ping Response и отправляется обратно запрашивающему.
5.6.1.2 Ping Response (0x01)
Ping Response - это Ping-пакет с флагом ответа, установленным на True.
5.6.2 Служба узлов
Служба Nodes Service используется для запроса у другого узла DHT до 4 известных им узлов, которые находятся ближе всего к запрашиваемому узлу.
RPC-служба DHT Nodes использует формат упакованных узлов.
5.6.2.1 Запрос узлов (0x02)
| Length | Type | Contents |
|---|---|---|
| 32 | Public Key | Requested DHT Public Key |
Открытый ключ DHT, отправленный в запросе, ищет отправитель.
5.6.2.2 Ответ узлов (0x04)
| Length | Type | Contents |
|---|---|---|
| 1 | Int | Number of nodes in the response (maximum 4) |
| [39, 204] | Node Infos | Nodes in Packed Node Format |
Узел IPv4 имеет размер 39 байт, узел IPv6 - 51 байт, поэтому максимальный размер составляет 51 * 4 = 204 байта.
Ответы узлов должны содержать 4 ближайших узла, которые отправитель ответа имеет в своем списке известных узлов.
5.7 Принципы работы DHT
Только протокол UDP (IP Type 2 и 10) используется в модуле DHT при отправке узлов с упакованным форматом узлов. Это связано с тем, что протокол TCP используется для отправки ретрансляционной информации TCP, а DHT - только UDP.
Это сделано для увеличения скорости поиска пиров. Toxcore также хранит 8 узлов (они должны быть такими же или меньше, чем узлы, которые Toxcore хранит для каждого индекса в своем списке close list, чтобы убедиться, что все найденные ближайшие пиры будут знать искомый узел), ближайших к каждому из открытых ключей в своем списке друзей DHT (или списке открытых ключей DHT, которые он активно пытается найти и подключиться к ним). Toxcore пингует каждый узел в списках каждые 60 секунд, чтобы проверить, жив ли он. Он не хранит себя ни в одном из списков и не посылает никаких запросов самому себе. Узлы могут находиться более чем в одном списке, например, если открытый ключ DHT пира очень близок к открытому ключу DHT друга, которого ищут. Он также посылает запросы get node на случайный узел (случайность делает его непредсказуемым, предсказуемость или знание того, какой узел будет пинговаться следующим, может облегчить некоторые атаки, нарушающие работу сети, поскольку добавляет возможный вектор атаки) в каждом из этих списков узлов каждые 20 секунд, при этом публичным ключом поиска является его публичный ключ для ближайшего узла, а публичными ключами поиска - те, которые находятся в списке друзей DHT. Узлы удаляются после 122 секунд отсутствия ответа. Узлы добавляются в списки после получения от них правильного ответа ping или пакета send node. Если узел уже присутствует в списке, он обновляется, если изменился его IP-адрес. Узел может быть добавлен в список, только если список не полон или если открытый ключ DHT узла ближе, чем открытый ключ DHT хотя бы одного из узлов в списке к открытому ключу, поиск которого ведется в этом списке. Когда узел добавляется в полный список, он заменяет самый дальний узел.
Если увеличить число узлов до 32, это увеличит количество пакетов, необходимых для проверки того, жив ли каждый из них, что увеличит использование полосы пропускания, но повысит надежность. Если бы количество узлов было уменьшено, надежность снизилась бы вместе с использованием полосы пропускания. Причина такой зависимости между надежностью и количеством узлов заключается в том, что если мы предположим, что не все узлы имеют открытые UDP порты или находятся за NAT, это означает, что каждый из этих узлов должен иметь возможность хранить определенное количество узлов за NAT, чтобы другие могли найти эти узлы за NAT. Например, если 7/8 узлов находятся за NAT, использование 8 узлов будет недостаточно, потому что вероятность того, что некоторые из этих узлов невозможно будет найти в сети, будет слишком высока. Если бы таймауты пингов и задержки между пингами были выше, это уменьшило бы использование полосы пропускания, но увеличило бы количество отключенных узлов, которые все еще хранятся в списках. Уменьшение этих задержек привело бы к обратному результату.
Если увеличить число узлов ближайших к каждому открытому ключу от 8, до 16, это увеличит использование полосы пропускания, может повысить эффективность атак на симметричных NAT и надежность. Уменьшение этого числа будет иметь противоположный эффект.
При получении пакета send node, toxcore проверяет, может ли каждый из полученных узлов быть добавлен в любой из списков. Если узел может быть добавлен, toxcore посылает ему ping-пакет, если не может - игнорирует. При получении пакета get node, toxcore найдет 4 узла в своих списках узлов, наиболее близких к открытому ключу в пакете, и отправит их в ответе send node. Таймауты и количество узлов в списках для toxcore были выбраны исключительно по ощущениям и, вероятно, не являются лучшими значениями. Это также относится к поведению, которое является простым и должно быть улучшено, чтобы сделать сеть более устойчивой к атакам sybil.
5.8 Пакеты запроса DHT
| Length | Contents |
|---|---|
| 1 | uint8_t (0x20) |
| 32 | receiver's DHT public key |
| 32 | sender's DHT public key |
| 24 | nonce |
| ? | encrypted message |
Пакеты запросов DHT - это пакеты, которые могут быть отправлены через один узел DHT другому узлу, который он знает. Они используются для отправки зашифрованных данных друзьям, с которыми мы не обязательно связаны напрямую в DHT.
Узел DHT, получивший пакет запроса DHT, проверяет, является ли открытый ключ получателя его открытым ключом DHT, и если да, то расшифровывает и обрабатывает пакет. Если нет, они проверят, знают ли они этот открытый ключ DHT (если он есть в их списке близких узлов). Если нет, они отбрасывают пакет. Если да, то они повторно отправят точно такой же пакет этому узлу DHT.
Зашифрованное сообщение шифруется с помощью открытого ключа DHT получателя, закрытого ключа DHT отправителя и nonce (случайно сгенерированные 24 байта).
Пакеты запросов DHT используются для пакетов DHTPK (см. лук) и пакетов NAT ping.
5.8.1 Пакеты NAT ping
Инкапсулируются в пакет запроса DHT.
Пакеты NAT ping используются для проверки, находится ли друг, с которым мы не связаны напрямую, в сети и готов ли он
5.8.1.1 Запрос NAT ping
| Length | Contents |
|---|---|
| 1 | uint8_t (0xfe) |
| 1 | uint8_t (0x00) |
| 8 | uint64_t random number |
Полученный NAT ping запрос необходимо проверить, не от друга ли он.
Проверка осуществляется путем сравнения открытого ключа из пакета запроса DHT, в который был инкапсулирован данный NAT ping запрос, со списком открытых ключей собственных друзей.
Если NAT ping запрос...
... от своего друга, ответ NAT ping с тем же случайным числом, что и запрос, должен быть послан обратно через узлы, которые знают друга, пославшего запрос.
Если нет узлов, которые знают друга, пакет будет отброшен.
... не от собственного друга, пакет будет сброшен.
5.8.1.2 Ответ NAT ping
| Length | Contents |
|---|---|
| 1 | uint8_t (0xfe) |
| 1 | uint8_t (0x01) |
| 8 | uint64_t random number (the same that was received in request) |
5.9 Сквозное сеодинение
Механизм для определения необходимости запуска сквозного сеодинения:
Запрос от 8 ближайших к собственному узлу пиров IP:порт друга.
По крайней мере 4+ узлов возвращают IP:порт друга.
Отправьте запрос DHT ping на каждый из этих IP:портов.
Если ответ не получен, начните Сквозное сеодинение
Числа 8 и 4 используются в toxcore и были выбраны только на основе "ощущения" и могут быть не оптимальными.
Перед началом сквозное сеодинение другу посылается пакет NAT ping через пиров, которые утверждают, что знают друга. Если получен ответ NAT ping с тем же случайным числом, то сквозное сеодинение должно быть начато.
Получение ответа NAT ping означает, что друг находится в сети и активно ищет нас, поскольку это единственный способ узнать узлы, которые знают нас. сквозное сеодинение будет работать только в том случае, если друг активно пытается подключиться к нам.
Запросы NAT ping отправляются каждые 3 секунды в toxcore. Если в течение 6 секунд не получен ответ, сквозное сеодинение прекращается.
Отправка запросов NAT ping через более длительные интервалы может увеличить вероятность того, что другой узел выйдет из сети и пакеты ping, посылаемые в процессе создания сквозного сеодинения будут отправлены "мертвому" аналогу, но потенциально это может снизить использование полосы пропускания. Уменьшение интервалов будет иметь противоположный эффект.
Для "сквозное сеодинения" мы предполагаем, что люди, использующие Tox, используют один из трех типов NAT:
- Cone NAT
- Restricted Cone NAT
- Symmetric NAT
4+ близких к заданному публичному ключу пиров вернули один и тот же IP:порт
4+ близких к данному открытому ключу пиров вернули один и тот же IP, но разный порт
близкие к данному PK пиры вернули разные IP:порты
5.9.1 Cone NAT
Поведение программного обеспечения, выполняющего NAT:
Назначить один целый порт каждому UDP сокету за NAT, любой пакет с любого IP:порта, посланный на этот назначенный порт из Интернета, будет перенаправлен на сокет за ним.
Сквозное соединение с помощью обычных cone NAT достигается просто за счет способа функционирования DHT, т.е. мы посылаем на IP:порт друга DHT запрос ping, и получаем ответ DHT ping.
5.9.2 Restricted Cone NAT
Поведение программного обеспечения, выполняющего NAT:
Назначить один целый порт каждому UDP сокету за NAT. Перенаправлять пакеты только с IP, на которые сокет UDP отправил пакет.
Если 4+ узлов рядом с нами возвращают один и тот же IP:порт друга, это означает, что друг находится на Restricted Cone NAT.
Сквозное соединение можно осуществить, заставив друга отправить пакет на наш публичный IP:порт. Это означает, что сквозное соединение может быть достигнуто легко и что мы должны просто продолжать регулярно посылать пакеты DHT ping на этот IP:порт, пока не получим ответ DHT ping. Это сработает, потому что друг ищет нас в DHT. Найдя нас, друг отправит пакет на наш публичный IP:порт , тем самым установив соединение.
5.9.3 Симметричный NAT
Худший вариант.
Поведение программного обеспечения, выполняющего NAT:
Присваивать новый порт для каждого IP:порта, на который отправляется пакет. Рассматривать каждый новый пир, на который отправляется UDP-пакет, как соединение. Пересылать в сокет пакеты только с назначенного IP:порта этого соединения.
В случае плохой реализации, crash & burn, заставляя пользователей плакать.
Близкие узлы не возвращают один и тот же порт для друга - это означает, что друг находится на симметричном NAT.
Некоторые симметричные NAT открывают порты последовательно, что может привести к тому, что порты, возвращаемые близкими нам узлами, будут, например, 1345, 1347, 1389, 1395.
Сквеозное соединение с помощью симметричных NAT основано на угадывании того, какие порты с большей вероятностью будут использоваться другом, когда он пытается отправить нам ping-запрос. Запросы DHT ping посылаются на эти порты до тех пор, пока не будет получен ответ DHT ping.
Toxcore пробует все порты, рядом с каждым возвращенным портом (например, для 4 портов, перечисленных ранее, он будет пробовать: 1345, 1347, 1389, 1395, 1346, 1348, 1390, 1396, 1344, 1346...), постепенно пробуя порты все дальше от тех, о работе которых сообщили близкие узлы.
Пробуйте до 48 портов каждые 3 секунды, пока соединение не будет установлено.
После 5 попыток удвойте (?)диапазон(?) и начните пробовать порты от 1024, 48 одновременно, включая ранее угаданные порты.
Это, похоже, исправляет ситуацию для некоторых симметричных NAT, скорее всего, потому что многие из них перезапускают счетчик на 1024. - irungentoo
Увеличение количества портов, проверяемых в секунду, ускорит процесс скврзного соединения, но также может привести к DoS NAT из-за большого количества пакетов, отправляемых на разные IP за короткий промежуток времени. Уменьшение количества портов замедляет процесс сквозного соединения
Хотя это работает, но метод может быть улучшен.
5.9.4 Разные IP:порты
Может возникнуть, когда пиры возвращают разные IP и порты.
Есть 2 случая, когда это может произойти:
друг находится за очень строгим NAT, который не может быть пробит.
друг недавно подключился к другому интернет соединению и некоторые пиры все еще имеют устаревшую информацию.
Ничего нельзя сделать, если NAT очень строгий, поэтому рекомендуется использовать наиболее распространенный IP, возвращаемый пирами, и игнорировать другие IP:порты.
5.10 Информация о Bootstrap узлах DHT (0xf0)
Узлы Bootstrap - это обычные узлы Tox со стабильным открытым ключом DHT. Это означает, что открытый ключ DHT не меняется при перезагрузках. Узлы бутстрапа DHT имеют один дополнительный тип запроса: Bootstrap Info. Запрос представляет собой пакет длиной 78 байт, где первый байт - 0xf0. Остальные байты игнорируются.
Формат ответа следующий:
| Length | Type | Contents |
|---|---|---|
| 4 | Word32 | Bootstrap node version |
| 256 | Bytes | Message of the day |
6 Обнаружение локальной сети
Обнаружение локальной сети - это способ обнаружения пиров Tox, находящихся в локальной сети. Если два друга Tox находятся в локальной сети, то наиболее эффективным способом их совместного общения будет использование локальной сети. Если клиент Tox открыт в локальной сети, в которой существует другой клиент Tox, то хорошим поведением будет загрузиться в сеть, используя клиент Tox в локальной сети. Именно этого и добивается обнаружение локальной сети.
Обнаружение локальной сети происходит путем отправки UDP пакета через UDP сокет toxcore на широковещательный адрес интерфейса в IPv4, глобальный широковещательный адрес (255.255.255.255) и многоадресный адрес в IPv6 (FF02::1) на UDP порт Tox по умолчанию (33445).
Пакет LAN Discovery:
| Length | Contents |
|---|---|
| 1 | uint8_t (33) |
| 32 | DHT public key |
Пакеты LAN Discovery содержат открытый ключ DHT отправителя. При получении пакета LAN Discovery отправителю пакета будет отправлен пакет DHT get nodes. Это означает, что экземпляр DHT будет загружать себя к каждому пиру, от которого он получает один из этих пакетов. Благодаря этому механизму клиенты Tox будут автоматически загружаться от других клиентов Tox, работающих в локальной сети.
Когда этот механизм включен, toxcore отправляет эти пакеты каждые 10 секунд, чтобы снизить задержки. Пакеты можно посылать и каждые 60 секунд, но это сделает поиск пиров по сети в 6 раз медленнее.
Поиск по локальной сети позволяет двум друзьям в локальной сети найти друг друга, так как DHT отдает приоритет адресам локальной сети над адресами вне локальной сети для DHT пиров. Успешная отправка запроса get node request/bootstrapping от пира должна также добавить его в список пиров DHT, если мы его ищем. Пир не должен быть немедленно добавлен, если пакет LAN discovery с открытым ключом DHT, который мы пингуем, должен быть послан, и должен быть получен правильный ответ, прежде чем мы сможем сказать, что этот пир найден.
Обнаружение локальной сети - это то, как Tox обрабатывает и заставляет все хорошо работать в локальной сети.