Netzwerk einrichten

devel.one wird mit einem Network-PlugIn ausgeliefert, welches ein vermaschtes Netz (ein Mesh-Network) ermöglicht. Dazu werden Instanzen von D1 auf einem oder mehreren Rechnern mit TCP-Links zu einem Netz verknüpft.

Technisch gesehen wird beim Laden des Netzwerk-PlugIns die Konfiguration von einem TcpStarter-Objekt gelesen und die darin gefundenen TCP-Server und -Clients erstellt.

Clients und Server können natürlich auch über Services gestartet und gestoppt werden.

TCP-Server automatisch starten

Das automatische Starten eines Servers kann durch einen Eintrag in der Konfiguration erreicht werden. Dazu ist lediglich der Port anzugeben, der für den Server geöffnet werden soll:

<entry path="plugin/D1Tcp/autostart/Server1" key="Port" force="True">
    <value>22000</value>
</entry>

Der Pfad, der für den Servereintrag in der Konfiguration verwendet wird, muss immer mit “plugin/D1Tcp/autostart/” beginnen. Dort befinden sich die Verzeichnisse der Server, die mittels Autostart gestartet werden sollen. Der Sub-Pfad (hier “Server1”) kann frei gewählt werden (z.B. “Server1”, “Server22000” oder ähnlich). Alle Einstellungen für den jeweiligen Server müssen dann in diesem Pfad liegen.

TCP-Client automatisch starten

Clients können ebenfalls automatisch gestartet werden. Dazu genügen Einträge in der Konfiguration:

<entry path="plugin/D1Tcp/autoconnect/Client1" key="Port" force="True">
    <value>22000</value>
</entry>
<entry path="plugin/D1Tcp/autoconnect/Client1" key="TcpAddress" force="True">
    <value>localhost</value>
</entry>

Bei Clients müssen mindestens zwei Einträge vorhanden sein (TCP-Adresse und Port). Es gibt keinen Default-Port.

Der Pfad, der für den Clienteintrag in der Konfiguration verwendet wird, muss immer mit “plugin/D1Tcp/autoconnect/” beginnen. Dort befinden sich die Verzeichnisse der Clients, die mittels Autoconnect gestartet werden sollen. Der Sub-Pfad (hier “Client1”) kann frei gewählt werden (z.B. “Client1”, “ClientCompany” oder ähnlich). Alle Einstellungen für den jeweiligen Client müssen dann in diesem Pfad liegen.

SSL/TLS verwenden

Für beide Netzwerkteilnehmer (Client und Server) kann die Verwendung von SSL bzw TLS in der Konfiguration eingeschaltet werden:

<entry path="plugin/D1Tcp/autoconnect/Client1" key="UseSsl" force="True">
    <value>True</value>
</entry>

bzw. für den Server:

<entry path="plugin/D1Tcp/autostart/Server1" key="UseSsl" force="True">
    <value>True</value>
</entry>

Das genügt jedoch noch nicht, denn es wird noch ein TrustStore mit den Keys benötigt. Dieser wird per System-Property übergeben, ebenso das Passwort für den Truststore:

-Djavax.net.ssl.trustStore=m:\run2\common\testkeystore.ks
-Djavax.net.ssl.trustStorePassword=test01

Obige Variante kann so in Eclipse in den Run/Debug-Configurations angegeben werden:

../_images/EclipseRunConfiguration1.PNG

Beim Start einer D1-Instanz per Command-Line wird es ebenfalls so verwendet:

