How two games find each other, what gets encrypted and how, what a server can and cannot see, and how to run your own rendezvous service for your circle.
Comment deux jeux se trouvent, ce qui est chiffré et comment, ce qu'un serveur peut voir ou non, et comment faire tourner son propre service de rendez-vous pour un cercle d'amis.
No server holds your files. Your game talks straight to your friend's game. A small rendezvous service only helps the two of you find each other, and relays encrypted bytes when a direct connection is impossible.
SHA-256("linkpearl:mbox:v1" ‖ name@world ‖ window), truncated. Anyone who sees your character can compute it; that is how the orange glyph appears next to players who use Linkpearl.HMAC-SHA256(secret, "linkpearl:rv:v1" ‖ ⌊unix / 600⌋). When two announcements match, the service swaps the two addresses. The address lists are sealed under a key derived from that same secret, so the service can't read them. The plugins then try each other's addresses in priority order (local network, then the public address the service observed) and punch through the NAT.When no direct path exists (some strict NATs), the session goes through the relay of the service that introduced you, still end-to-end encrypted: it only sees sealed bytes. Your peers list shows whether each session is direct or relayed.
A direct connection shows your IP address to your peer, as in any peer-to-peer game or voice chat. Only people you paired with ever get it. A session relayed after a failed direct attempt does not hide it either: the addresses were exchanged during the attempt.
Aucun serveur ne détient les fichiers. Le jeu d'un joueur parle directement à celui de son ami. Un petit service de rendez-vous les aide seulement à se trouver, et relaie des octets chiffrés quand la connexion directe est impossible.
SHA-256("linkpearl:mbox:v1" ‖ nom@monde ‖ fenêtre), tronqué. Quiconque voit le personnage peut la calculer ; c'est ainsi que le glyphe orange apparaît à côté des joueurs qui utilisent Linkpearl.HMAC-SHA256(secret, "linkpearl:rv:v1" ‖ ⌊unix / 600⌋). Quand deux annonces correspondent, le service échange les deux adresses. Les listes d'adresses sont scellées sous une clé dérivée de ce même secret, donc le service ne peut pas les lire. Les plugins essaient alors les adresses de l'autre par ordre de priorité (réseau local, puis l'adresse publique que le service a observée) et percent le NAT.Quand aucun chemin direct n'existe (certains NAT stricts), la session passe par le relais du service qui a mis les deux joueurs en relation, toujours chiffrée de bout en bout : il ne voit que des octets scellés. La liste des pairs indique si chaque session est directe ou relayée.
Une connexion directe montre l'adresse IP de chaque joueur à son pair, comme tout jeu ou chat vocal en pair à pair. Seuls les pairs établis l'obtiennent. Une session relayée après un essai direct raté ne la cache pas non plus : les adresses ont été échangées pendant l'essai.
Everything comes from .NET's standard library, with no third-party cryptography.
| Role | Primitive |
|---|---|
| Identity, signatures | ECDSA P-256, SHA-256 |
| Key agreement | ECDH P-256, ephemeral for every session |
| Key derivation | HKDF-SHA256 |
| Authenticated encryption | AES-256-GCM |
| Content addressing | SHA-256 |
A custom construction. The full protocol, its trust model and its known limits are written up in docs/protocol.md (French).
Tout vient de la bibliothèque standard de .NET, sans cryptographie tierce.
| Rôle | Primitive |
|---|---|
| Identité, signatures | ECDSA P-256, SHA-256 |
| Accord de clé | ECDH P-256, éphémère à chaque session |
| Dérivation | HKDF-SHA256 |
| Chiffrement authentifié | AES-256-GCM |
| Adressage de contenu | SHA-256 |
Une construction maison. Le protocole complet, son modèle de confiance et ses limites connues sont décrits dans docs/protocol.md.
| Simulated latency | 0 ms | 20 ms | 60 ms |
|---|---|---|---|
| Throughput, 405 MB look | 53.8 MB/s | 33.0 MB/s | 17.5 MB/s |
Measured on a test bench, on one machine, with the real transfer engine on both sides. Real networks will vary. The upload limiter exists but is off by default.
| Latence simulée | 0 ms | 20 ms | 60 ms |
|---|---|---|---|
| Débit, apparence de 405 Mo | 53,8 Mo/s | 33,0 Mo/s | 17,5 Mo/s |
Mesuré sur un banc d'essai, sur une seule machine, avec le vrai moteur de transfert des deux côtés. Un vrai réseau donnera autre chose. Le limiteur d'envoi existe mais il est désactivé par défaut.
| It sees | It never sees |
|---|---|
| IP addresses | Your files and appearances |
| Which characters are online, if they are discoverable | Manifests and private keys |
| Who is paired with whom: pairing requests and IP addresses give it away | The secret each pair shares |
| Pairing requests: name, world and public keys | Sessions, direct or relayed: only sealed bytes |
| That two tickets match in a ten-minute window | What peers exchange once connected |
Being discoverable means being discoverable. For a stranger to recognise you in game, your mailbox address has to derive from your name, and FFXIV names can be enumerated. So the operator of a service can tell which characters are online. You can opt out with Me signaler aux autres joueurs (let other players see me) in Réglages › Visibilité; then nobody can spot you or send you a request.
What a service keeps: nothing on disk, and little for long in memory. A pairing request is never stored: it is delivered at once, or dropped if the target is not connected. A mailbox exists only while your game is connected, an announcement lives ten minutes at most, an invitation twenty-four hours and vanishes as soon as it is used. Your IP address is kept only for the length of the connection. By default the log says what happens, never to whom: no IP addresses, no names. It goes to the host's system journal, whose retention the operator sets.
A rendezvous is not an authority. It can make a connection fail; it cannot read your files or pass itself off as a peer you already paired with. One caveat, stated plainly: a pairing request goes through the target's mailbox, so a malicious operator could slip in at that moment, without either of you noticing. That is why the choice of services matters, and why a circle running its own is the strongest setup: the operator is one of you.
| Il voit | Il ne voit jamais |
|---|---|
| Les adresses IP | Les fichiers et les apparences |
| Quels personnages sont en ligne, s'ils sont détectables | Les manifestes et les clés privées |
| Qui est pairé avec qui : les demandes de pairage et les adresses IP le trahissent | Le secret que partage chaque paire |
| Les demandes de pairage : nom, monde et clés publiques | Les sessions, directes ou relayées : seulement des octets scellés |
| Que deux tickets correspondent dans une fenêtre de dix minutes | Ce que les pairs échangent une fois connectés |
Être détectable, c'est être détectable. Pour qu'un inconnu reconnaisse un joueur en jeu, l'adresse de sa boîte doit dériver de son nom, et les noms de FFXIV s'énumèrent. L'opérateur d'un service peut donc savoir quels personnages sont en ligne. Le réglage Me signaler aux autres joueurs, dans Réglages › Visibilité, permet de s'en retirer ; personne ne peut alors repérer le personnage ni lui envoyer de demande.
Ce qu'un service conserve : rien sur disque, et peu de temps en mémoire. Une demande de pairage n'est jamais stockée : elle est remise aussitôt, ou abandonnée si la cible n'est pas connectée. Une boîte n'existe que tant que le jeu est connecté, une annonce vit dix minutes au plus, une invitation vingt-quatre heures et disparaît dès qu'elle sert. L'adresse IP n'est gardée que le temps de la connexion. Par défaut, le journal dit ce qui se passe, jamais à qui : ni adresse IP, ni nom. Il part dans le journal système de l'hébergeur, dont l'opérateur règle la durée de conservation.
Un rendez-vous n'est pas une autorité. Il peut faire échouer une connexion ; il ne peut ni lire les fichiers ni se faire passer pour un pair déjà établi. Une réserve, dite franchement : une demande de pairage passe par la boîte de la cible, donc un opérateur malveillant pourrait s'intercaler à ce moment-là, sans que ni l'un ni l'autre ne s'en aperçoive. C'est pourquoi le choix des services compte, et pourquoi un cercle qui fait tourner le sien est la configuration la plus sûre : l'opérateur fait partie du réseau.
There is no central server. Anyone can run a rendezvous service, and the plugin talks to every service in your list at once (Réglages › Réseau › Services de rendez-vous, Settings › Network › Rendezvous services).
Il n'y a pas de serveur central. N'importe qui peut faire tourner un service de rendez-vous, et le plugin parle à tous les services de la liste à la fois (Réglages › Réseau › Services de rendez-vous).
One condition for the first two: pair with your service alone in the list. A pairing request goes through every service in the list, and the pair then announces on all of them. You can add the public service back afterwards to meet players outside the circle: existing pairs will not announce there, though it will see you online if you stay discoverable.
The cost: a small server and a few minutes of setup, detailed below. And whoever runs it sees what a service sees: pick someone the circle trusts.
The service is one self-contained binary, lprdv. It needs no .NET runtime and no database.
You need a Linux x86-64 server with a public IP address and systemd (a small VPS is enough; the provided unit caps the service at 512 MB of RAM), SSH access with sudo, and one port open in both TCP and UDP, 47900 by default.
Bandwidth: introducing peers costs almost nothing. Relaying is what costs: two players who can't connect directly send their whole appearance, often hundreds of megabytes, through your server.
In one command. The generator writes it for you: download, check, install, open the port and start. The steps below do the same by hand.
Checking by hand proves less. The checksum below comes from the same release as the binary: it proves the download is intact, not that the project published it. The generator's script also checks the release signature against the project key it carries, which a replaced release file cannot fake.
# system account that runs the service
sudo useradd --system --home-dir /var/lib/lprdv --shell /usr/sbin/nologin lprdv
# latest release and its unit; this checksum only catches a damaged download
cd /tmp
curl -fLO https://github.com/LinkPearl-Sync/linkpearl-sync-rendezvous/releases/latest/download/lprdv
curl -fLO https://github.com/LinkPearl-Sync/linkpearl-sync-rendezvous/releases/latest/download/lprdv.service
curl -fLO https://github.com/LinkPearl-Sync/linkpearl-sync-rendezvous/releases/latest/download/lprdv.sha256
sha256sum -c --ignore-missing lprdv.sha256
sudo install -D -m 0755 lprdv /opt/lprdv/lprdv
# hardened systemd unit, restarts on its own
sudo install -m 0644 lprdv.service /etc/systemd/system/lprdv.service
sudo systemctl daemon-reload
sudo systemctl enable --now lprdv
In TCP and UDP, on the server and in your host's firewall panel if it has one.
sudo ufw allow 47900
This should print 200. The log never contains IP addresses unless you start the service with --verbose.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:47901/healthz
journalctl -u lprdv -f
In game: Réglages › Réseau › Services de rendez-vous, type your server's address (rdv.example.org, or rdv.example.org:47900 if you changed the port), then Ajouter (Add). Each friend does the same. Keep the default service in the list if you still want to see players outside your circle.
The console shows the service's health, counters and history, and lets you approve directory applications. It only answers the server itself: reach it through an SSH tunnel, open http://localhost:47901, and log in with any user name and the token as password.
ssh -L 47901:127.0.0.1:47901 you@your-server
sudo cat /var/lib/lprdv/admin.token
Out of the box, the service applies to the open network at startup and every day, under the IPv4 address it connects from. It joins after 72 hours of reliable answers. Add --no-announce to stay out, or give it a name and a label as below.
To let your users discover other services, list them in /var/lib/lprdv/peers.txt, one address label per line. The file is re-read live. To appear in another service's directory instead, pass it to --announce-to; its operator decides.
sudo systemctl edit lprdv
Then, in the editor that opens:
[Service]
ExecStart=
ExecStart=/opt/lprdv/lprdv --port 47900 --public-address rdv.example.org --label "My Free Company"
Servers installed with the generator update themselves, 24 hours after each signed release. By hand, rerun its command, or download and check the new binary as in step 1, then:
sudo install -m 0755 /tmp/lprdv /opt/lprdv/lprdv
sudo systemctl restart lprdv
Back up /var/lib/lprdv. It holds your settings, your directory, and the ban list with its salt, which cannot be rebuilt. Nothing about your users is stored there.
Moderation. The console keeps a ban list of characters, published as slow hashes at /api/bans so it can't serve as a directory of modders. The plugin does not apply these lists yet.
Every option is documented in the rendezvous repository (and in lprdv --help, in French).
Une condition pour les deux premiers points : se pairer avec le service du cercle seul dans la liste. Une demande de pairage passe par tous les services de la liste, et la paire s'annonce ensuite sur chacun. Le service public peut être rajouté ensuite, pour croiser des joueurs hors du cercle : les pairages déjà formés ne s'y annonceront pas, mais il verra le personnage en ligne s'il reste détectable.
Ce que ça coûte : un petit serveur et quelques minutes d'installation, détaillés ci-dessous. Et qui le fait tourner voit ce que voit un service : mieux vaut quelqu'un en qui le cercle a confiance.
Le service est un seul binaire autonome, lprdv. Il n'a besoin ni du runtime .NET ni de base de données.
Il faut un serveur Linux x86-64 avec une adresse IP publique et systemd (un petit VPS suffit ; l'unité fournie plafonne le service à 512 Mo de mémoire), un accès SSH avec sudo, et un port ouvert en TCP et en UDP, 47900 par défaut.
La bande passante : mettre les pairs en relation ne coûte presque rien. C'est le relais qui coûte : deux joueurs qui ne peuvent pas se connecter directement font passer toute leur apparence, souvent des centaines de mégaoctets, par le serveur.
En une commande. Le générateur l'écrit pour vous : téléchargement, vérification, installation, ouverture du port et démarrage. Les étapes ci-dessous font la même chose à la main.
À la main, la vérification prouve moins. L'empreinte ci-dessous vient de la même version que le binaire : elle prouve que le téléchargement est intact, pas que le projet l'a publié. Le script du générateur vérifie en plus la signature de la version avec la clé du projet qu'il embarque, ce qu'un fichier de version remplacé ne peut pas imiter.
# compte système qui fait tourner le service
sudo useradd --system --home-dir /var/lib/lprdv --shell /usr/sbin/nologin lprdv
# dernière version et son unité ; cette empreinte n'attrape qu'un téléchargement abîmé
cd /tmp
curl -fLO https://github.com/LinkPearl-Sync/linkpearl-sync-rendezvous/releases/latest/download/lprdv
curl -fLO https://github.com/LinkPearl-Sync/linkpearl-sync-rendezvous/releases/latest/download/lprdv.service
curl -fLO https://github.com/LinkPearl-Sync/linkpearl-sync-rendezvous/releases/latest/download/lprdv.sha256
sha256sum -c --ignore-missing lprdv.sha256
sudo install -D -m 0755 lprdv /opt/lprdv/lprdv
# unité systemd durcie, qui redémarre seule
sudo install -m 0644 lprdv.service /etc/systemd/system/lprdv.service
sudo systemctl daemon-reload
sudo systemctl enable --now lprdv
En TCP et en UDP, sur le serveur et dans le pare-feu de l'hébergeur s'il en a un.
sudo ufw allow 47900
Ceci doit afficher 200. Le journal ne contient jamais d'adresse IP, sauf si le service est lancé avec --verbose.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:47901/healthz
journalctl -u lprdv -f
En jeu : Réglages › Réseau › Services de rendez-vous, saisir l'adresse du serveur (rdv.exemple.fr, ou rdv.exemple.fr:47900 si le port a changé), puis Ajouter. Chaque ami fait de même. Garder le service par défaut dans la liste permet de continuer à voir les joueurs hors du cercle.
La console montre la santé du service, ses compteurs et leur historique, et permet d'approuver les candidatures à l'annuaire. Elle ne répond qu'au serveur lui-même : passer par un tunnel SSH, ouvrir http://localhost:47901, et se connecter avec n'importe quel identifiant et le jeton comme mot de passe.
ssh -L 47901:127.0.0.1:47901 utilisateur@serveur
sudo cat /var/lib/lprdv/admin.token
Sans rien configurer, le service se porte candidat au réseau ouvert au démarrage puis chaque jour, sous l'adresse IPv4 d'où il se connecte. Il y entre après 72 heures de réponses fiables. --no-announce l'en tient à l'écart ; on peut aussi lui donner un nom et un libellé, comme ci-dessous.
Pour que les utilisateurs découvrent d'autres services, les lister dans /var/lib/lprdv/peers.txt, une ligne adresse libellé par service. Le fichier est relu à chaud. Pour figurer plutôt dans l'annuaire d'un autre service, le donner à --announce-to ; son opérateur décide.
sudo systemctl edit lprdv
Puis, dans l'éditeur qui s'ouvre :
[Service]
ExecStart=
ExecStart=/opt/lprdv/lprdv --port 47900 --public-address rdv.exemple.fr --label "Ma compagnie libre"
Les serveurs installés par le générateur se mettent à jour seuls, 24 heures après chaque release signée. À la main, relancer sa commande, ou télécharger et vérifier le nouveau binaire comme à l'étape 1, puis :
sudo install -m 0755 /tmp/lprdv /opt/lprdv/lprdv
sudo systemctl restart lprdv
Sauvegarder /var/lib/lprdv. Il contient les réglages, l'annuaire, et la liste de bannissement avec son sel, qui ne se reconstruit pas. Rien des utilisateurs n'y est stocké.
Modération. La console tient une liste de personnages bannis, publiée sous forme d'empreintes lentes sur /api/bans pour qu'elle ne puisse pas servir d'annuaire des joueurs moddés. Le plugin n'applique pas encore ces listes.
Toutes les options sont décrites dans le dépôt du rendez-vous et dans lprdv --help.