• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Перевод текстовой специикации протокола TOX ч2

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
7 TCP-сервер

Цель TCP-сервера - действовать как TCP-реле между клиентами, которые не могут подключиться друг к другу напрямую или по каким-то причинам ограничены в использовании протокола TCP для подключения друг к другу. TCP_server обычно запускается только на реальных серверных машинах, но любой клиент Tox может разместить у себя такой сервер, поскольку api для его запуска раскрывается через tox.h api.
Для подключения к размещенному TCP-серверу toxcore использует модуль TCP-клиента.
TCP-соединения между TCP-клиентом и сервером шифруются, чтобы посторонний человек не мог узнать информацию о том, кто к кому подключается, просто посмотрев на чье-то соединение с TCP-сервером. Это полезно, когда кто-то подключается через что-то вроде Tor, например. Это также предотвращает внедрение данных в поток третьей стороной и позволяет нам считать, что любые полученные данные не были подделаны и являются именно теми, которые были отправлены клиентом.
TCP-сервер, по сути, действует как ретранслятор между двумя одноранговыми сетями. Когда TCP-клиент подключается к серверу, он сообщает серверу, с какими клиентами он хочет, чтобы сервер его соединил. Сервер позволит двум клиентам соединиться друг с другом только в том случае, если они оба указали серверу, что хотят соединиться друг с другом. Это делается для того, чтобы недруги не могли проверить, подключен ли кто-то к TCP-серверу. TCP-сервер поддерживает отправку пакетов вслепую через него клиентам с клиентом с открытым ключом X (OOB-пакеты), однако TCP-сервер не дает никакой обратной связи относительно того, прибыл пакет или нет, и поэтому он полезен только для отправки данных друзьям, которые могут не знать, что мы подключены к текущему TCP-серверу, в то время как мы знаем, что они подключены.
Это происходит, когда один пир обнаруживает TCP реле и открытый ключ DHT другого пира до того, как другой пир обнаруживает свой открытый ключ DHT. В этом случае OOB-пакеты будут использоваться до тех пор, пока другой пир не узнает, что пир подключен к реле и не установит соединение через него.
Для того чтобы toxcore работал на TCP, только TCP-сервер поддерживает ретрансляцию луковых пакетов от TCP-клиентов и отправку любых ответов от них TCP-клиентам.

7.1 TCP-соединение

Когда клиент впервые подключается к TCP-серверу, он открывает TCP-соединение с IP-адресом и портом, который прослушивает TCP-сервер. После установления соединения отправляется пакет Handshake Request. Затем сервер отвечает своим Handshake Response, и устанавливается безопасное соединение. После этого соединение считается неподтвержденным, и клиент должен отправить на сервер зашифрованные данные, прежде чем сервер сможет отметить соединение как подтвержденное.
Это делается для того, чтобы предотвратить тип атаки, при которой клиент отправляет пакет рукопожатия, а затем сразу же прерывает соединение. Чтобы предотвратить это, сервер должен подождать несколько секунд, пока клиент получит пакет handshake, прежде чем подтвердить соединение. После этого оба могут общаться друг с другом, используя зашифрованное соединение.

7.2 TCP Handshake

Логика, лежащая в основе формата рукопожатия, заключается в том, что нам:

необходимо доказать серверу, что мы владеем закрытым ключом, связанным с открытым ключом, которым мы себя объявляем.
установить безопасное соединение, которое обладает совершенной прямой секретностью
предотвратить любые атаки воспроизведения, самозванства или другие атаки.

Как достигается каждый из этих пунктов:

Если клиент не владеет закрытым ключом, связанным с открытым ключом, он не сможет создать пакет рукопожатия.
Временные сеансовые ключи, созданные клиентом и сервером в зашифрованной части Handshake Packets, используются для шифрования/дешифрования пакетов во время сеанса.

Предотвращаются следующие атаки:

Злоумышленник изменяет любой байт пакета Handshake: Расшифровка не удается, атаки невозможны.
Атакующий перехватывает пакет рукопожатия у клиента и позже воспроизводит его на сервере: Атакующий никогда не получит от сервера подтверждения соединения (нет эффекта).
Злоумышленник перехватывает ответ сервера и отправляет его клиенту при следующей попытке подключения к серверу: Клиент никогда не подтвердит соединение. (См.: TCP_client)
Злоумышленник пытается выдать себя за сервер: Он не сможет расшифровать Handshake и не сможет ответить.
Атакующий пытается выдать себя за клиента: Сервер не сможет расшифровать Handshake.

7.2.1 Диаграмма Handshake

7.2.1.1 Диаграмма Handshake - клиент

PK + PublicKey
SK + SecretKey
PcK + PrecomputedKey (Combined Key)

Диграмму переписывать я не стал, а сделал в виде фотографий в которых нижняя дополняет верхнюю, думаю будет понятно

Посмотреть вложение 54373
Посмотреть вложение 54374

7.2.1.2 Диаграмма рукопожатия — сервер​


PK + PublicKey
SK + SecretKey
PcK + PrecomputedKey (Combined Key)

Посмотреть вложение 54375
Посмотреть вложение 54376

7.2.2 Запрос рукопожатия

Первые 32 байта в запросе - это открытый ключ DHT, с помощью которого клиент TCP сообщает о себе серверу. Следующие 24 байта - это nonce, который TCP-клиент использует вместе с секретным ключом, связанным с открытым ключом в первых 32 байтах пакета, для шифрования остальной части пакета.
Зашифрованная полезная нагрузка содержит временный открытый ключ, который будет использоваться для шифрования во время соединения и будет отброшен после него. Он также содержит Client Base Nonce, который будет использоваться позже для шифрования пакетов, отправляемых на TCP-сервер.
Для установления безопасного соединения с TCP-сервером клиент отправляет серверу следующие 128 байт Handshake Packet:

LengthContents
32DHT Public Key of client
24Nonce for the encrypted payload
72Payload (plus MAC)

7.2.2.1 Полезная нагрузка пакета запроса рукопожатия

Полезная нагрузка шифруется с помощью закрытого ключа DHT клиента и открытого ключа сервера, а также с помощью nonce и содержит:

LengthContents
32Temporary Public Key
24Client Base Nonce

Client Base Nonce - это случайный nonce, который TCP-клиент хочет, чтобы TCP-сервер использовал для расшифровки пакетов, отправляемых TCP-серверу.

7.2.3 Ответ на рукопожатие

Если сервер успешно расшифровывает зашифрованную полезную нагрузку из пакета Handshake Packet, он отвечает следующим Handshake Response длиной 96 байт:

LengthContents
24Nonce for the encrypted payload
72Payload (plus MAC)

Клиент уже знает долгосрочный открытый ключ сервера, поэтому он опущен в ответе Handshake Response, таким образом, в незашифрованной части присутствует только nonce. Зашифрованная полезная нагрузка ответа содержит те же элементы, что и зашифрованная полезная нагрузка Handshake Request: временный открытый ключ, привязанный к данному соединению, и Server Base Nonce, который будет использоваться позже при расшифровке пакетов, полученных от TCP-сервера, оба уникальны для данного соединения.

7.2.3.1 Полезная нагрузка ответа на квитирование

