Этот перевод был создан с помощью машинного обучения и может быть не на 100% точным. Просмотреть английскую версию

Протокол Garlic Farm

Proposal 150
Open
Author zzz
Created 2019-05-02
Last Updated 2019-05-20

Обзор

Это спецификация протокола Garlic Farm, основанная на JRaft, его коде “exts” для реализации поверх TCP и примере приложения “dmprinter” JRAFT .

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

Это будет бэкенд для координации маршрутизаторов, публикующих записи в Meta LeaseSet. См. предложение 123.

Цели

  • Малый размер кода
  • Основан на существующей реализации
  • Без сериализованных Java-объектов или любых Java-специфичных функций или кодировок
  • Любая загрузка (bootstrapping) находится за рамками спецификации. Предполагается, что хотя бы один другой сервер зашит в код или настроен вне протокола
  • Поддержка как внеполосных, так и внутри-I2P сценариев использования

Дизайн

Протокол Raft не является конкретным протоколом; он определяет лишь машину состояний. Поэтому мы документируем конкретный протокол JRaft и основываем наш протокол на нём. Единственное изменение по сравнению с протоколом JRaft — добавление рукопожатия с аутентификацией.

Raft избирает Лидера, задача которого — публиковать журнал (log). Журнал содержит данные конфигурации Raft и прикладные данные. Прикладные данные содержат статус маршрутизатора каждого сервера и Destination для кластера Meta LS2. Серверы используют общий алгоритм для определения издателя и содержимого Meta LS2. Издатель Meta LS2 НЕ обязательно является Лидером Raft.

Спецификация

Сетевой протокол работает поверх SSL-сокетов или не-SSL I2P-сокетов. I2P-сокеты проксируются через HTTP-прокси. Поддержка нешифрованных clearnet-сокетов отсутствует.

Рукопожатие и аутентификация

Не определено в JRaft.

Цели:

  • Метод аутентификации по логину/паролю
  • Идентификатор версии
  • Идентификатор кластера
  • Расширяемость
  • Простота проксирования при использовании I2P-сокетов
  • Не раскрывать сервер как Garlic Farm сервер без необходимости
  • Простой протокол, чтобы не требовалась полная реализация веб-сервера
  • Совместимость с общепринятыми стандартами, чтобы реализации могли использовать стандартные библиотеки при желании

Мы будем использовать рукопожатие, похожее на WebSocket, и аутентификацию HTTP Digest RFC 2617 . HTTP Basic аутентификация по RFC 2617 НЕ поддерживается. При проксировании через HTTP-прокси общение с прокси происходит согласно RFC 2616 .

Учётные данные

Являются ли имена пользователей и пароли привязанными к кластеру или к серверу — зависит от реализации.

HTTP Запрос 1

Инициатор отправляет следующее.

Все строки завершаются CRLF, как требуется HTTP.


GET /GarlicFarm/CLUSTER/VERSION/websocket HTTP/1.1
  Host: (ip):(port)
  Cache-Control: no-cache
  Connection: close
  (любые другие заголовки игнорируются)
  (пустая строка)

  CLUSTER — имя кластера (по умолчанию "farm")
  VERSION — версия Garlic Farm (сейчас "1")

HTTP Ответ 1

Если путь неверен, получатель отправит стандартный ответ “HTTP/1.1 404 Not Found”, как в RFC 2616 .

Если путь верен, получатель отправит стандартный ответ “HTTP/1.1 401 Unauthorized”, включая заголовок WWW-Authenticate с аутентификацией по HTTP Digest, как в RFC 2617 .

После этого обе стороны закрывают сокет.

HTTP Запрос 2

Инициатор отправляет следующее, как в RFC 2617 .

Все строки завершаются CRLF, как требуется HTTP.


GET /GarlicFarm/CLUSTER/VERSION/websocket HTTP/1.1
  Host: (ip):(port)
  Cache-Control: no-cache
  Connection: keep-alive, Upgrade
  Upgrade: websocket
  (Sec-Websocket-* заголовки, если проксируется)
  Authorization: (заголовок авторизации HTTP Digest, как в RFC 2617)
  (любые другие заголовки игнорируются)
  (пустая строка)

  CLUSTER — имя кластера (по умолчанию "farm")
  VERSION — версия Garlic Farm (сейчас "1")

HTTP Ответ 2

Если аутентификация неверна, получатель отправит ещё один стандартный ответ “HTTP/1.1 401 Unauthorized”, как в RFC 2617 .

