Garlic Farm-Protokoll

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

Überblick

Dies ist die Spezifikation für das Garlic Farm-Wire-Protokoll, basierend auf JRaft, seinem “exts”-Code zur Implementierung über TCP und seiner “dmprinter”-Beispielanwendung JRAFT. JRaft ist eine Implementierung des Raft-Protokolls RAFT.

Wir konnten keine Implementierung mit einem dokumentierten Wire-Protokoll finden. Allerdings ist die JRaft-Implementierung einfach genug, dass wir den Code überprüfen und dann sein Protokoll dokumentieren konnten. Dieser Vorschlag ist das Ergebnis dieser Bemühung.

Dies wird das Backend für die Koordination von Routern sein, die Einträge in einem Meta-LeaseSet veröffentlichen. Siehe Vorschlag 123.

Ziele

  • Kleine Codegröße
  • Basierend auf bestehender Implementierung
  • Keine serialisierten Java-Objekte oder Java-spezifische Funktionen oder Codierungen
  • Jegliches Bootstraping ist außerhalb des Umfangs. Es wird angenommen, dass mindestens ein anderer Server fest codiert oder außerhalb dieses Protokolls konfiguriert ist.
  • Unterstützung sowohl für Out-of-Band- als auch für In-I2P-Anwendungsfälle.

Design

Das Raft-Protokoll ist kein konkretes Protokoll; es definiert nur eine Zustandsmaschine. Daher dokumentieren wir das konkrete Protokoll von JRaft und basieren unser Protokoll darauf. Es gibt keine Änderungen am JRaft-Protokoll außer der Hinzufügung eines Authentifizierungs-Handshakes.

Raft wählt einen Leader, dessen Aufgabe es ist, ein Log zu veröffentlichen. Das Log enthält Raft-Konfigurationsdaten und Anwendungsdaten. Die Anwendungsdaten enthalten den Status jedes Serverrouters und das Ziel für den Meta-LS2-Cluster. Die Server verwenden einen gemeinsamen Algorithmus, um den Herausgeber und den Inhalt des Meta LS2 zu bestimmen. Der Herausgeber des Meta LS2 ist NICHT notwendigerweise der Raft-Leader.

Spezifikation

Das Wire-Protokoll erfolgt über SSL-Sockets oder nicht-SSL-I2P-Sockets. I2P-Sockets werden durch das HTTP-Proxy weitergeleitet. Es gibt keine Unterstützung für Clearnet-nicht-SSL-Sockets.

Handshake und Authentifizierung

Nicht definiert von JRaft.

Ziele:

  • Benutzer-/Passwort-Authentifizierungsmethode
  • Versionskennung
  • Cluster-Kennung
  • Erweiterbar
  • Einfachheit des Proxying bei Verwendung für I2P-Sockets
  • Server als Garlic Farm-Server nicht unnötig exponieren
  • Einfaches Protokoll, damit keine vollständige Webserver-Implementierung erforderlich ist
  • Kompatibel mit gängigen Standards, sodass Implementierungen, falls gewünscht, Standardbibliotheken verwenden können

Wir werden einen websocket-ähnlichen Handshake WEBSOCKET und HTTP-Digest-Authentifizierung RFC-2617 verwenden. Die RFC 2617-Basic-Authentifizierung wird NICHT unterstützt. Beim Proxying durch den HTTP-Proxy kommunizieren Sie mit dem Proxy wie in RFC-2616 spezifiziert.

Credentials

Ob Benutzernamen und Passwörter pro Cluster oder pro Server erfolgen, ist implementierungsabhängig.

HTTP-Anfrage 1

Der Absender sendet folgendes.

Alle Zeilen werden mit CRLF abgeschlossen, wie von HTTP gefordert.

GET /GarlicFarm/CLUSTER/VERSION/websocket HTTP/1.1
  Host: (ip):(port)
  Cache-Control: no-cache
  Connection: close
  (any other headers ignored)
  (blank line)

  CLUSTER ist der Name des Clusters (Standard "farm")
  VERSION ist die Garlic Farm-Version (derzeit "1")

HTTP-Antwort 1

Wenn der Pfad nicht korrekt ist, sendet der Empfänger eine standardmäßige “HTTP/1.1 404 Not Found”-Antwort, wie in RFC-2616.