Полезная нагрузка шифруется с помощью закрытого ключа сервера и открытого ключа DHT клиента, а также с помощью nonce и содержит:

LengthContents
32Public key
24Server Base Nonce


Server Base Nonce - это случайный nonce, который TCP-сервер хочет, чтобы TCP-клиент использовал для расшифровки пакетов, отправляемых TCP-клиенту.

7.3 После рукопожатия

Теперь клиент будет знать временный открытый ключ сервера и Server Base Nonce для соединения, а сервер будет знать Client Base Nonce и временный открытый ключ для соединения.
Затем клиент отправит зашифрованный пакет на сервер. Первый пакет должен быть любым действительным зашифрованным пакетом данных, на который сервер должен ответить, например, если клиент посылает Ping Request (0x04), сервер должен ответить Ping Response.
Первый пакет, отправленный клиентом, и ответ сервера используются для проверки того, может ли клиент правильно зашифровать отправленный пакет и правильно расшифровать полученный ответ.
Это важно и для сервера, поскольку правильно зашифрованный клиентом запрос означает, что он действительно пришел от клиента, а не от ответных пакетов злоумышленника.
Сервер должен предотвращать ресурсозатратные атаки путем тайминга клиентов, если они не посылают никаких зашифрованных пакетов на сервер, чтобы доказать серверу, что соединение было установлено правильно.
В Toxcore нет таймаута для клиентов, вместо этого он хранит подключающихся клиентов в больших круговых списках и отключает их, если их запись в списке заменяется более новым соединением. Это делается для того, чтобы предотвратить негативное влияние атак TCP flood на подключенные узлы. Однако есть гораздо лучшие способы сделать это, и единственная причина, по которой toxcore делает это таким образом, заключается в том, что написать его было очень просто. Когда соединения подтверждаются, они перемещаются в другое место.
Когда сервер подтверждает соединение, он должен посмотреть в списке подключенных аналогов, не подключен ли он уже к клиенту с таким же объявленным открытым ключом. Если это так, сервер должен уничтожить предыдущее соединение, так как это означает, что клиент ранее вышел по таймеру и снова подключается. Из-за конструкции Toxcore очень маловероятно, чтобы два разных пира имели одинаковый открытый ключ.

7.4 Зашифрованный пакет данных

Зашифрованные пакеты данных для посторонних выглядят следующим образом:

LengthContents
2uint16_t length of data
variableencrypted data

В потоке TCP они будут выглядеть следующим образом: [[length][data]][[length][data]][[length][data]]....

7.4.1 Ключи зашифрованных пакетов данных

И клиент, и сервер используют временные открытый и секретный ключи соединения, которые генерируются каждый для своего соединения, а затем часть открытого ключа отправляется другому в процессе рукопожатия. Затем они используются, как показано на схеме, для генерации комбинированного ключа, который одинаков для обеих сторон:

Client: Server:
generate_shared_key( generate_shared_key(
[temp connection Public Key of server], [temp connection Public Key of client],
[temp connection Secret Key of client]) [temp connection Secret Key of server])
= =
[Combined Key] [Combined key]

Сгенерированный комбинированный ключ одинаков для обеих сторон и используется для шифрования и дешифрования зашифрованных пакетов данных.



7.4.2 Зашифрованные nonces пакетов данных

7.4.2.1 Зашифрованные пакеты данных от сервера

Каждый зашифрованный пакет данных, отправленный клиенту, будет зашифрован с помощью комбинированного ключа и с помощью Server Base Nonce, увеличенного на количество отправленных пакетов: Server Base Nonce + количество отправленных пакетов.Количество отправленных пакетов начинается с 0 для первого пакета, в котором используется Server Base Nonce. Второй пакет будет использовать Server Base Nonce + 1, и так далее.

7.4.2.2 Зашифрованные пакеты данных от клиента

Каждый пакет, отправленный на сервер от клиента, будет зашифрован с помощью комбинированного ключа и Client Base Nonce, увеличенного на количество отправленных пакетов: Client Base Nonce + количество отправленных пакетов. Количество отправленных пакетов начинается с 0 для первого пакета, в котором используется Client Base Nonce. Второй пакет будет использовать Client Base Nonce + 1, и так далее.

7.4.3 Размер пакета зашифрованных данных

Зашифрованные пакеты данных имеют жесткий максимальный размер 2 + 2048 байт в реализации сервера toxcore TCP.
2 байта информируют о количестве байт, которые имеет следующий пакет данных.

2048 байт достаточно для того, чтобы убедиться, что все пакеты toxcore могут пройти через него, и оставляет некоторое дополнительное пространство на случай, если протокол будет изменен в будущем.
В текущем toxcore самые большие отправляемые зашифрованные пакеты данных будут иметь размер 2 + 1417, что в сумме составляет 1419.

7.4.4 Обоснование зашифрованного пакета данных

Логика, лежащая в основе формата зашифрованных пакетов, такова:

TCP - потоковый протокол, нам нужны пакеты.
Любые атаки должны быть предотвращены.

Как достигается каждый из этих пунктов:

2 байта перед каждым пакетом зашифрованных данных обозначают длину. Мы предполагаем, что функционирующий TCP будет передавать байты по порядку. Если TCP работает некорректно, это, скорее всего, означает, что он подвергается атаке, о чем см. следующий пункт.

Следующие атаки предотвращаются:

Изменение длины байта приведет к тайм-ауту соединения и/или сбою расшифровки.
Изменение любых зашифрованных байтов приведет к сбою расшифровки.
Инъекция любых байтов приведет к сбою расшифровки.
Попытка переупорядочить пакеты приведет к сбою расшифровки из-за упорядоченного nonce.
Удаление любых пакетов из потока приведет к неудаче расшифровки из-за заказанного nonce.

7.5 Типы зашифрованной полезной нагрузки

Ниже представлены различные типы данных, которые могут быть отправлены в зашифрованных пакетах данных.

7.5.1 Запрос маршрутизации (0x00)

LengthContents
1uint8_t (0x00)
32Public key

7.5.2 Ответ на запрос маршрутизации (0x01)

LengthContents
1uint8_t (0x01)
1uint8_t rpid
32Public key

rpid недействителен connection_id (0), если отказано, connection_id, если принято if accepted.

7.5.3 Connect notification (0x02)

LengthContents
1uint8_t (0x02)
1uint8_t connection_id of connection that got connected

7.5.4 Disconnect notification (0x03)​


Длина Содержание
1uint8_t(0x03)
1uint8_t connection_idс оединения, которое было отключено

7.5.5 Запрос проверки связи (0x04)​


Длина Содержание
1uint8_t(0x04)
8uint64_t ping_id(0 недействителен)

7.5.6 Ответ на эхо-запрос (понг) (0x05)​


Длина Содержание
1uint8_t(0x05)
8uint64_t ping_id(0 недействителен)

7.5.7 Отправка OOB (0x06)​


Длина Содержание
1uint8_t(0x06)
32Открытый ключ назначения
переменная Данные

7.5.8 Прием OOB (0x07)​


Длина Содержание
1uint8_t(0x07)
32Открытый ключ отправителя
переменная Данные

7.5.9 Луковый пакет (0x08)