Если аутентификация верна, получатель отправит следующий ответ, как в протоколе WebSocket.

Все строки завершаются CRLF, как требуется HTTP.


HTTP/1.1 101 Switching Protocols
  Connection: Upgrade
  Upgrade: websocket
  (Sec-Websocket-* заголовки)
  (любые другие заголовки игнорируются)
  (пустая строка)

После получения этого ответа сокет остаётся открытым. Начинается протокол Raft, определённый ниже, на том же сокете.

Кэширование

Учётные данные должны кэшироваться как минимум на один час, чтобы последующие подключения могли сразу переходить к “HTTP Запросу 2” выше.

Типы сообщений

Существует два типа сообщений: запросы и ответы. Запросы могут содержать записи журнала (Log Entries) и имеют переменный размер; ответы не содержат записей журнала и имеют фиксированный размер.

Типы сообщений 1–4 — стандартные RPC-сообщения, определённые в Raft. Это основа протокола Raft.

Типы сообщений 5–15 — расширенные RPC-сообщения, определённые в JRaft, для поддержки клиентов, динамического изменения серверов и эффективной синхронизации журнала.

Типы сообщений 16–17 — сообщения RPC для компрессии журнала, определённые в разделе 7 Raft.

СообщениеНомерОтправительПолучательПримечания
RequestVoteRequest1КандидатПоследовательСтандартный RPC Raft; не должен содержать записи журнала
RequestVoteResponse2ПоследовательКандидатСтандартный RPC Raft
AppendEntriesRequest3ЛидерПоследовательСтандартный RPC Raft
AppendEntriesResponse4ПоследовательЛидер / КлиентСтандартный RPC Raft
ClientRequest5КлиентЛидер / ПоследовательОтвет — AppendEntriesResponse; должен содержать только прикладные записи журнала
AddServerRequest6КлиентЛидерДолжен содержать только одну запись журнала ClusterServer
AddServerResponse7ЛидерКлиентЛидер также отправит JoinClusterRequest
RemoveServerRequest8ПоследовательЛидерДолжен содержать только одну запись журнала ClusterServer
RemoveServerResponse9ЛидерПоследователь
SyncLogRequest10ЛидерПоследовательДолжен содержать только одну запись журнала LogPack
SyncLogResponse11ПоследовательЛидер
JoinClusterRequest12ЛидерНовый серверПриглашение присоединиться; должен содержать только одну запись журнала Configuration
JoinClusterResponse13Новый серверЛидер
LeaveClusterRequest14ЛидерПоследовательКоманда покинуть кластер
LeaveClusterResponse15ПоследовательЛидер
InstallSnapshotRequest16ЛидерПоследовательРаздел 7 Raft; должен содержать только одну запись журнала SnapshotSyncRequest
InstallSnapshotResponse17ПоследовательЛидерРаздел 7 Raft

Установление соединения

После HTTP-рукопожатия последовательность установления следующая:


Новый сервер Alice              Случайный последователь Bob

  ClientRequest   ------->
          <---------   AppendEntriesResponse

  Если Bob сообщает, что он лидер, продолжить ниже.
  Иначе Alice должна отключиться от Bob и подключиться к лидеру.


  Новый сервер Alice              Лидер Charlie

  ClientRequest   ------->
          <---------   AppendEntriesResponse
  AddServerRequest   ------->
          <---------   AddServerResponse
          <---------   JoinClusterRequest
  JoinClusterResponse  ------->
          <---------   SyncLogRequest
                       OR InstallSnapshotRequest
  SyncLogResponse  ------->
  OR InstallSnapshotResponse

Последовательность отключения:


Последователь Alice              Лидер Charlie

  RemoveServerRequest   ------->
          <---------   RemoveServerResponse
          <---------   LeaveClusterRequest
  LeaveClusterResponse  ------->

Последовательность выборов:


Кандидат Alice               Последователь Bob

  RequestVoteRequest   ------->
          <---------   RequestVoteResponse

  если Alice выигрывает выборы:

  Лидер Alice                Последователь Bob

  AppendEntriesRequest   ------->
  (heartbeat)
          <---------   AppendEntriesResponse

Определения

  • Источник (Source): идентифицирует отправителя сообщения
  • Назначение (Destination): идентифицирует получателя сообщения
  • Термины (Terms): см. Raft. Инициализируются в 0, монотонно возрастают
  • Индексы (Indexes): см. Raft. Инициализируются в 0, монотонно возрастают

Запросы

Запросы содержат заголовок и ноль или более записей журнала. Запросы содержат заголовок фиксированного размера и опциональные записи журнала переменного размера.