Wenn der Pfad korrekt ist, sendet der Empfänger eine standardmäßige “HTTP/1.1 401 Unauthorized”-Antwort, einschließlich des WWW-Authenticate-HTTP-Digest-Authentifizierungs-Headers, wie in RFC-2617.

Beide Parteien schließen dann den Socket.

HTTP-Anfrage 2

Der Absender sendet folgendes, wie in RFC-2617 und WEBSOCKET.

Alle Zeilen werden mit CRLF abgeschlossen, wie von HTTP gefordert.

GET /GarlicFarm/CLUSTER/VERSION/websocket HTTP/1.1
  Host: (ip):(port)
  Cache-Control: no-cache
  Connection: keep-alive, Upgrade
  Upgrade: websocket
  (Sec-Websocket-* headers if proxied)
  Authorization: (HTTP-Digest-Authorization-Header gemäß RFC 2617)
  (any other headers ignored)
  (blank line)

  CLUSTER ist der Name des Clusters (Standard "farm")
  VERSION ist die Garlic Farm-Version (derzeit "1")

HTTP-Antwort 2

Wenn die Authentifizierung nicht korrekt ist, sendet der Empfänger eine weitere standardmäßige “HTTP/1.1 401 Unauthorized”-Antwort, wie in RFC-2617.

Wenn die Authentifizierung korrekt ist, sendet der Empfänger die folgende Antwort, wie in WEBSOCKET.

Alle Zeilen werden mit CRLF abgeschlossen, wie von HTTP gefordert.

HTTP/1.1 101 Switching Protocols
  Connection: Upgrade
  Upgrade: websocket
  (Sec-Websocket-* headers)
  (any other headers ignored)
  (blank line)

Nach dieser Empfang bleibt der Socket offen. Das nachstehend definierte Raft-Protokoll wird über denselben Socket gestartet.

Caching

Anmeldeinformationen sollen mindestens eine Stunde im Cache gespeichert werden, sodass nachfolgende Verbindungen direkt zur “HTTP-Anfrage 2” oben springen können.

Nachrichtentypen

Es gibt zwei Arten von Nachrichten, Anfragen und Antworten. Anfragen können Log-Einträge enthalten und sind variabel groß; Antworten enthalten keine Log-Einträge und sind fest groß.

Nachrichtentypen 1-4 sind die Standard-RPC-Nachrichten, die von Raft definiert sind. Dies ist das Kern-Raft-Protokoll.

Nachrichtentypen 5-15 sind die erweiterten RPC-Nachrichten, die von JRaft definiert sind, um Clients, dynamische Serveränderungen und effiziente Logsynchronisation zu unterstützen.

Nachrichtentypen 16-17 sind die Log-Kompressions-RPC-Nachrichten, die in Raft Abschnitt 7 definiert sind.

NachrichtNummergesendet vongesendet anHinweise
RequestVoteRequest1KandidatFollowerStandard Raft RPC; darf keine Log-Einträge enthalten
RequestVoteResponse2FollowerKandidatStandard Raft RPC
AppendEntriesRequest3LeaderFollowerStandard Raft RPC
AppendEntriesResponse4FollowerLeader / ClientStandard Raft RPC
ClientRequest5ClientLeader / FollowerAntwort ist AppendEntriesResponse; darf nur Anwendungs-Log-Einträge enthalten
AddServerRequest6ClientLeaderMuss nur einen ClusterServer-Log-Eintrag enthalten
AddServerResponse7LeaderClientLeader wird auch eine JoinClusterRequest senden
RemoveServerRequest8FollowerLeaderMuss nur einen ClusterServer-Log-Eintrag enthalten
RemoveServerResponse9LeaderFollower
SyncLogRequest10LeaderFollowerMuss nur einen LogPack-Log-Eintrag enthalten
SyncLogResponse11FollowerLeader
JoinClusterRequest12LeaderNeuer ServerEinladung zum Beitritt; muss nur einen Konfigurations-Log-Eintrag enthalten
JoinClusterResponse13Neuer ServerLeader
LeaveClusterRequest14LeaderFollowerBefehl zum Verlassen
LeaveClusterResponse15FollowerLeader
InstallSnapshotRequest16LeaderFollowerRaft Abschnitt 7; muss nur einen SnapshotSyncRequest-Log-Eintrag enthalten
InstallSnapshotResponse17FollowerLeaderRaft Abschnitt 7