Тот же формат, что и у исходного лукового пакета, но идентификатор пакета — 0x08 вместо 0x80.

7.5.10 Ответ лукового пакета (0x09)

Тот же формат, что и у лукового пакета, но идентификатор пакета — 0x09 вместо 0x8e.

7.5.11 Данные (0x10 и выше)

Длина Содержание
1uint8_t идентификатор пакета
1uint8_t идентификатор соединения
переменная данные


TCP-сервер настроен таким образом, чтобы минимизировать потери при ретрансляции множества пакетов, которые могут проходить между двумя равноправными пользователями, поэтому клиенты должны создавать соединения с другими клиентами на реле. Номер соединения представляет собой uint8_t и должен быть равен или больше 16, чтобы быть действительным. Поскольку uint8_t имеет максимальное значение 256, это означает, что максимальное количество различных соединений с другими клиентами, которое может быть у каждого соединения, равно 240. Причина, по которой действительные connection_ids больше 16, заключается в том, что они являются первым байтом пакетов данных. В настоящее время используются только номера от 0 до 9, однако мы оставляем несколько дополнительных номеров на случай, если нам понадобится расширить протокол, не нарушая его полностью.
Запрос маршрутизации (отправляется клиентом серверу): Посылаем запрос маршрутизации на сервер, что мы хотим подключиться к пиру с открытым ключом, где открытый ключ - это тот, которым пир объявил себя. Сервер должен ответить на этот запрос ответом маршрутизации.
Ответ маршрутизации (отправляется сервером клиенту): Ответ на запрос маршрутизации, сообщает клиенту, был ли запрос маршрутизации успешным (действительный идентификатор соединения), и если да, то сообщает идентификатор соединения (идентификатор соединения). Открытый ключ, отправленный в запросе маршрутизации, также отправляется в ответе, чтобы клиент мог отправлять много запросов одновременно на сервер без необходимости отслеживать, какой ответ принадлежит какому открытому ключу.
Единственная причина, по которой запрос маршрутизации может не сработать, это если соединение достигло максимального количества одновременных соединений. В случае неудачи запроса маршрутизации открытый ключ в ответе будет соответствовать открытому ключу в неудачном запросе.
Уведомление о подключении (отправляется сервером клиенту): Сообщает клиенту, что connection_id теперь подключен, что означает, что другой находится в сети и данные могут быть отправлены с использованием этого connection_id.
Уведомление об отключении (отправляется клиентом серверу): Отправляется, когда клиент хочет, чтобы сервер забыл о соединении, связанном с connection_id в уведомлении. Сервер должен удалить это соединение и иметь возможность повторно использовать connection_id для другого соединения. Если соединение было установлено, сервер должен послать уведомление об отключении другому клиенту. Другой клиент должен думать, что этот клиент просто отключился от TCP-сервера.
Уведомление о разъединении (отправляется сервером клиенту): Отправляется сервером клиенту, чтобы сообщить ему, что соединение с connection_id, которое было подключено, теперь отключено. Оно отправляется либо когда другой клиент соединения отключается, либо когда он сообщает серверу о разрыве соединения (см. выше).
Пакеты Ping и Pong (могут быть посланы как клиентом, так и сервером, оба ответят): пакеты ping используются для того, чтобы узнать, жива ли другая сторона соединения. TCP при установлении соединения не имеет никаких разумных тайм-аутов (1 неделя - это не разумно), поэтому мы вынуждены иметь свой собственный способ проверки, жива ли другая сторона. Ping id может быть любым, кроме 0, это связано с тем, как toxcore устанавливает переменную, хранящую ping_id, который был отправлен, в 0, когда он получает ответ pong, что означает, что 0 недействителен.
Сервер должен посылать пакеты ping каждые X секунд (toxcore TCP_server посылает их каждые 30 секунд и прерывает работу пира, если не получает ответа за 10). Сервер должен немедленно отвечать на пакеты ping пакетами pong.
Сервер должен отвечать на ping-пакеты pong-пакетами с тем же ping_id, который был в ping-пакете. Сервер должен проверить, что каждый pong-пакет содержит тот же ping_id, что и в ping, если нет, то pong-пакет должен быть проигнорирован.

OOB send (Отправляется клиентом на сервер): Если подключен пир с закрытым ключом, равным ключу, которым он объявил себя, данные в пакете OOB send будут отправлены этому пиру как пакет OOB recv. Если такой пир не подключен, пакет отбрасывается. Реализация TCP_server в toxcore имеет жесткий максимум длины данных OOB, равный 1024. 1024 было выбрано потому, что оно достаточно велико для пакетов net_crypto, связанных с рукопожатием, и достаточно велико, чтобы любые изменения в протоколе не требовали разрушения TCP-сервера. Однако оно недостаточно велико для самых больших пакетов net_crypto, отправляемых при установленном соединении net_crypto, чтобы предотвратить их отправку через OOB-пакеты.
OOB recv (отправляется сервером клиенту): OOB recv посылаются с объявленным открытым ключом пира, который послал пакет OOB send, и точными данными.
OOB-пакеты могут использоваться так же, как и обычные пакеты данных, однако дополнительный размер делает отправку данных через них менее эффективной, чем через пакеты данных.
Данные: Пакеты данных могут быть отправлены и получены только в том случае, если соответствующий connection_id является соединением (от него было получено уведомление Connect). Если сервер получит пакет данных для неподключенного или несуществующего соединения, он отбросит его.
Почему я использовал разные идентификаторы пакетов для всех пакетов, если некоторые из них отправляются только клиентом, а некоторые только сервером? Так меньше путаницы.

8 TCP-клиент

TCP-клиент - это клиент для TCP-сервера. Он устанавливает и поддерживает соединение с TCP-сервером.
Все форматы пакетов подробно описаны в TCP-сервере, поэтому в этом разделе будут рассмотрены только специфические детали TCP-клиента, которые не описаны в документации по TCP-серверу.
TCP клиенты могут выбирать подключение к TCP серверам через прокси. Большинство распространенных типов прокси (SOCKS, HTTP) работают путем установления соединения через прокси, используя протокол этого конкретного типа прокси. После установления соединения через этот прокси с TCP-сервером сокет ведет себя с точки зрения приложения точно так же, как TCP-сокет, который подключается непосредственно к экземпляру TCP-сервера. Это означает, что поддержка прокси очень проста.
TCP-клиент сначала устанавливает TCP-соединение, либо через прокси, либо напрямую с TCP-сервером. Он использует открытый ключ DHT в качестве долгосрочного ключа при подключении к TCP-серверу.
Он устанавливает безопасное соединение с TCP-сервером. После установления соединения с TCP-сервером и получения ответа на квитирование от TCP-сервера реализация toxcore немедленно отправляет пакет ping. В идеале первыми отправленными пакетами должны быть пакеты запроса маршрутизации, но это решение упрощает код и позволяет серверу подтвердить соединение.

