You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
126 lines
19 KiB
126 lines
19 KiB
Формат кодограммы: |
|
------------------ |
|
пакеты идут по следующему пути: |
|
пакет кем-то помещается в входную очередь (ETCP input_queue). |
|
Когда load balancer готов отправить следующий пакет он делает вызов etcp_request_pkt в etcp.c/h и etcp либо формирует следующую кодограмму или встаёт в состояние async ожидания (возвращая null): |
|
- либо wait ETCP input_queue (если нечего передавать) |
|
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия". |
|
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку. |
|
как формируется пакет: |
|
1. берем пакет из списка неподтвержденных пакетов ожидающих отправку |
|
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем |
|
если пакет найден: |
|
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало |
|
2. в конец добавляем секцию 0x00 с данными пакета |
|
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен |
|
Функция прикрепления опциональных секций: |
|
- запрашивает выбор канала передачи. |
|
- добавляет накопившиеся ACK |
|
При приёме пакета (от etcp_connections): |
|
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам: |
|
- ack: помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также и ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL |
|
- timestamp: обновляем last RTT, пересчитываем RTT10,RTT100 (плавающим окном за последние 10/100 пакетов), и jitter=max(last 10)-min(last 10 packets) |
|
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). |
|
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть. |
|
|
|
пересканирование inflight: |
|
- вызывается по таймеру, таймер заводится при добавлении пакета в пустой inflight. |
|
- если время последней отправки > RTT10*k1+jitter*k2 (коэффициенты вынести в настройки utun instance) то пакет заново отправляется |
|
и заводим таймер отправки следующей ретрансмиссии (список отсортирован по времени) |
|
- если встречаем успешный ack, запоминаем это и его timestamp (назовем lp_ts). |
|
- если после этого далее в очереди находим пакет который не имеет переповторов и timestamp отправки раньше чем [ИСПРАВЛЕНО: фраза обрезана в оригинале; предположительно, "раньше чем lp_ts" на основе контекста; если нет, пометить как неполное] |
|
|
|
inflight - два списка (ll_queue): |
|
- список с неподтвержденными пакетами ожидающими ack (при запросе ретрансмиссии пакет перемещается в ожидающие отправку) |
|
- список с неподтвержденными пакетами ожидающими отправку (при отправке возвращается в ожидающие ack) |
|
доп. свойства пакетов: |
|
- указатель на ETC_LINK последней отправки |
|
- timestamp последней отправки |
|
- число отправок |
|
- число запросов переповтора |
|
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight. |
|
optimal_inflight расчитывается из RTT и bandwidth линка. |
|
|
|
bandwidth по каждому линку адаптивно подстраиваем следующим алгоритмом: |
|
периодически инициируем burst_transmission (напоминаем ID пакета на которой она началась). |
|
после передаём пачку пакетов только по этому линку со следующими таймингами: |
|
<pkt1> <delay 4x> <pkt2><pkt3><pkt4><pkt5> - т.е. чуть задерживаем канал а потом выплёвываем пачку пакетов. |
|
и добавляем опцию [meas_ts][n] - где n - номер пакета в пачке |
|
приёмная сторона видя эту секцию должна отправить секцию [meas_resp] по всем пакетам, которые содержат локальный RECV_Timestamp фиксирующий время приёма каждого пакета с [meas_ts]. |
|
приняв эти response анализируем по разнице между pkt1-pkt2 не забита ли очередь отправки, а по дельте pkt2-3-4-5 оцениваем bandwidth. |
|
дальше по алгоритму (который потом оттюним) корректируем transmitter speed limit. |
|
|
|
|
|
**** Формат кодограмм для etcp.c/h **** |
|
Каждая кодограмма состоит из обязательного заголовка и опциональных секций: |
|
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах: |
|
[Timestamp high][Timestamp low][flags] |
|
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется |
|
- flags: bit0 = up/down (recv_keepalive) |
|
|
|
шифруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ |
|
2. Опциональные секции (одна или несколько) добавляются в etcp.c: |
|
а) Подтверждения (ACK) - заголовок 0x01: |
|
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo] |
|
- count: количество пар ID+timestamp (1-32) |
|
- last_delivered_id: последний ID, доставленный получателю |
|
- last_rx_id: последний полученный ID (для синхронизации прогресса) |
|
в) channel timestamp (добавляется после выбора канала передачи): |
|
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low] |
|
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно |
|
- RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно). |
|
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами] |
|
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя): |
|
[0x00] [ID 4 байта] [данные...] - Данные пакета |
|
- ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit] |
|
Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?] |
|
цель протокола: |
|
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT) |
|
Установка подключения для канала (реализуется в etcp_connections.c/h): |
|
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи). |
|
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно): |
|
- RTT последнего пакета и время отправки последнего пакета |
|
- пакеты делятся на 3 категории: <300 байт, 300-699 байт, 700+ байт. для каждой категории отдельно считаются метрики: |
|
- количество отправленных в пакетах байтах |
|
- количество перезапросов (по каждому каналу отдельно) в пакетах и байтах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики. |
|
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент |
|
Планировщик/балансир каналов (etcp_loadbalancer.c/h): |
|
для каждого канала ограничение полосы с тестированием отклика: |
|
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков) |
|
Ограничение реализуем следующим образом: |
|
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности (т.е. было ненулевое время передачи одного байта). |
|
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время) |
|
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); [СПОРНО: сложность с float; риск precision issues] |
|
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения. |
|
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client. |
|
процесс установки соединения: |
|
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK) |
|
- по таймауту повторяем init запросы, ведем счетчик запросов init. |
|
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1. |
|
При добавлении нового канала посылаем INIT 0x04 (без сброса), а при первичной инициализации подключения - 0x02 (это нужно, т.к. например перезапуск локального софта должен инициировать перезапуск удаленного соединения). |
|
|
|
**** Формат кодограмм для etcp_connections.c/h **** |
|
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: [СПОРНО: заголовок в etcp — только 2 байта TS; здесь подразумевается ID? Уточнить единый формат] |
|
1) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (без сброса): |
|
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mtu low] [keepalive high] [keepalive low] [recovery high] [recovery low] [my link_id 1 байт] [my public key (64 байта, не шифруется)] |
|
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы. |
|
- link_id: локальный идентификатор канала (0-255), назначается отправителем для идентификации канала |
|
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета (т.к. инициатор соединения всегда имеет оригинальный peer public key в конфиге - по нему исключаем MITM) |
|
2) Init подтверждение - заголовок 0x03/0x05 (ответы соответственно на коды 0x02 и 0x04): |
|
[0x03/0x05] [my_node_id 64bit] [my mtu high] [my mtu low] [my link_id 1 байт] [peer ipv4 4 байта] [peer port 2 байта] |
|
- Подтверждение инициализации (канал успешно создан, можно начинать обмен) |
|
- link_id: локальный идентификатор канала (0-255) отправителя ответа |
|
- peer ip.port: возвращаем ip:port с которого пришел init запрос (чтобы клиент узнал свой external ip:port если за nat) |
|
При получении init получатель пакета должен: |
|
- reset ETCP_LINK с этим ip_port если он есть |
|
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации) |
|
приём кодограмм из UDP выглядит так: |
|
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue |
|
|
|
Keepalive: пакет без секций (только timestamp+flags) |
|
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика. |
|
|
|
Клиент: если все линки =down то начинается процедура восстановления связи: |
|
- клиент посылает init без сброса |
|
- сервер как получает init без сброса отправляет init подтверждение: со сбросом или без сброса в зависимости от состояния init его соединения (инициализировано - то без сброса). |
|
|
|
|