Etablierung

Nach dem HTTP-Handshake ist die Einrichtungssequenz wie folgt:

Neue Serverin Alice            Zufälliger Follower Bob

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

  Wenn Bob sagt, er sei der Leader, weiter wie unten. Andernfalls muss Alice die Verbindung zu Bob trennen und sich mit dem Leader verbinden.


  Neue Serverin Alice            Leader Charlie

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

Trennsequenz:

Followerin Alice            Leader Charlie

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

Wahlsequenz:

Kandidatin Alice               Follower Bob

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

  Wenn Alice die Wahl gewinnt:

  Leiterin Alice                Follower Bob

  AppendEntriesRequest   ------->
  (Herzschlag)
          <---------   AppendEntriesResponse

Definitionen

  • Quelle: Identifiziert den Ursprungsort der Nachricht
  • Ziel: Identifiziert den Empfänger der Nachricht
  • Begriffe: Siehe Raft. Initialisiert auf 0, steigt monoton
  • Indizes: Siehe Raft. Initialisiert auf 0, steigt monoton

Anfragen

Anfragen enthalten einen Header und null oder mehr Log-Einträge. Anfragen haben einen Header mit fester Größe und optionale Logs-Einträge variabler Größe.

Anfrageheader

Der Anfrageheader ist 45 Bytes groß, wie folgt. Alle Werte sind unsigned Big-Endian.

Nachrichtentyp:         1 Byte
  Quelle:                 ID, 4-Byte-Integer
  Ziel:                   ID, 4-Byte-Integer
  Begriff:                Aktueller Begriff (siehe Anmerkungen), 8-Byte-Integer
  Letzter Log-Begriff:    8-Byte-Integer
  Letzter Log-Index:      8-Byte-Integer
  Bestätigungsindex:      8-Byte-Integer
  Log-Eintragsgröße:      Gesamte Größe in Bytes, 4-Byte-Integer
  Log-Einträge:           siehe unten, Gesamtlänge wie angegeben

Anmerkungen

Im RequestVoteRequest ist Begriff der Begriff des Kandidaten. Andernfalls ist es der aktuelle Begriff des Leaders.

Im AppendEntriesRequest, wenn die Log-Eintragsgröße null ist, ist diese Nachricht eine Herzschlag (Keepalive)-Nachricht.

Logs-Einträge

Das Log enthält null oder mehr Log-Einträge. Jeder Log-Eintrag ist wie folgt. Alle Werte sind unsigned Big-Endian.

Begriff:        8-Byte-Integer
  Werttyp:        1 Byte
  Eintragsgröße:  In Bytes, 4-Byte-Integer
  Eintrag:        Länge wie angegeben

Log-Inhalt

Alle Werte sind unsigned Big-Endian.

Log-WerttypNummer
Anwendung1
Konfiguration2
ClusterServer3
LogPack4
SnapshotSyncRequest5

Anwendung

Anwendungsinhalte sind UTF-8-codiert JSON. Siehe den Abschnitt Anwendungsebene unten.

Konfiguration

Dies wird verwendet, damit der Leader eine neue Clusterkonfiguration serialisiert und an Peers repliziert. Es enthält null oder mehr ClusterServer-Konfigurationen.

Log-Index:   8-Byte-Integer
  Letzter Log-Index:   8-Byte-Integer
  ClusterServer-Daten für jeden Server:
    ID:                4-Byte-Integer
    Endpunkt-Datenlänge: In Bytes, 4-Byte-Integer
    Endpunkt-Daten:     ASCII-String der Form "tcp://localhost:9001", Länge wie angegeben

ClusterServer

Die Konfigurationsinformationen für einen Server in einem Cluster. Dies ist nur in einer AddServerRequest- oder RemoveServerRequest-Nachricht enthalten.

Verwendet in einer AddServerRequest-Nachricht:

ID:                4-Byte-Integer
  Endpunkt-Datenlänge: In Bytes, 4-Byte-Integer
  Endpunkt-Daten:      ASCII-String der Form "tcp://localhost:9001", Länge wie angegeben