Ping-пакеты, как и все остальные пакеты данных, отправляются в зашифрованном виде.
Пакеты Ping отправляются клиентом toxcore TCP каждые 30 секунд с тайм-аутом в 10 секунд, с тем же интервалом и тайм-аутом, что и пакеты ping сервера toxcore TCP. Они одинаковы, потому что выполняют одну и ту же задачу. TCP-клиент должен иметь механизм, чтобы убедиться, что важные пакеты (запросы маршрутизации, уведомления об отключении, пакеты ping, пакеты ответа ping) не будут отброшены из-за того, что TCP-сокет переполнен. Если это произойдет, TCP-клиент должен сохранить эти пакеты и расставить их по порядку, когда TCP-сокет на сервере снова станет доступен для записи. TCP-клиент также должен учитывать, что пакеты могут быть больше, чем количество байт, которое он может записать в сокет в данный момент. В этом случае он должен сохранить байты пакета, которые он не записал в сокет, и записать их в сокет, как только сокет позволит, чтобы соединение не разорвалось. Он также должен предположить, что может получить только часть зашифрованного пакета. В этом случае он должен сохранить полученную часть пакета и подождать, пока придет остальная часть пакета, прежде чем обрабатывать его. TCP-клиент можно использовать для открытия маршрута к друзьям, подключенным к TCP-серверу. Это делается путем отправки запроса маршрутизации на TCP-сервер с открытым ключом DHT друга. Это указывает серверу на необходимость зарегистрировать connection_id в открытом ключе DHT, отправленном в пакете. Затем сервер ответит пакетом ответа маршрутизации. Если соединение было принято, TCP-клиент сохранит идентификатор соединения для этого соединения. TCP-клиент убедится, что пакеты ответа маршрутизации являются ответами на отправленный им пакет маршрутизации, сохранив, что он отправил пакет маршрутизации на этот открытый ключ, и сверив ответ с ним. Это предотвращает возможность использования клиента плохим TCP-сервером.

TCP-клиент будет обрабатывать уведомления о подключении и отключении, оповещая использующий его модуль о том, что соединение с другом установлено или прервано.
TCP-клиент посылает уведомление о разрыве соединения, чтобы прервать соединение с другом. Он должен отправить пакет уведомления о разрыве соединения независимо от того, был ли друг онлайн или оффлайн, чтобы TCP-сервер снял соединение с регистрации.
Данные друзьям можно отправлять через TCP-реле с помощью пакетов OOB (out of band) и подключенных соединений. Для отправки OOB-пакета необходимо знать открытый ключ DHT друга. Пакеты OOB отправляются вслепую, и нет способа запросить TCP-реле, чтобы узнать, подключен ли друг перед отправкой. Пакеты OOB следует отправлять, когда соединение с другом через TCP-реле не находится в подключенном состоянии, но известно, что друг подключен к этому реле. Если друг подключен через TCP реле, то должны быть отправлены обычные пакеты данных, так как они меньше, чем OOB пакеты.
OOB recv и пакеты данных должны быть обработаны и переданы в модуль, использующий их.

9 TCP-соединения

TCP_connections заботится об обработке нескольких экземпляров TCP-клиентов для установления надежного соединения через TCP-реле с другом. Соединение с другом с помощью только одного реле будет не очень надежным, поэтому TCP_connections обеспечивает уровень абстракции, необходимый для управления несколькими реле. Например, он гарантирует, что если один из ретрансляторов выйдет из строя, соединение с другом не пострадает. Это достигается путем соединения с другим аналогом с помощью более чем одного реле.
TCP-соединение в TCP_connections определяется как соединение с аналогом через одно или несколько TCP-реле. Для подключения к другому пиру с помощью TCP_connections будет создано соединение в TCP_connections с пиром с открытым ключом X DHT. Некоторые TCP-реле, к которым, как мы знаем, подключен пир, будут затем связаны с этим пиром. Если peer еще не подключен напрямую, эти реле будут теми, которые peer отправил нам через onion-модуль. Равный пользователь также будет отправлять некоторые реле, с которыми он непосредственно соединен, после установления соединения, однако это делается другим модулем.
TCP_connections имеет список всех реле, к которым он подключен. Он старается держать количество реле, к которым он подключен, как можно меньше, чтобы минимизировать нагрузку на реле и снизить использование полосы пропускания для клиента. В toxcore желаемое количество соединений TCP-реле для каждого пира установлено на 3, а максимальное - на 6. Причина этих чисел в том, что 1 означает отсутствие резервных ретрансляторов, а 2 - только 1 резервный. Чтобы быть уверенным в надежности соединения, 3 кажется разумным нижним пределом. Максимальное число 6 - это максимальное количество реле, которое может быть привязано к каждому сверстнику. Если 2 пира подключены каждый к 6+ реле, и им обоим нужно подключиться к такому количеству реле из-за других друзей, то здесь вступает в игру этот максимум. Нет причин, почему это число равно 6, но в toxcore оно должно быть по крайней мере вдвое больше желаемого числа (3), потому что код предполагает это.
При необходимости TCP_connections подключается к TCP-реле, чтобы использовать их для отправки луковых пакетов. Это делается только в том случае, если нет UDP-соединения с сетью. Когда есть UDP-соединение, пакеты отправляются только с помощью UDP, потому что отправка с помощью TCP-реле может быть менее надежной. Также важно, чтобы мы постоянно были подключены к некоторым реле, поскольку эти реле будут использоваться только TCP-аналогами для инициирования соединения с нами.

В toxcore каждый клиент подключен к 3 реле, даже если нет TCP пиров и onion не нужен. Оптимальным может быть подключение к этим реле только во время инициализации toxcore, поскольку это единственное время, когда пиры будут подключаться к нам через TCP-реле, к которым мы подключены. Из-за того, как работает onion, после фазы инициализации, когда каждый пир ищет в onion, а затем, если он найден, ему посылается информация, необходимая для обратного соединения (DHT pk, TCP реле), больше не должно быть пиров, соединяющихся с нами через TCP реле. Это может быть способом дальнейшего снижения нагрузки на TCP-реле, однако перед его реализацией необходимо провести дополнительные исследования.
TCP_connections выбирает один ретранслятор и использует только его для отправки данных другому пиру. Причина отказа от выбора случайного реле для каждого пакета заключается в том, что это сильно ухудшает качество связи между двумя пирами и делает передачу видео и аудио с потерями очень плохой. По этой причине выбирается один ретранслятор, который используется для передачи всех данных. Если по какой-либо причине данные не могут быть переданы через этот ретранслятор, используется следующий ретранслятор. Это может произойти, если сокет TCP переполнен, поэтому реле не обязательно должно быть сброшено в этом случае. Реле обрываются только в случае истечения времени или если они становятся бесполезными (если реле слишком много или они больше не используются для передачи данных другим пользователям).
TCP_connections в toxcore также содержит механизм, позволяющий переводить соединения в спящий режим. TCP-соединения с другими пирами могут быть переведены в спящий режим, если соединение с пиром устанавливается с помощью UDP после того, как соединение было установлено с помощью TCP. UDP - это метод, предпочитаемый net_crypto для связи с другими пирами. Чтобы отслеживать ретрансляторы, которые использовались для связи с другим пиром в случае, если UDP-соединение не работает, они сохраняются TCP_connections, когда соединение переводится в спящий режим. Любые реле, которые использовались только этим резервным соединением, сохраняются, а затем отключаются. Если соединение пробуждается, реле снова подключаются и соединение восстанавливается. Перевод соединения в спящий режим - это то же самое, что сохранение всех реле, используемых соединением, и удаление соединения. Пробуждение соединения равносильно созданию нового соединения с теми же параметрами и восстановлению всех реле.
Следует рассмотреть метод обнаружения потенциально неработающих реле, которые пытаются нарушить работу сети, утверждая, что они соединяются с одноранговым узлом, когда это не так, или злонамеренно отбрасывают все пакеты. В настоящее время в Toxcore не реализована такая система, и ее добавление требует дополнительных исследований и, вероятно, расширения протокола.
Когда TCP-соединение подключается к ретранслятору, оно создает новый экземпляр TCP_клиента для этого ретранслятора. В любой момент, если экземпляр TCP_client сообщает, что он отключился, TCP-реле будет прервано. Как только TCP-реле сообщит, что оно подключено, TCP_connections найдет все соединения, связанные с реле, и объявит реле, что хочет подключиться к каждому из них с помощью запросов маршрутизации. Если реле сообщает, что одноранговый клиент для соединения находится в сети, номер соединения и реле будут использоваться для отправки данных в этом соединении с помощью пакетов данных. Если пир не сообщает, что пир находится онлайн, но ретранслятор связан с соединением, то для отправки данных вместо пакетов данных будут использоваться пакеты TCP OOB (out of band). Пакеты TCP OOB используются в этом случае, так как ретранслятор, скорее всего, имеет подключенный пир, но не отправил запрос на маршрутизацию для соединения с нами.
TCP_connections используется как мост между отдельными экземплярами TCP_client и net_crypto, или мост между отдельными соединениями и чем-то, что требует интерфейса, похожего на одно соединение.