Заголовок запроса

Заголовок запроса — 45 байт, следующий формат. Все значения — беззнаковые, big-endian.


Тип сообщения:      1 байт
  Источник:            ID, 4-байтовое целое
  Назначение:          ID, 4-байтовое целое
  Терм:                Текущий терм (см. примечания), 8-байтовое целое
  Последний терм журнала:     8-байтовое целое
  Последний индекс журнала:   8-байтовое целое
  Индекс коммита:      8-байтовое целое
  Размер записей журнала:  Общий размер в байтах, 4-байтовое целое
  Записи журнала:       см. ниже, общая длина как указано

Примечания

В RequestVoteRequest терм — это терм кандидата. В остальных случаях — текущий терм лидера.

В AppendEntriesRequest, если размер записей журнала равен нулю, это сообщение является heartbeat (keepalive).

Записи журнала

Журнал содержит ноль или более записей. Каждая запись имеет следующий формат. Все значения — беззнаковые, big-endian.


Терм:           8-байтовое целое
  Тип значения:     1 байт
  Размер записи:     В байтах, 4-байтовое целое
  Запись:          длина как указано

Содержимое журнала

Все значения — беззнаковые, big-endian.

Тип значения журналаНомер
Прикладной1
Конфигурация2
ClusterServer3
LogPack4
SnapshotSyncRequest5

Прикладной

Содержимое кодируется в UTF-8 JSON . См. раздел “Прикладной уровень” ниже.

Конфигурация

Используется лидером для сериализации новой конфигурации кластера и репликации на пиры. Содержит ноль или более конфигураций ClusterServer.


Индекс журнала:  8-байтовое целое
  Последний индекс журнала:  8-байтовое целое
  Данные ClusterServer для каждого сервера:
    ID:                4-байтовое целое
    Длина данных Endpoint: В байтах, 4-байтовое целое
    Данные Endpoint:     ASCII-строка вида "tcp://localhost:9001", длина как указано

ClusterServer

Информация о конфигурации сервера в кластере. Включается только в сообщения AddServerRequest или RemoveServerRequest.

При использовании в AddServerRequest:


ID:                4-байтовое целое
  Длина данных Endpoint: В байтах, 4-байтовое целое
  Данные Endpoint:     ASCII-строка вида "tcp://localhost:9001", длина как указано

При использовании в RemoveServerRequest:


ID:                4-байтовое целое

LogPack

Включается только в SyncLogRequest.

Следующие данные сжимаются с помощью gzip перед передачей:


Длина данных индекса: В байтах, 4-байтовое целое
  Длина данных журнала:   В байтах, 4-байтовое целое
  Данные индекса:     по 8 байт на каждый индекс, длина как указано
  Данные журнала:       длина как указано

SnapshotSyncRequest

Включается только в InstallSnapshotRequest.


Последний индекс журнала:  8-байтовое целое
  Последний терм журнала:   8-байтовое целое
  Длина данных конфигурации: В байтах, 4-байтовое целое
  Данные конфигурации:     длина как указано
  Смещение:          Смещение данных в базе, в байтах, 8-байтовое целое
  Длина данных:        В байтах, 4-байтовое целое
  Данные:            длина как указано
  Is Done:         1 если завершено, 0 если нет (1 байт)

Ответы

Все ответы — 26 байт, следующий формат. Все значения — беззнаковые, big-endian.


Тип сообщения:   1 байт
  Источник:         ID, 4-байтовое целое
  Назначение:    Обычно фактический ID получателя (см. примечания), 4-байтовое целое
  Терм:           Текущий терм, 8-байтовое целое
  Следующий индекс:     Инициализируется как последний индекс журнала лидера + 1, 8-байтовое целое
  Принято:    1 если принято, 0 если нет (см. примечания), 1 байт

Примечания

ID назначения обычно — фактический получатель сообщения. Однако для AppendEntriesResponse, AddServerResponse и RemoveServerResponse — это ID текущего лидера.

В RequestVoteResponse, Is Accepted равно 1 при голосовании за кандидата (запросившего), и 0 при отказе в голосовании.

Прикладной уровень

Каждый сервер периодически публикует прикладные данные в журнал через ClientRequest. Прикладные данные содержат статус маршрутизатора каждого сервера и Destination для кластера Meta LS2. Серверы используют общий алгоритм для определения издателя и содержимого Meta LS2. Сервер с «лучшим» недавним статусом в журнале — издатель Meta LS2. Издатель Meta LS2 НЕ обязательно является Лидером Raft.