Verwendet in einer RemoveServerRequest-Nachricht:

ID:                4-Byte-Integer

LogPack

Dies ist nur in einer SyncLogRequest-Nachricht enthalten.

Folgendes wird vor der Übertragung komprimiert:

Index-Datenlänge: In Bytes, 4-Byte-Integer
  Log-Datenlänge:   In Bytes, 4-Byte-Integer
  Index-Daten:      8 Bytes für jeden Index, Länge wie angegeben
  Log-Daten:        Länge wie angegeben

SnapshotSyncRequest

Dies ist nur in einer InstallSnapshotRequest-Nachricht enthalten.

Letzter Log-Index:   8-Byte-Integer
  Letzter Log-Begriff:  8-Byte-Integer
  Konfigurationsdatenlänge: In Bytes, 4-Byte-Integer
  Konfigurationsdaten:     Länge wie angegeben
  Versatz:          Der Versatz der Daten in der Datenbank, in Bytes, 8-Byte-Integer
  Datenlänge:       In Bytes, 4-Byte-Integer
  Daten:            Länge wie angegeben
  Ist abgeschlossen: 1 falls abgeschlossen, 0 falls nicht (1 Byte)

Antworten

Alle Antworten sind 26 Bytes groß, wie folgt. Alle Werte sind unsigned Big-Endian.

Nachrichtentyp:  1 Byte
  Quelle:          ID, 4-Byte-Integer
  Ziel:            Normalerweise die tatsächliche Ziel-ID (siehe Anmerkungen), 4-Byte-Integer
  Begriff:         Aktueller Begriff, 8-Byte-Integer
  Nächster Index:  Initialisiert auf den letzten Log-Index des Leaders + 1, 8-Byte-Integer
  Ist angenommen:  1 falls angenommen, 0 falls nicht (siehe Anmerkungen), 1 Byte

Anmerkungen

Die Ziel-ID ist normalerweise das tatsächliche Ziel für diese Nachricht. Für AppendEntriesResponse, AddServerResponse und RemoveServerResponse ist es jedoch die ID des aktuellen Leaders.

Im RequestVoteResponse ist “Ist angenommen” 1 für eine Stimme für den Kandidaten (Anforderer) und 0 für keine Stimme.

Anwendungsebene

Jeder Server veröffentlicht regelmäßig Anwendungsdaten im Log in einer ClientRequest. Anwendungsdaten enthalten den Status jedes Serverrouters und das Ziel für den Meta-LS2-Cluster. Die Server verwenden einen gemeinsamen Algorithmus, um den Herausgeber und den Inhalt des Meta LS2 zu bestimmen. Der Server mit dem “besten” aktuellen Status im Log ist der Herausgeber des Meta-LS2. Der Herausgeber des Meta LS2 ist NICHT notwendigerweise der Raft-Leader.

Inhalte der Anwendungsdaten

Anwendungsinhalte sind UTF-8-codiert JSON, der Einfachheit und Erweiterbarkeit halber. Die vollständige Spezifikation ist TBD. Das Ziel ist es, genügend Daten zur Verfügung zu stellen, um einen Algorithmus zu schreiben, der den “besten” Router zur Veröffentlichung des Meta LS2 bestimmt, und für den Herausgeber, genügend Informationen zu haben, um die Ziele im Meta LS2 zu gewichten. Die Daten enthalten sowohl Router- als auch Zielstatistiken.

Die Daten können optional Fernerkundungsdaten über die Gesundheit der anderen Server und die Möglichkeit, das Meta LS abzurufen, enthalten. Diese Daten würden in der ersten Veröffentlichung nicht unterstützt werden.

Die Daten können optional Konfigurationsinformationen enthalten, die von einem Administrator-Client veröffentlicht werden. Diese Daten würden in der ersten Veröffentlichung nicht unterstützt werden.

Wenn “name: value” aufgeführt ist, spezifiziert dies den JSON-Map-Schlüssel und -Wert. Andernfalls ist die Spezifikation TBD.

Cluster-Daten (oberste Ebene):

  • cluster: Cluster-Name
  • date: Datum dieser Daten (long, ms seit der Epoche)
  • id: Raft ID (Integer)

Konfigurationsdaten (config):

  • Jegliche Konfigurationsparameter