java -cp ../lib/* -Dlogback.configurationFile=logback.xml -Djavax.net.ssl.trustStore=..\common\testkeystore.ks -Djavax.net.ssl.trustStorePassword=test01 de.softdevel.d1.kernel.impl.CKernel kernel/ConfigDir=./ kernel/PluginDir=../plugins/ kernel/RecordDbFile=../common/recorddb.xml

Automatischer Reconnect des Client

Damit ein Client in periodisch versucht, eine Verbindung zu einem Server herzustellen, genügt ein Eintrag in der Konfiguration:

<entry path="plugin/D1Tcp/autoconnect/Client1" key="Retry" force="True">
    <value>5000</value>
</entry>

Der Wert für das Zeitintervall wird in Millisekunden angegeben. Im Beispiel wird der Client alle 5 Sekunden versuchen, den Server zu erreichen.

Bei der Verwendung von Reconnect-Timern ist die Reihenfolge des Starts von Servern und Clients im Netzwerk nicht von Bedeutung. Die Instanzen werden sich von allein verbinden. Ebenso können Server und Clients einzeln heruntergefahren und wieder gestartet werden.

../_images/Sequenz-Monitor1.PNG

Messages auf dem Weg zum anderen Host

Für den Weg zu einem anderen Host über einen Transport-Kanal muss die Nachricht gepackt werden. Dazu wird sie erst einmal über einen Stream in ein Byte-Array überführt. Das ist jedoch nur der Anfang. Für bestimmte Nachrichten lohnt sich eine Kompression, die das Byte-Array weiter verkleinert. Zudem ist mitunter eine zusätzliche Verschlüsselung gewünscht, falls bestimmte Hosts auf dem Weg nicht vertrauenswürdig sind.

Wir haben hier das Konzept der Package Builder Sequence implementiert. Jede Methode, das Paket zu verwandeln, wird mit einem Buchstaben gekennzeichnet.

Zur Zeit gibt es

  • M = Message streamen (mandatory)
  • Z = Paket mit dem ZIP-Algorithmus komprimieren
  • A = mit AES verschlüsseln.

Das Paket trägt im Header die angewandte Sequenz, z.B. “MZA” für alle drei Builder Arten. Beim Auspacken werden die Builder in umgekehrter Reihenfolge aufgerufen.

../_images/PackageBuilderSequence500x139.png

Welche Package Builder Sequence zum tragen kommt, kann man auf vielfältige Art und Weise einstellen:

  • Eine Sequenz kann man im Envelope einer Message einstellen. Sie hat die höchste Priorität.

  • Fehlt die Sequenz im Envelope, kommt die eigene Streckenfrequenz zum Zuge. Sie gilt nur für eine bestimmte Strecke zwischen 2 Hosts, welche nicht benachbart sein muss. im Datenpool liegt sie unter:

    plugin/network/hosts/{HID}/MyPacketBuilderSequence
    

    wobei HID die gegnerische Host-ID ist.

  • Fehlt die Streckensequenz, wird die Sequenz für den gesamten Host angezogen. Sie wird in der Konfiguration wie folgt eingetragen:

    <entry path="plugin/network" key="PacketBuilderSequence" force="True">
        <description>
            The PacketBuilderSequence is the sequence of packing messages, before they will be sent to another host.
            'M' means 'Message' and is mandatory.
            'Z' means 'Zip': ZIP compression is used if the compressed packet is smaller then the uncompressed packet.
            'A' means 'AES': AES encryption is used after compression.
            The default PacketBuilderSequence is 'MZA'.
        </description>
        <value>MZA</value>
    </entry>
    
  • Fehlt auch dieser Eintrag, wird “M” angenommen, als weder Kompression noch Verschlüsselung verwendet.

../_images/HopHopHop400x294.png

Die symmetrische AES-Verschlüsselung ist nicht zu Verwechseln mit der Benutzung von SSL/TLS für einen Link zwischen zwei benachbarten Hosts. Sie gilt vielmehr für die interne Verschlüsselung einer Nachricht zwischen Sender und Empfänger-Host, also z.B. zwischen “A” und “D” im Diagramm oben.

Wie erfährt denn der Sender-Host, wie es der Empfänger-Host gerne hätte? Und wie wird denn verschlüsselt, wenn nicht die TLS-Schicht dafür herangezogen wird?

Nun, bevor die erste Nachricht von z.B. A nach D geschickt wird, muss A den Host D nach seinen Präferenzen fragen. Dieses geschieht (natürlich!) durch eine Message (GetHostRecord). (Falls bei der ersten zu schickenden Message das noch nicht getan wurde, wird die aktuelle Message einen Augenblick zurückgestellt und erst einmal die GetHostRecord geschickt.) Mitgegeben wird ein Public Key eines RSA-Schlüssels von A, mit dem dann D einen symmetrischen SessionKey verschlüsselt. Zusätzlich werden diverse andere Daten mitgegeben, wie Name, Vendor, Package Builder Sequence (!), Lizenzkey etc. Damit erfährt A nun alles, was er für die Kommunikation mit D benötigt. Umgekehrt (D–>A) läuft es genauso. Es werden also zwischen jedem Host-Pärchen zwei verschiedene Sessionkeys (hin und zurück) vereinbart. Das passiert nur am Anfang einer Session (und nach Ablauf einer gewissen Zeit wieder), und ist so schnell, dass man es nicht bemerkt.

Einen Haken hat die Sache: Wenn einer der beteiligten Hosts herunter- und wieder heraufgefahren wird, dann werden die Sessionkeys ungültig. Da der gegnerische Host nicht unbedingt mitbekommt, das der eine Host kurz down war, bekommt Host D von Host A eine Nachricht, die noch mit dem alten SessionKey verschlüsselt ist. Diese Nachricht ist verloren. Immerhin wird der Umstand genutzt, und Host A wird aufgefordert, einen neuen HostRecord anzufordern.

Priorisierung von Messages

Die Priorität, die im Envelope einer Message eingetragen werden kann, gilt nur auf den Strecken zwischen den Hosts. Wenn eine Message von einem Host an einen benachbarten Host weitergereicht werden soll, kommt sie erst einmal in eine Queue mit eben dieser Priorität. Die nächste zu transportierende Message entnimmt das Network-Plugin immer der höchsten Queue (wenn da nichts enthalten ist, wird die nächsthöhere Queue geleert usw.). So können langsame Nachrichten (z.B. LOG-Nachrichten) die wichtigen nicht ausbremsen. Ziel ist eben ein quicklebendiges System.

Antworten erhalten übrigens immer die höchste Priorität.