Содержимое прикладных данных

Прикладные данные кодируются в UTF-8 JSON , для простоты и расширяемости. Полная спецификация ещё не определена. Цель — предоставить достаточно данных для написания алгоритма определения «лучшего» маршрутизатора для публикации Meta LS2 и для издателя — достаточной информации для взвешивания Destinations в Meta LS2. Данные будут содержать статистику маршрутизатора и Destination.

Данные могут опционально содержать данные дистанционного мониторинга о состоянии других серверов и возможности получения Meta LS. Эти данные не будут поддерживаться в первом релизе.

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

Если указано “name: value”, это означает ключ и значение в JSON-карте. В остальных случаях спецификация ещё не определена.

Данные кластера (уровень верхнего уровня):

  • cluster: имя кластера
  • date: дата этих данных (long, мс с эпохи)
  • id: Raft ID (целое)

Данные конфигурации (config):

  • Любые параметры конфигурации

Статус публикации MetaLS (meta):

  • destination: destination MetaLS, base64
  • lastPublishedLS: если есть, base64-кодировка последнего опубликованного MetaLS
  • lastPublishedTime: в мс, или 0 если никогда
  • publishConfig: статус конфигурации издателя: off/on/auto
  • publishing: статус издателя MetaLS, булево true/false

Данные маршрутизатора (router):

  • lastPublishedRI: если есть, base64-кодировка последнего опубликованного Router Info
  • uptime: время работы в мс
  • Задержка заданий (Job lag)
  • Исследовательские туннели
  • Участвующие туннели
  • Настроенная пропускная способность
  • Текущая пропускная способность

Destinations (destinations): Список

Данные Destination:

  • destination: destination, base64
  • uptime: время работы в мс
  • Настроенные туннели
  • Текущие туннели
  • Настроенная пропускная способность
  • Текущая пропускная способность
  • Настроенные соединения
  • Текущие соединения
  • Данные чёрного списка

Данные дистанционного мониторинга маршрутизатора:

  • Последняя увиденная версия RI
  • Время получения LS
  • Данные теста соединения
  • Данные профиля ближайших floodfill’ов за периоды вчера, сегодня и завтра

Данные дистанционного мониторинга Destination:

  • Последняя увиденная версия LS
  • Время получения LS
  • Данные теста соединения
  • Данные профиля ближайших floodfill’ов за периоды вчера, сегодня и завтра

Данные дистанционного мониторинга Meta LS:

  • Последняя увиденная версия
  • Время получения
  • Данные профиля ближайших floodfill’ов за периоды вчера, сегодня и завтра

Интерфейс администрирования

Предстоит определить, возможно, в отдельном предложении. Не требуется для первого релиза.

Требования к интерфейсу администратора:

  • Поддержка нескольких мастер-destination, т.е. нескольких виртуальных кластеров (ферм)
  • Предоставление полного обзора общего состояния кластера — все статистики, опубликованные участниками, кто текущий лидер и т.д.
  • Возможность принудительного удаления участника или лидера из кластера
  • Возможность принудительной публикации metaLS (если текущий узел — издатель)
  • Возможность исключения хэшей из metaLS (если текущий узел — издатель)
  • Функциональность импорта/экспорта конфигурации для массовых развёртываний

Интерфейс маршрутизатора

Предстоит определить, возможно, в отдельном предложении. i2pcontrol не требуется для первого релиза, детальные изменения будут включены в отдельное предложение.

Требования к API Garlic Farm — маршрутизатор (внутри-JVM java или i2pcontrol)

  • getLocalRouterStatus()
  • getLocalLeafHash(Hash masterHash)
  • getLocalLeafStatus(Hash leaf)
  • getRemoteMeasuredStatus(Hash masterOrLeaf) // вероятно, не в MVP
  • publishMetaLS(Hash masterHash, List contents) // или подписанный MetaLeaseSet? Кто подписывает?
  • stopPublishingMetaLS(Hash masterHash)
  • аутентификация — предстоит определить

Обоснование

Atomix слишком большой и не позволит нам настроить протокол для маршрутизации через I2P. Кроме того, его формат передачи данных не задокументирован и зависит от сериализации Java.

Примечания

Проблемы

  • Нет способа для клиента узнать и подключиться к неизвестному лидеру. Небольшое изменение — чтобы Последователь отправлял конфигурацию как запись журнала в AppendEntriesResponse — могло бы решить это.

Миграция

Проблем обратной совместимости нет.

Ссылки