MetaLS-Veröffentlichungsstatus (meta):

  • destination: die Metalls-Destination, Base64
  • lastPublishedLS: falls vorhanden, Base64-Codierung des zuletzt veröffentlichten Metalls
  • lastPublishedTime: in ms oder 0, falls niemals
  • publishConfig: Publisher-Konfigurationsstatus aus/an/auto
  • publishing: Metalls-Publisher-Status Boolescher Wert wahr/falsch

Router-Daten (router):

  • lastPublishedRI: falls vorhanden, Base64-Codierung der zuletzt veröffentlichten Router-Info
  • uptime: Betriebszeit in ms
  • Jobverzögerung
  • Erkundungstunnel
  • Beteiligte Tunnel
  • Konfigurierte Bandbreite
  • Aktuelle Bandbreite

Ziele (destinations): Liste

Zieldaten:

  • destination: das Ziel, Base64
  • uptime: Betriebszeit in ms
  • Konfigurierte Tunnel
  • Aktuelle Tunnel
  • Konfigurierte Bandbreite
  • Aktuelle Bandbreite
  • Konfigurierte Verbindungen
  • Aktuelle Verbindungen
  • Blacklist-Daten

Remote-Router-Sensorikdaten:

  • Letzte gesehene RI-Version
  • LS Abrufzeit
  • Verbindungstestdaten
  • Profil-Daten der nächsten Floodfills für die Zeiträume gestern, heute und morgen

Remote-Ziel-Sensorikdaten:

  • Letzte gesehene LS-Version
  • LS Abrufzeit
  • Verbindungstestdaten
  • Profil-Daten der nächsten Floodfills für die Zeiträume gestern, heute und morgen

Meta LS-Sensorikdaten:

  • Letzte gesehene Version
  • Abrufzeit
  • Profil-Daten der nächsten Floodfills für die Zeiträume gestern, heute und morgen

Verwaltungsinterface

TBD, möglicherweise ein separater Vorschlag. Nicht erforderlich für die erste Veröffentlichung.

Anforderungen eines Admin-Interfaces:

  • Unterstützung für mehrere Master-Ziele, d.h. mehrere virtuelle Cluster (Farmen)
  • Umfassende Übersicht über den gemeinsam genutzten Cluster-Zustand bieten - alle von den Mitgliedern veröffentlichten Statistiken, wer ist der aktuelle Leader usw.
  • Möglichkeit, einen Teilnehmer oder Leader aus dem Cluster zu entfernen
  • Möglichkeit, die Veröffentlichung von MetaLS zu erzwingen (wenn aktueller Knoten Herausgeber ist)
  • Möglichkeit, Hashes aus MetaLS auszuschließen (wenn aktueller Knoten Herausgeber ist)
  • Import-/Export-Funktionalität für Konfigurationen für Massenbereitstellungen

Router-Interface

TBD, möglicherweise ein separater Vorschlag. i2pcontrol ist in der ersten Veröffentlichung nicht erforderlich und detaillierte Änderungen werden in einem separaten Vorschlag enthalten sein.

Anforderungen für Garlic Farm an Router-API (in-JVM Java oder i2pcontrol)

  • getLocalRouterStatus()
  • getLocalLeafHash(Hash masterHash)
  • getLocalLeafStatus(Hash leaf)
  • getRemoteMeasuredStatus(Hash masterOrLeaf) // wahrscheinlich nicht im MVP
  • publishMetaLS(Hash masterHash, List contents) // oder signiertes MetaLeaseSet? Wer unterschreibt?
  • stopPublishingMetaLS(Hash masterHash)
  • Authentifizierung TBD?

Begründung

Atomix ist zu groß und erlaubt keine Anpassung, um das Protokoll über I2P zu leiten. Außerdem ist sein Wire-Format undokumentiert und hängt von der Java-Serialisierung ab.

Anmerkungen

Probleme

  • Es gibt keine Möglichkeit für einen Client, von einem unbekannten Leader zu erfahren und sich mit ihm zu verbinden. Es wäre eine kleine Änderung, wenn ein Follower die Konfiguration als Log-Eintrag in der AppendEntriesResponse senden würde.

Migration

Keine Abwärtskompatibilitätsprobleme.

Referenzen