10 Сетевой криптопротокол

Транспортный протокол Tox - это то, что Tox использует для установления и безопасной отправки данных друзьям. Он обеспечивает шифрование, упорядоченную доставку и совершенную секретность пересылки. Это протокол UDP, но он также используется, когда два друга соединяются через TCP-реле.
Причина, по которой протокол для соединений с друзьями через TCP-реле и прямой UDP одинаков, заключается в простоте, чтобы соединение могло переключаться между двумя протоколами без необходимости разъединения и повторного соединения. Например, два пользователя Tox могут сначала соединиться по TCP, а через несколько секунд переключиться на UDP, когда станет возможным прямое UDP-соединение. Открытие UDP-маршрута или "сквозное соединение" выполняется модулем DHT, а открытие ретранслированного TCP-соединения - модулем TCP_connection. Транспортный протокол Tox выполняет задачу безопасного соединения двух пиров (друзей Tox) после того, как будет найден маршрут или канал связи между ними. Прямой UDP предпочтительнее TCP, потому что он прямой и не ограничен возможными перегруженными TCP-реле. Кроме того, пир может соединиться с другим, используя транспортный протокол Tox, только если он знает настоящий открытый ключ и открытый ключ DHT того пира, с которым он хочет соединиться. Однако модули DHT и TCP-соединения требуют эту информацию для того, чтобы найти и открыть маршрут к пиру, что означает, что мы предполагаем, что эта информация известна toxcore и была передана net_crypto при создании соединения
net_crypto.
Поскольку этот протокол работает через UDP, он должен учитывать возможную потерю пакетов, пакеты, приходящие в неправильном порядке, и должен реализовать некоторый контроль перегрузки. Это реализовано выше уровня, на котором зашифрованы пакеты. Это не позволяет злонамеренному TCP-реле нарушить соединение, изменяя проходящие через него пакеты. Предотвращение потери пакетов позволяет очень хорошо работать с TCP-реле, которые, как мы предполагаем, могут выйти из строя в любой момент, поскольку соединение будет оставаться прочным, даже если потребуется переключиться на другое TCP-реле, что приведет к потере пакетов.
Перед отправкой фактического пакета рукопожатия одноранговый клиент должен получить cookie. Этот шаг получения cookie служит для принимающего peer'а способом подтверждения того, что peer, инициирующий соединение, может получать ответы, чтобы предотвратить некоторые типы DoS-атак.
Равный пользователь, получивший пакет запроса cookie, не должен выделять никаких ресурсов для соединения. Он просто отвечает на пакет пакетом ответа cookie, содержащим cookie, который запрашивающий пир должен затем использовать в квитировании для установления фактического соединения.
Ответ cookie должен быть отправлен обратно по той же ссылке, по которой был отправлен пакет запроса cookie. Причина этого заключается в том, что если ответ будет отправлен по другому каналу, то этот канал может не работать, и пир не будет ожидать ответа от другого канала. Например, если запрос отправлен по UDP с ip-портом X, он должен быть отправлен обратно по UDP на ip-порт X. Если запрос был получен из пакета TCP OOB, он должен быть отправлен обратно пакетом TCP OOB через тот же ретранслятор, причем получателем должен быть пир, который отправил запрос. Если он был получен из установленного соединения TCP relay, он должен быть отправлен обратно через такое же соединение.
Когда получен запрос на cookie, пир не должен использовать информацию в пакете запроса для чего-либо, он не должен хранить ее, он должен только создать cookie и ответ на него, затем отправить созданный пакет ответа на cookie и забыть их. Это делается для того, чтобы предотвратить возможные атаки. Например, если пир будет выделять долговременную память для каждого полученного пакета запроса куки, то простого переполнения пакетов будет достаточно для эффективной атаки отказа в обслуживании, заставив программу исчерпать память.

Код:
cookie request packet (145 bytes):

[uint8_t 24]
[Sender's DHT Public key (32 bytes)]
[Random nonce (24 bytes)]
[Encrypted message containing:
    [Sender's real public key (32 bytes)]
    [padding (32 bytes)]
    [uint64_t echo id (must be sent back untouched in cookie response)]
]

Зашифрованное сообщение шифруется с помощью закрытого ключа DHT отправителя, открытого ключа DHT получателя и ключа nonce.
Идентификатор пакета для пакетов запроса cookie - 24. Запрос содержит открытый ключ DHT отправителя, который является ключом, используемым (закрытый ключ DHT) (вместе с открытым ключом DHT получателя) для шифрования зашифрованной части пакета cookie, и nonce, также используемый для шифрования зашифрованной части пакета. Вставка используется для поддержания обратной совместимости с предыдущими версиями протокола. Идентификатор echo id в запросе cookie должен быть отправлен обратно нетронутым в ответе cookie. Этот идентификатор эха - это способ, с помощью которого отправляющий запрос пир может быть уверен, что полученный ответ был ответом на отправленный им пакет.
Причина отправки открытого ключа DHT и реального открытого ключа в запросе cookie в том, что оба они содержатся в cookie, отправленном обратно в ответе.
В настоящее время Toxcore посылает 1 пакет запроса cookie каждую секунду 8 раз перед тем, как разорвать соединение, если нет ответов.

Код:
cookie response packet (161 bytes):

[uint8_t 25]
[Random nonce (24 bytes)]
[Encrypted message containing:
    [Cookie]
    [uint64_t echo id (that was sent in the request)]
]

Зашифрованное сообщение шифруется тем же симметричным ключом, что и пакет запроса cookie, на который оно отвечает, но с другим nonce.
Идентификатор пакета для пакетов запроса cookie - 25. Ответ содержит nonce и зашифрованную часть, зашифрованную с помощью nonce. Зашифрованная часть шифруется тем же ключом, который используется для расшифровки зашифрованной части запроса, что означает, что дорогостоящая генерация общего ключа должна быть вызвана только один раз для обработки и ответа на пакет запроса cookie с ответом cookie.
Содержимым зашифрованной части является Cookie (см. ниже) и идентификатор echo, который был отправлен в запросе.

Код:
The Cookie should be (112 bytes):

[nonce]
[encrypted data:
    [uint64_t time]
    [Sender's real public key (32 bytes)]
    [Sender's DHT public key (32 bytes)]
]

Cookie - это 112-байтовый фрагмент данных, который создается и отправляется запрашивающему пользователю как часть пакета ответа cookie. Пир, который хочет подключиться к другому, должен получить пакет cookie от того пира, к которому он пытается подключиться. Единственный способ отправить корректный пакет рукопожатия другому пиру - это сначала получить от него cookie.
Cookie содержит информацию, которая докажет получателю рукопожатия, что пир получил ответ cookie, и содержит зашифрованную информацию, которая сообщит получателю пакета рукопожатия достаточно информации для расшифровки, проверки пакета рукопожатия и принятия соединения.
Когда запускается toxcore, он генерирует симметричный ключ шифрования, который он использует для шифрования и расшифровки всех пакетов cookie (используя NaCl аутентифицированное шифрование точно так же, как и шифрование везде в toxcore). Только экземпляр toxcore, создающий пакеты, знает ключ шифрования, что означает, что все cookie, которые он успешно расшифровывает и проверяет, были созданы им.
Переменная времени в cookie используется для предотвращения использования слишком старых пакетов cookie. В Toxcore установлен тайм-аут в 15 секунд для пакетов cookie. Если пакет cookie используется более чем через 15 секунд после его создания, Toxcore будет считать его недействительным.
При ответе на пакет запроса cookie реальный открытый ключ отправителя - это известный ключ, отправленный peer'ом в зашифрованной части пакета запроса cookie, а открытый ключ DHT отправителя - это ключ, используемый для шифрования зашифрованной части пакета запроса cookie.
При генерации cookie для помещения в зашифрованную часть рукопожатия: Одним из требований для успешного подключения к другому человеку является то, что мы знаем его открытый ключ DHT и его реальный долгосрочный открытый ключ, что означает наличие достаточной информации для создания cookie.


Пакет рукопожатия:

Код:
[uint8_t 26]
[Cookie]
[nonce (24 bytes)]
[Encrypted message containing:
    [24 bytes base nonce]
    [session public key of the peer (32 bytes)]
    [sha512 hash of the entire Cookie sitting outside the encrypted part]
    [Other Cookie (used by the other to respond to the handshake packet)]
]

ID пакета для пакетов рукопожатия - 26. Cookie - это cookie-файл, полученный путем отправки сверстнику пакета запроса cookie и получения ответного пакета с cookie-файлом в нем. Он также может быть получен в пакете квитирования при получении сверстником пакета квитирования (Other Cookie).
Nonce - это число, используемое для шифрования зашифрованной части пакета рукопожатия. Зашифрованная часть пакета рукопожатия шифруется с помощью долговременных ключей обоих пиров. Это делается для предотвращения выдачи себя за другого.
Внутри зашифрованной части пакета рукопожатия находится " base nonce" и открытый ключ сессии. Base nonce" - это значение, которое другая сторона должна использовать для шифрования каждого пакета данных, добавляя + 1 к нему для каждого отправленного пакета данных. (первый пакет - 'base nonce' + 0, следующий - 'base nonce' + 1 и т.д.). Обратите внимание, что для математических операций nonce рассматривается как 24-байтовое число в формате big endian). Сеансовый ключ - это временный открытый ключ соединения, который пир сгенерировал для данного соединения и отправляет другому пиру. Этот сеансовый ключ используется для того, чтобы соединение имело идеальную прямую секретность. Важно сохранить аналог закрытого ключа открытого ключа сеанса, отправленного во время рукопожатия, открытый ключ, полученный другой стороной, и оба полученных и отправленных базовых несов, поскольку они используются для шифрования/дешифрования пакетов данных.
Хеш cookie в зашифрованной части используется для того, чтобы убедиться, что злоумышленник не взял более старый действительный пакет рукопожатия и не заменил пакет cookie внутри более новым, что было бы плохо, так как он мог бы воспроизвести его и, возможно, устроить беспорядок.
Other Cookie" - это действительный cookie, который мы помещаем в рукопожатие, чтобы другая сторона могла ответить действительным рукопожатием без необходимости делать запрос на получение cookie.
Пакет рукопожатия отправляется обеими сторонами соединения. Если одноранговый компьютер получает рукопожатие, он проверяет, действителен ли cookie, расшифровывается ли зашифрованная часть и подтверждается ли она, действителен ли хэш cookie, принадлежит ли долгосрочный открытый ключ известному другу. Если все эти данные верны, то соединение считается "Принятым", но не "Подтвержденным".
Если нет существующего соединения с пиром, идентифицированным долгосрочным открытым ключом, для которого нужно установить статус 'Принято', то будет создано соединение с этим статусом. Если соединение с таким пиром со статусом 'Принято' еще не существует, это соединение будет установлено как принятое. Если для данного пира существует соединение со статусом 'Подтвержден', пакет рукопожатия будет проигнорирован и отброшен (Причина отбрасывания заключается в том, что мы не хотим, чтобы несколько запоздалые пакеты рукопожатия убили соединение), за исключением случаев, когда открытый ключ DHT в cookie, содержащемся в пакете рукопожатия, отличается от известного открытого ключа DHT пира. В этом случае соединение будет немедленно уничтожено, поскольку это означает, что оно больше не действительно, и новое соединение будет немедленно создано со статусом 'Принято'.
Иногда toxcore может получить открытый ключ DHT пира сначала с пакетом рукопожатия, поэтому важно, чтобы этот случай был обработан и чтобы реализация передавала открытый ключ DHT другим модулям (DHT, TCP_connection, потому что такое случается.
Пакеты Handshake должны создаваться только один раз во время соединения, но должны отправляться с интервалами, пока мы не убедимся, что другой модуль их получил. Это происходит, когда получен и расшифрован правильный зашифрованный пакет данных.

Состояния соединения:

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

Toxcore посылает квитирующие пакеты каждую секунду 8 раз и прерывает соединение, если соединение не подтверждается (не получен зашифрованный пакет) в течение этого времени.

Идеальный сценарий рукопожатия:

Посмотреть вложение 54377


Причина, по которой рукопожатие происходит именно таким образом, заключается в определенных требованиях к конструкции:

Рукопожатие не должно передавать долгосрочные открытые ключи пиров возможному злоумышленнику, который будет просматривать пакеты, но каждый пир должен точно знать, что он соединяется с правильным пиром, а не с самозванцем.
Соединение должно быть установлено, если только один из пиров имеет информацию, необходимую для инициирования соединения (открытый ключ DHT пира и ссылка на пир).
Если оба пира инициируют соединение друг с другом одновременно, соединение должно быть успешным и без проблем.
Должна быть обеспечена идеальная передовая секретность.
Должна быть устойчива к любым возможным атакам.

В связи с тем, как он спроектирован, только одно соединение возможно одновременно между двумя сверстниками.

Зашифрованные пакеты:

LengthContents
1uint8_t (0x1b)
2uint16_t The last 2 bytes of the nonce used to encrypt this
variable Payload

Полезная нагрузка шифруется ключом сеанса и "базовым нецелем", заданным получателем в квитировании + номер пакета (начиная с 0, большая эндинарная математика).
Идентификатор пакета для зашифрованных пакетов равен 27. Зашифрованные пакеты - это пакеты, используемые для отправки данных другому пирингу в соединении. Поскольку эти пакеты могут быть отправлены по UDP, реализация должна предполагать, что они могут прийти не по порядку или даже не прийти вообще.
Чтобы получить ключ, используемый для шифрования/дешифрования каждого пакета в соединении, пир берет открытый ключ сессии, полученный во время рукопожатия, и аналог закрытого ключа, который он отправил во время рукопожатия, и генерирует из них общий ключ. Этот общий ключ будет идентичным для обоих пиров. Важно отметить, что ключи соединения должны быть стерты при разрыве соединения.
Чтобы создать зашифрованный пакет для отправки другому пиру, данные шифруются с помощью общего ключа для этого соединения и базового nonce, который другой пир отправил в пакете рукопожатия, с добавлением к нему общего количества зашифрованных пакетов, отправленных в соединении ('base nonce' + 0 для первого отправленного зашифрованного пакета данных, 'base nonce' + 1 для второго и т.д.). Обратите внимание, что для математических операций, таких как сложение, nonce рассматривается как большое конечное число). 2-байтовое число (uint16_t) в начале зашифрованного пакета - это последние 2 байта этого 24-байтового nonce.
Чтобы расшифровать полученный зашифрованный пакет, вычисляется nonce, которым был зашифрован пакет, используя базовый nonce, который отправил друг другу одноранговый клиент, и 2-байтовое число в начале пакета. Сначала мы предполагаем, что пакеты, скорее всего, будут приходить не по порядку и некоторые из них будут потеряны, но потери пакетов и отсутствие порядка никогда не будут достаточными для того, чтобы номеру в 2 байта понадобился дополнительный байт. Пакет расшифровывается с использованием общего ключа для соединения и вычисленного nonce.
Toxcore использует следующий метод для вычисления nonce для каждого пакета:

diff = (2 байта номера на пакете) - (последние 2 байта текущего сохраненного базового nonce) ПРИМЕЧАНИЕ: рассматривайте эти 3 переменные как 16-битные беззнаковые числа, ожидается, что результат будет иногда переворачиваться.

скопируйте saved_base_nonce в temp_nonce.
temp_nonce = temp_nonce + diff. temp_nonce - правильный nonce, который может быть использован для расшифровки пакета.
DATA_NUM_THRESHOLD = (1/3 от максимального числа, которое может быть сохранено в беззнаковом 2-битовом целочисленном числе).
если расшифровка прошла успешно и diff > (DATA_NUM_THRESHOLD * 2), то:
saved_base_nonce = saved_base_nonce + DATA_NUM_THRESHOLD

Сначала берется разница между 2 байтами номера в пакете и последним. Поскольку 3 значения являются беззнаковыми 16-битными интами, а перенос является частью математики, что-то вроде diff = (10 - 65536) означает, что diff равно 11.

Затем он копирует сохраненный базовый nonce в буфер временного nonce.
Затем он добавляет diff к nonce (nonce находится в формате big endian).
После успешной расшифровки проверяется, было ли diff больше, чем 2/3 значения, которое может содержаться в 16-битном беззнаковом int, и в случае успеха увеличивает сохраненный базовый nonce на 1/3 от максимального значения.
Это только один из многих способов, которыми может быть вычислен nonce для каждого зашифрованного пакета.
Зашифрованные пакеты, которые не могут быть расшифрованы, просто отбрасываются.
Причина обмена базовыми несами заключается в том, что поскольку ключ для шифрования пакетов одинаков для полученных и отправленных пакетов, должен существовать криптографический способ сделать невозможной атаку, в результате которой кто-то сможет воспроизвести пакеты обратно отправителю, а отправитель будет думать, что эти пакеты пришли от другого аналога.
Данные в зашифрованных пакетах:

[our recvbuffers buffer_start, (highest packet number handled + 1), (big endian)]
[uint32_t packet number if lossless, sendbuffer buffer_end if lossy, (big endian)]
[data]

Зашифрованные пакеты могут быть с потерями или без потерь. Пакеты с потерями - это просто зашифрованные пакеты, которые отправляются друг другу. Если они будут потеряны, придут в неправильном порядке или даже если злоумышленник продублирует их (обязательно учитывайте это для всего, что использует пакеты с потерями), они просто будут расшифрованы по мере поступления и переданы тому, кто должен их обрабатывать, в зависимости от идентификатора данных.
Пакеты без потерь - это пакеты, содержащие данные, которые будут доставлены по порядку в соответствии с реализацией протокола. В этом протоколе получатель сообщает отправителю, какие номера пакетов он получил, а какие нет, и отправитель должен повторно отправить все пакеты, которые были потеряны. Любая попытка удвоения пакетов приведет к тому, что все (кроме первого полученного) будут проигнорированы.
Каждый пакет без потерь содержит как 4-байтовый номер, указывающий на самый старший номер полученного и обработанного пакета, так и 4-байтовый номер пакета, который является номером данных в пакете.
В пакетах с потерями схема такая же, только вместо номера пакета второй 4-байтовый номер представляет собой номер пакета без потерь, если бы он был отправлен сразу после него. Это число используется приемником, чтобы узнать, были ли потеряны какие-либо пакеты. (например, если он получает 4 пакета с номерами (0, 1, 2, 5), а затем позже пакет с потерями с вторым номером: 8, он знает, что пакеты: 3, 4, 6, 7 были потеряны и запросит их).
Как достигается надежность:
Прежде всего, важно сказать, что номера пакетов переносятся, следующее число после 0xFFFFFFFF (максимальное значение в 4 байтах) - 0. Следовательно, все математические операции с номерами пакетов предполагаются только для беззнаковых 32-битных целых чисел, если не сказано иначе. Например, 0 - 0xFFFFFFFF будет равно 1 из-за опрокидывания.
При отправке пакета без потерь создается пакет, номер пакета которого равен номеру последнего созданного пакета без потерь + 1 (начиная с 0). Номера пакетов используются как для надежности, так и для упорядоченной доставки, поэтому они должны быть последовательными.
Затем пакет сохраняется вместе с номером пакета, чтобы пир мог отправить его снова, если получатель не получит его. Пакеты удаляются из хранилища только тогда, когда получатель подтверждает, что получил их.
Приемник получает пакеты и сохраняет их вместе с номером пакета. Когда приемник получает пакет, он сохраняет его вместе с номером пакета в массиве. Если в буфере уже есть пакет с таким номером, пакет отбрасывается. Если номер пакета меньше, чем номер последнего обработанного пакета, пакет отбрасывается. Обработанный пакет означает, что он был извлечен из буфера и передан вверх соответствующему модулю.
Предполагая новое соединение, отправитель посылает 5 пакетов без потерь получателю: 0, 1, 2, 3, 4 - это номера отправленных пакетов, а получатель получает: 3, 2, 0, 2 в таком порядке.
Приемник сохраняет пакеты и отбрасывает второй пакет с номером 2, у него есть: 0, 2, 3 в его буфере. Он передаст первый пакет соответствующему модулю и удалит его из массива, но поскольку пакет с номером 1 отсутствует, он остановится на этом. Содержимое буфера теперь: 2, 3. Получатель знает, что пакет номер 1 отсутствует, и запросит его у отправителя с помощью пакета запроса пакетов:

идентификаторы данных:

ID Data
0padding (skipped until we hit a non zero (data id) byte)
1packet request packet (lossy packet)
2connection kill packet (lossy packet)
......
16+reserved for Messenger usage (lossless packets)
192+reserved for Messenger usage (lossy packets)
255reserved for Messenger usage (lossless packet)

Пакеты отключения соединения сообщают другому, что соединение разорвано.
Номера пакетов — это первый байт данных в пакете.
пакет запроса пакета

[uint8_t (1)][uint8_t num][uint8_t num][uint8_t num]...[uint8_t num]

Пакеты запроса пакетов используются одной стороной соединения для запроса пакетов у другой. Чтобы создать полный пакет запроса пакетов, запрашивающий берет номер последнего пакета, который был обработан (отправлен в соответствующий модуль и удален из массива (0 в примере выше)). Из этого номера вычитается номер первого пропущенного пакета (1 - 0) = 1. Это означает, что полный пакет для запроса пакета с номером 1 будет выглядеть так:



[uint32_t 1]

[uint32_t 0]

[uint8_t 1][uint8_t 1].



Если запрашивался также пакет номер 4, возьмем разницу между номером пакета и номером последнего запрашиваемого пакета (4 - 1) = 3. Таким образом, пакет будет выглядеть следующим образом:



[uint32_t 1]

[uint32_t 0]

[uint8_t 1][uint8_t 1][uint8_t 3].



Но что если число больше 255? Допустим, пиру необходимо запросить пакеты 3, 6, 1024, пакет будет выглядеть следующим образом:



[uint32_t 1]

[uint32_t 2]

[uint8_t 1][uint8_t 3][uint8_t 3][uint8_t 0][uint8_t 0][uint8_t 0][uint8_t 0][uint8_t 253].



Каждый 0 в пакете представляет собой прибавление 255 до тех пор, пока не будет достигнут байт, не содержащий 0, который затем прибавляется, и в результате получается запрашиваемое число.
Этот запрос разработан для того, чтобы быть небольшим при запросе пакетов в реальных сетевых условиях, где запрашиваемые номера пакетов будут близки друг к другу. Поместить каждый запрашиваемый номер пакета в 4 байта было бы очень просто, но это сделало бы пакеты запроса неоправданно большими, поэтому пакеты выглядят следующим образом.
Когда пакет запроса получен, он будет декодирован, а все пакеты, находящиеся между запрошенными пакетами, будут считаться успешно полученными другой стороной.
Пакеты запроса пакетов отправляются по крайней мере каждые 1 секунду в toxcore и чаще, когда пакеты принимаются.
В настоящее время используется следующая формула (обратите внимание, что эта формула, скорее всего, неоптимальна):

REQUEST_PACKETS_COMPARE_CONSTANT = 50.0 double request_packet_interval =.

(REQUEST_PACKETS_COMPARE_CONSTANT /

(((double)num_packets_array(&conn->recv_array) + 1.0) / (conn->packet_recv_rate

+ 1.0)));

num_packets_array(&conn->recv_array) возвращает разницу между наибольшим номером полученного пакета и последним обработанным. В коде toxcore она относится к общему размеру текущего массива (с дырами, которые являются заполнителями для еще не полученных пакетов, которые заведомо отсутствуют).
conn->packet_recv_rate - количество успешно принятых пакетов данных в секунду.

Эта формула была создана с учетом логики, что чем выше "задержка" в пакетах (num_packets_array(&conn->recv_array)) по сравнению со скоростью получения пакетов, тем больше пакетов запроса должно быть отправлено.
Запрошенные пакеты отправляются каждый раз, когда они могут быть отправлены, т.е. они будут подчиняться контролю перегрузки и не обходить его. Они отправляются один раз, последующие пакеты запроса будут использоваться, чтобы узнать, был ли пакет получен или его следует отправить повторно.
Пинг или rtt (round trip time) между двумя пирами можно рассчитать, сохранив время отправки каждого пакета и взяв разницу между временем отправки последнего пакета, подтвержденного пакетом запроса, и временем получения пакета запроса. rtt может быть рассчитан для каждого пакета запроса. Наименьший из них (для всех пакетов) будет наиболее близок к реальному пингу.
Этот ping или rtt может быть использован для того, чтобы узнать, следует ли повторно отправить пакет запроса, который запрашивает только что отправленный пакет, или нужно подождать следующего (чтобы узнать, успела ли другая сторона получить пакет).
Алгоритм управления перегрузкой имеет цель определить, сколько пакетов может быть отправлено по каналу связи каждую секунду, прежде чем ни один из них больше не будет отправлен. Принцип его работы заключается в том, чтобы отправлять пакеты все быстрее и быстрее, пока ни один из них не сможет пройти через канал, а затем прекратить отправлять их быстрее.
В настоящее время управление перегрузками использует следующую формулу в toxcore, однако это, вероятно, не самый лучший способ.
Текущая формула состоит в том, чтобы взять разницу между текущим размером очереди отправки и размером очереди отправки 1,2 секунды назад, взять общее количество пакетов, отправленных за последние 1,2 секунды, и вычесть из него предыдущее число.
Затем разделите это число на 1,2, чтобы получить скорость передачи пакетов в секунду. Если эта скорость меньше минимальной скорости отправки 8 пакетов в секунду, установите значение 8.
Событие перегрузки можно определить как событие, когда количество запрошенных пакетов превышает количество пакетов, которые, по мнению системы управления перегрузками, могут быть отправлены в данном кадре. Если событие перегрузки произошло в течение последних 2 секунд, скорость отправки пакетов соединения устанавливается равной скорости отправки, рассчитанной ранее, если нет, она устанавливается равной этой скорости отправки, умноженной на 1,25, чтобы увеличить скорость.
Как я уже сказал, это не идеально, и, вероятно, можно найти лучшее решение или подкорректировать цифры.
Для устранения возможной проблемы, когда невозможно отправить данные с низкой пропускной способностью, такие как текстовые сообщения, при отправке данных с высокой пропускной способностью, таких как файлы, можно сделать так, чтобы приоритетные пакеты полностью игнорировали контроль перегрузки, помещая их в очередь пакетов отправки и отправляя их, даже если контроль перегрузки говорит не делать этого. Это используется в toxcore для всех пакетов, не связанных с передачей файлов, чтобы предотвратить передачу файлов, мешающую отправке обычных пакетов сообщений.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх