← Blog

· 20 min de lecture #rust#mqtt#iot#unikernel#open source

urMQTT : nous construisons un broker MQTT qui est aussi un système d'exploitation

Notes de développement sur urMQTT, un unikernel écrit en Rust qui est lui-même un broker MQTT : un seul ELF avec sa propre pile TCP/IP, TLS 1.3, broker et SQLite, démarré depuis un seul ISO.

Les appareils embarqués dans les véhicules, sur les parkings et dans les armoires électroniques ont une caractéristique commune : ils envoient de petits messages, rarement, mais ils sont très nombreux et toujours connectés. Depuis des années, nous les raccordons à des brokers MQTT, et depuis des années la même question nous tracasse. Pourquoi faut-il un Linux complet, OpenSSL, le runtime d'un langage, SSH, un gestionnaire de paquets, et seulement ensuite un broker, pour une tâche aussi simple ? Chacune de ces couches apporte ses propres vulnérabilités, consomme sa propre mémoire et ajoute un élément de plus susceptible de tomber en panne, alors qu'une flotte d'appareils n'attend d'un serveur que trois choses : tenir un grand nombre de connexions presque inactives, authentifier chaque appareil de façon fiable et acheminer les messages sans perte.

C'est ainsi qu'est né urMQTT : un unikernel qui est un broker MQTT. Le noyau, la pile TCP/IP, TLS 1.3, MQTT et une base de données intégrée tiennent dans un seul binaire Rust qui démarre depuis un seul ISO. Pas de processus, pas d'appels système, pas de shell. La première plateforme sur laquelle il est construit et testé est aarch64, et le même code se dirige vers x86_64, par UEFI et BIOS classique, et vers du matériel réel : l'objectif est un seul ISO qui démarre sur tous. Le projet est open source sous licence MIT ou Apache 2.0, et dans cet article nous souhaitons consigner où nous en sommes, comment il est construit en interne et ce que nous avons appris en chemin.

Ce que signifie exactement « unikernel »

Un broker classique est un programme qui s'exécute sur un système d'exploitation. urMQTT n'a aucun système d'exploitation en dessous de lui : il s'amorce lui-même via le firmware UEFI et le bootloader Limine, met en place ses propres tables de pages, programme le contrôleur d'interruptions et le timer, découvre les périphériques virtio via PCIe, obtient une adresse par DHCP et écoute sur un seul port. Tout ce que feraient habituellement le noyau, les pilotes, la pile réseau et l'application tient ici dans un seul ELF qui s'exécute au niveau EL1.

La comparaison avec une installation typique parle d'elle-même :

Broker typique sous Linux urMQTT
Services exposés SSH, broker, souvent aussi un port de métriques ou d'administration uniquement TCP 8883 (MQTT sur TLS 1.3 avec mTLS)
Code dans l'image noyau, userland, libc, OpenSSL, runtime un seul ELF Rust (plus SQLite en version vendorisée)
Gestion à distance shell, fichiers de configuration, mises à jour de paquets aucune. Une nouvelle version est un nouvel ISO
Mémoire par connexion inactive des kilo-octets (tampons de socket, objets TLS, heap) objectif ≤ 512 B
Modèle multicœur threads et verrous, ou une seule boucle d'événements un thread par cœur, aucun état partagé, traitement de bout en bout
Intégrité du code mappages à la fois inscriptibles et exécutables possibles W^X, tables de pages verrouillées, PAC/BTI

Les priorités sont, dans cet ordre : sécurité, scalabilité, performances multicœurs, faible empreinte et installation immuable. Chaque fois que deux objectifs entrent en conflit, celui qui figure plus haut dans cette liste l'emporte.

Où nous en sommes

Le projet est en pré-alpha, mais dix-sept jalons de développement, de M0 à M17, sont bouclés. Aujourd'hui, urMQTT sert MQTT 3.1.1 sur TLS 1.3 avec des certificats clients obligatoires, et conserve son état dans SQLite sur un disque brut. En bref, voici ce qu'il fait déjà :

  • Noyau : vecteurs d'exceptions propres avec rapports décodés, tables de pages propres avec W^X appliqué, pages de garde, PAC et BTI là où le processeur les possède, GICv3 et GICv2, timer générique, SMP jusqu'à 64 cœurs.
  • Découverte du matériel à partir du DTB ou d'ACPI vers une description de plateforme unique, avec un résultat identique depuis les deux sources.
  • Pilotes : PCIe ECAM, MSI-X, virtio-net, virtio-blk, virtio-scsi et virtio-rng, ainsi qu'un CSPRNG initialisé avec de l'entropie matérielle.
  • Réseau : DHCPv4, ARP, ICMP et IPv4 strict maison, puis un TCP maison côté serveur uniquement : cookies SYN, roue de timers hiérarchique, NewReno, fusion des ACK, aucun état TIME_WAIT.
  • TLS : TLS 1.3 uniquement, mTLS obligatoire, identité de l'appareil issue du CN ou d'un SAN URI ; la poignée de main est assurée par rustls, puis une couche d'enregistrements maison avec AES-128-GCM et ChaCha20-Poly1305. Reprise de session par tickets PSK, liste de révocation, contrôle d'admission dès la poignée de main.
  • MQTT : codec sans copie, fuzzé et testé avec de vrais clients mosquitto ; QoS 0 et 1, jokers, messages retenus, testament (LWT), sessions persistantes, ACL limitées à devices/{id}/#, limites de débit par appareil.
  • Stockage : SQLite dans le noyau via un VFS sur disque brut, en mode WAL, avec vérification d'intégrité au démarrage ; registre des appareils, révocations, messages retenus, sessions persistantes avec files de messages pour les appareils hors ligne ; par cœur, un journal de télémétrie distinct avec CRC par enregistrement et récupération après un plantage.
  • Exploitation et supervision : timer de surveillance pour les gestionnaires, contrôle de santé, journalisation limitée, syslog et statsd sur UDP, une admission qui résiste aux tempêtes de reconnexions.
  • Livraison : reconstructions identiques au bit près vérifiées en CI, ISO et image disque GPT brute, signatures minisign, instructions pour KVM, Proxmox et Hetzner.

Chaque étape est marquée par le jalon qui l'a apportée, et le fichier docs/PROGRESS.md du dépôt est la seule source de vérité sur ce qui fonctionne réellement.

L'architecture en trois images

La conception complète est décrite dans docs/ARCHITECTURE.md, et les raisons de chaque décision importante sont consignées sous forme d'ADR dans docs/DECISIONS.md. Voici un aperçu.

Les couches

Les couches d'urMQTT : du broker, en passant par le codec MQTT, TLS, TCP et le réseau, jusqu'aux pilotes virtio et à la couche d'architecture, le tout dans un seul ELF au niveau EL1.
Les couches d'urMQTT : du broker, en passant par le codec MQTT, TLS, TCP et le réseau, jusqu'aux pilotes virtio et à la couche d'architecture, le tout dans un seul ELF au niveau EL1.

Du haut vers le bas : le broker (sessions, arbre des sujets, QoS, messages retenus, testaments, ACL, limites), le codec MQTT, TLS, TCP, la couche réseau, puis les pilotes. À côté de ce chemin principal se trouve le stockage (SQLite et le journal de télémétrie), et sous le tout la gestion de la mémoire, la découverte de la plateforme et la configuration compilée en constantes au moment de la construction, avec le certificat CA, le certificat du serveur et sa clé intégrés. L'hyperviseur avec ses périphériques virtio est la seule chose qui reste hors de l'image.

Un thread par cœur, aucun état partagé

Modèle d'exécution : chaque cœur de données a sa propre boucle et sa propre part du broker, et le cœur le plus élevé n'exécute que SQLite. Les cœurs communiquent exclusivement par des anneaux SPSC.
Modèle d'exécution : chaque cœur de données a sa propre boucle et sa propre part du broker, et le cœur le plus élevé n'exécute que SQLite. Les cœurs communiquent exclusivement par des anneaux SPSC.

Chaque cœur de données fait tourner sa propre boucle avec sa propre pile TCP, ses états TLS, sa part du broker (sessions et arbre des sujets), son cache de heap et sa partition de télémétrie. Les cœurs ne partagent aucun état modifiable, seulement des anneaux bornés sans verrou, un pour chaque paire ordonnée de cœurs. Le cœur le plus élevé exécute SQLite et rien d'autre.

La carte réseau n'a aujourd'hui qu'une paire de files, détenue par le cœur 0. Il oriente chaque flux MQTT vers l'un des cœurs de travail d'après le hash Toeplitz de son quadruplet, et envoie ce que les cœurs de travail renvoient. Une session vit sur son cœur d'attache, déterminé par le hash de l'identifiant du client, de sorte que la prise de contrôle, les testaments et la persistance ne franchissent jamais les cœurs. Une connexion qui atterrit sur un autre cœur y conserve son TCP et son TLS, et transmet le MQTT en clair au cœur d'attache avec un contrôle de flux fondé sur des crédits. Chaque message routé est en outre diffusé vers les autres parts du broker, dont chacune détient une réplique des messages retenus.

Trois règles maintiennent ce modèle. Traitement de bout en bout : un paquet reçu est traité intégralement sur un seul cœur, du TCP au message chiffré de réponse en passant par le déchiffrement TLS sur place, l'analyse MQTT et le routage, sans files de tâches et sans copie dans des tampons applicatifs. Interrogation adaptative : sous charge, les files de réception sont interrogées activement, et au repos le cœur retourne aux interruptions et à WFI ; les gestionnaires d'interruptions se contentent d'acquitter l'interruption et de marquer le travail. Timer de surveillance : un tick de 10 ms par cœur attrape les gestionnaires qui dépassent leur budget.

La vie d'un PUBLISH

Le chemin d'un message PUBLISH en QoS 1 : de l'enregistrement TLS sur la carte réseau, en passant par le déchiffrement, la vérification des ACL et le routage, jusqu'au PUBACK fusionné avec l'ACK TCP dans un seul segment.
Le chemin d'un message PUBLISH en QoS 1 : de l'enregistrement TLS sur la carte réseau, en passant par le déchiffrement, la vérification des ACL et le routage, jusqu'au PUBACK fusionné avec l'ACK TCP dans un seul segment.

L'appareil envoie un enregistrement TLS. La carte réseau le remet à la boucle du cœur par un descripteur, sans copie. La boucle vérifie le TCP, déchiffre le TLS sur place et analyse le MQTT depuis un tampon emprunté, puis contrôle en mémoire l'ACL, la limite de débit et le chemin dans l'arbre des sujets. Si un autre cœur s'intéresse à ce sujet, il reçoit un message de routage avec une référence au contenu et l'identifiant du sujet ; la base reçoit un enregistrement de télémétrie ou une mise à jour de message retenu, par lots. La réponse PUBACK est chiffrée directement dans le tampon sortant et fusionnée avec l'ACK TCP, de sorte qu'un seul segment part vers l'appareil. Pour un autre cœur, le message ne coûte qu'un chiffrement par abonné au moment de l'envoi.

Ce que coûte une connexion

L'objectif est au plus 512 octets par connexion TLS inactive, avec une ambition de 256. Tant qu'une connexion est au repos, rien n'est conservé en tampon pour elle : les données reçues sont traitées directement depuis la trame réseau, et les tampons d'envoi, d'enregistrement et MQTT n'existent que tant que quelque chose est inachevé ou non acquitté. Le noyau le mesure lui-même : des compteurs exacts du heap, en octets demandés et en blocs occupés, consignés toutes les dix secondes sous la forme du heap au-dessus du niveau de base par connexion ouverte.

Élément Taille Comment
Bloc de contrôle TCP 96 B drapeaux empaquetés, RTT/RTO sur 16 bits, emplacement dans la roue de timers, aucun tampon au repos
Emplacement dans la table TCP 4–8 B simple indice de connexion, table remplie au plus à moitié
Connexion et état d'enregistrement TLS 1.3 152 B secret et séquence par sens (40 B) ; clé et IV dérivés par enregistrement
Connexion MQTT 48 B identité et testament sur le heap uniquement tant qu'ils existent
Session MQTT, forme chaude 64 B un abonnement en ligne, identifiants de nœuds internés
Abonnement devices/<id>/cmd/# 0 nœud d'arbre stocké relativement à l'identifiant de session, commun à tous les appareils

Mesuré sous HVF avec quatre cœurs virtuels, dont trois de travail, chaque appareil tenant une connexion mTLS et un abonnement en QoS 1, arrondis de l'allocateur compris :

Connexions Octets par connexion
960 531 B
9 922 494 B
10 197 474 B

Le pool de trames réseau est fixe, d'environ 2 MiB, soit à peu près 200 B par connexion à dix mille connexions et environ 2 B à un million. Nous n'avons pas pu mesurer davantage : le réseau en mode utilisateur de QEMU sous macOS a cessé d'accepter des connexions à 10 197, à peu près à la limite de 10 240 descripteurs du processus QEMU. Le chiffre diminue à mesure que les parts fixes sont réparties sur davantage de connexions, il ne croît donc pas avec leur nombre.

Le modèle de sécurité

La sécurité est la première priorité et l'emporte sur tous les autres objectifs en cas de conflit. Le modèle comporte plusieurs couches :

  • Un seul port. TCP 8883, uniquement TLS 1.3, certificats clients obligatoires signés par la CA intégrée à l'image. L'identifiant du client MQTT doit être égal à l'identifiant de l'appareil figurant dans le certificat.
  • Aucune entrée. Pas de shell, pas de saisie console, pas de SSH, pas d'API d'administration, pas de code dynamique, pas de SQL accessible par le réseau et pas de système de fichiers.
  • Sûreté mémoire. Rust, avec forbid(unsafe_code) dans les paquets de protocole ; chaque bloc unsafe porte un commentaire // SAFETY: vérifié par un lint, et aucune panique sur une entrée externe : clippy interdit unwrap, expect, l'indexation et panic! dans les paquets du chemin de données.
  • Atténuation de l'exploitation. W^X est appliqué par l'encodage des descripteurs et par le bit SCTLR_EL1.WXN, et audité à chaque démarrage. Après le démarrage, les tables de pages sont en lecture seule, sans alias inscriptible. La moitié basse de l'espace d'adressage est désactivée. Chaque pile a des pages de garde, et les exceptions s'exécutent sur une pile distincte. PAC et BTI sont activés lorsque le processeur les prend en charge, ce que vérifient les tests de fumée.
  • Résistance aux abus. Cookies SYN ; l'admission refuse de nouvelles connexions, avant toute cryptographie, lorsque le heap est bas, que la base prend du retard, que trop de poignées de main sont en cours par cœur ou par adresse source, ou que le débit de poignées de main est trop élevé. Limites de débit par appareil, files bornées, fenêtre TCP liée à la contre-pression du broker, journalisation limitée, timer de surveillance et contrôle de santé.
  • Immuabilité. La configuration, la CA et la clé du serveur sont intégrées à l'image. Changer quoi que ce soit signifie une nouvelle image.

Le modèle de menaces est consigné dans docs/SECURITY.md.

Un stockage sans système de fichiers

Les données vivent sur un disque distinct, virtio-blk ou virtio-scsi, sur lequel il n'y a pas de système de fichiers. La disposition est minimale et propre au projet :

LBA 0      [ superbloc : magic · version de la disposition · uuid · table des régions · CRC-32C ]
LBA 8, 16  [ table des tailles de fichiers, emplacements A/B (le plus récent valide l'emporte) ]
LBA 64     [ base principale SQLite ½ ][ WAL SQLite ⅛ ][ journal 1 MiB ][ journal de télémétrie : 8 partitions … ]
derniers 4 KiB [ zone d'autotest du périphérique bloc au démarrage ]

SQLite (3.54.1 vendorisé, avec vérification du condensat à la construction) est compilé dans le noyau avec clang, sans bibliothèque standard, et utilise un heap memsys5 fixe. Son VFS fait correspondre trois fichiers fixes à des régions du disque. Il fonctionne en mode WAL avec locking_mode=EXCLUSIVE, il n'a donc pas besoin d'index en mémoire partagée, et avec synchronous=FULL ; si quick_check trouve une erreur au démarrage, le système s'arrête en toute sécurité. La base contient le registre des appareils, les révocations, les messages retenus et les sessions persistantes avec des files de messages QoS 1 pour les appareils qui ne sont pas connectés.

Le cœur dédié à la base, celui qui porte le numéro le plus élevé, exécute SQLite et rien d'autre. Le plan de données lui envoie les changements d'état par un anneau SPSC sans verrou ; il les regroupe en une transaction toutes les 5 ms ou toutes les 512 opérations et renvoie les acquittements par un second anneau. Un disque vide est formaté au premier démarrage ; tout le reste n'est jamais reformaté par-dessus.

La télémétrie suit une autre voie. Chaque PUBLISH accepté sur les sujets de storage.telemetry_topics part dans la partition du journal de télémétrie de son cœur : un anneau de segments avec CRC par enregistrement, salé par le numéro de séquence du segment, de sorte que les anciens passages de l'anneau ne réapparaissent jamais. Les cœurs transmettent les enregistrements à la base par leur propre anneau, sans copie, et celle-ci les écrit toutes les 5 ms ou tous les 256 KiB. Au démarrage, la récupération conserve chaque enregistrement écrit, tronque la queue inachevée et reprend dans un nouveau segment. La rétention se fait par la taille, à travers l'anneau, et par l'âge, via retention_days. Nous l'avons vérifié avec un million d'enregistrements et avec kill -9 en pleine écriture.

Les chiffres

L'outil de charge dans tools/loadgen fait trois choses : une tempête de poignées de main TLS 1.3 avec mTLS et CONNECT, des connexions de longue durée avec PINGREQ qui publient de la télémétrie chaque seconde, et des tempêtes de reconnexions. make bench sur un Apple M4 Pro sous HVF donne :

Cœurs de travail poignées de main/s dans l'invité passage à l'échelle via le réseau utilisateur QEMU (connexions/s)
1 862 1,00× 789
2 1 724 2,00× 636
4 3 438 3,99× 536

Le chiffre dans l'invité, où le client et le serveur se trouvent dans la même machine virtuelle et où chaque cœur de travail fonctionne seul de son côté, montre que le plan de données passe à l'échelle de façon linéaire. Le chiffre réseau est limité par le réseau en mode utilisateur monothread de QEMU, et non par l'invité ; cela aussi est consigné, sous la référence ADR-032.

Nous testons les tempêtes de reconnexions en connectant tous les appareils en même temps, en répétant avec un délai les tentatives refusées, puis en les faisant tous tomber sans DISCONNECT. Le paramètre heap_limit_mb= en mode développement force l'admission à considérer le heap plus petit qu'il ne l'est, ce qui permet de tester une tempête plus grande que le heap avec peu de sockets. Lors d'un tour de ce type avec 3 000 appareils, 1 368 se sont connectés en 36 secondes, et 5 619 tentatives ont été proprement refusées, avec une limite de 8 MiB ; la mémoire est revenue à son niveau après la tempête.

Notre façon de travailler

Le projet est piloté par une spécification écrite, PROMPT.md, avec un plan de jalons, et chaque jalon se termine par le même rituel : make check et make smoke au vert, un commit portant l'étiquette M<n>, une entrée dans docs/PROGRESS.md et un ADR pour chaque décision qui n'est pas évidente. Les règles sont courtes et non négociables : pas de panique sur une entrée externe, unsafe uniquement dans les paquets matériels et FFI et toujours avec un commentaire, pas d'allocations sur le chemin chaud, un commentaire pour chaque ordre d'opérations atomiques, chaque limite provient de la configuration, et les éléments provisoires volontaires échouent bruyamment.

La CI fait plus que ce que nous pourrions faire à la main : fmt, clippy pour l'hôte et pour aarch64-unknown-none, typos, actionlint et shellcheck ; des tests sous Linux et macOS sur arm64 ; la construction du noyau pour la configuration de développement et de production avec la taille de l'ELF dans le résumé ; le démarrage sous QEMU avec tous les scénarios de test de fumée et la conservation des journaux série ; cargo-deny pour les avis, les licences et les sources ; une minute de fuzzing sur chacune des onze cibles, et la nuit dix minutes par cible avec un corpus mis en cache. S'y ajoutent CodeQL, OpenSSF Scorecard, Dependabot et une publication avec provenance SLSA. Chaque action tierce est épinglée sur un SHA de commit complet.

Nous prenons la reproductibilité au pied de la lettre : make repro construit HEAD deux fois dans des arbres de travail neufs et compare l'ELF, l'ISO et l'image brute octet par octet. Les publications sont signées avec minisign, et les artefacts peuvent être vérifiés avec gh attestation verify. L'image de production contient la clé privée du serveur, l'ISO lui-même est donc un secret ; les images jointes aux publications sur GitHub sont des images de développement, avec des clés temporaires, et ne servent qu'à essayer le projet.

Ce que nous avons appris

Quelques leçons qui ont changé le plan :

  • Un TCP maison s'est finalement avéré rentable. Le prototype fonctionnait avec smoltcp et nous a bien servi jusqu'à M13. Mais un serveur qui tient des centaines de milliers de connexions n'a pas besoin d'un TCP client : il lui faut des cookies SYN, une roue de timers et un état de 96 octets. Avec M14, nous avons remplacé smoltcp par notre propre pile uniquement côté serveur, avec des tests de perte simulée et du fuzzing de segments.
  • Mesurez la mémoire de l'intérieur. Les estimations par connexion étaient inutilisables tant que le noyau ne s'est pas mis à compter lui-même le heap en blocs de l'allocateur plutôt qu'en octets demandés. C'est alors seulement que nous avons vu où partent réellement les 512 octets, et nous sommes descendus à 494.
  • L'outil de test fait partie du produit. La plus grande limite n'était pas le noyau, mais le réseau en mode utilisateur de QEMU et le nombre de ports éphémères sur la machine de développement. Pour cent mille connexions et plus, il faut vmnet-shared sous macOS ou Linux avec KVM, une interface tap et plusieurs adresses clientes.
  • SQLite sans système d'exploitation est possible, mais seulement à des conditions strictes : un heap fixe, un verrouillage exclusif sans mémoire partagée, un VFS qui ne connaît pas les fichiers, et une admission qui ne laisse pas la base prendre du retard.
  • Les tempêtes de reconnexions sont l'état normal, pas l'exception. Quand le réseau mobile tombe puis revient, des milliers d'appareils frappent à la porte en même temps. C'est pourquoi l'admission refuse la connexion avant toute cryptographie, et chaque connexion refusée ne coûte presque rien.

La suite

Le travail après M17 est prévu dans trois directions, et l'ordre entre elles est consigné dans docs/PLAN.md. Rien de tout cela n'est encore construit.

  • Un seul ISO pour plusieurs plateformes. Un même fichier urmqtt-<env>-<version>.iso et une image GPT brute qui démarrent sur UEFI aarch64, UEFI x86_64 et BIOS x86_64, dans des machines virtuelles et sur du matériel réel, en commençant par un PC Intel. Toujours une appliance avec tout à l'intérieur, reproductible et signée.
  • Un disque brut, sans système de fichiers. Une spécification du format au niveau de l'octet, un outil hôte urmqtt-disk qui partage les analyseurs avec le noyau, un alignement des régions sur 1 MiB, la croissance du disque avec les données, discard pour les disques à allocation dynamique, des instantanés A/B de la base, une vérification des bits et, en option, un chiffrement AES-256-XTS avec AES matériel.
  • Sûreté démontrable. Révocation et liste d'appareils autorisés à l'exécution par un canal de contrôle signé, sans nouvelle image ; tests différentiels et de conformité de notre TLS et de notre TCP ; KASLR, protection de la pile, kCFI, MTE et un allocateur durci ; SQLite isolé au niveau EL0 ; Secure Boot ; SBOM et cargo-vet ; et enfin un dossier pour un audit externe.

Les plateformes qui ne proposent que des périphériques non virtio, comme AWS ENA ou Azure MANA, ne sont pas prises en charge pour l'instant. Raspberry Pi 4 et 5 comme hôte KVM, Hetzner CAX et Oracle Ampere A1 sont documentés et pris en charge dans le code, mais n'ont pas encore été essayés sur un vrai service.

Essayez-le

Le dépôt se trouve sur github.com/zeljan-alduk/urMQTT. Avec Rust et sa chaîne d'outils nightly épinglée, ainsi que QEMU, make certs, make run et un client mosquitto suffisent pour un premier PUBLISH sur mTLS sur votre propre machine. L'avertissement du début vaut aussi pour la fin : il s'agit d'un pré-alpha, et tout ce qui fonctionne est consigné dans docs/PROGRESS.md. Les rapports de bogues, les questions de conception et les signalements de sécurité, selon les indications de SECURITY.md, sont les bienvenus. Nous reviendrons ici à chaque nouveau jalon.

Commentaires

Règles des commentaires

Règles des commentaires

Les commentaires servent à partager expériences et questions sur les sujets du blog. Ces règles gardent la discussion utile pour tous.

  1. Courtoisie Pas d'insultes, de menaces, de harcèlement, de discours de haine ni de discrimination, quel qu'en soit le motif. Critiquez l'argument, pas la personne.
  2. Rester dans le sujet Un commentaire doit porter sur l'article ou sur la discussion qui le suit.
  3. Ni publicité ni spam Pas de publicité, de chaînes de messages ni de liens sans contexte. Les liens et images sont bienvenus quand ils enrichissent la discussion.
  4. Vie privée d'autrui Ne publiez pas de données personnelles d'autres personnes : noms complets de particuliers, adresses, téléphones, e-mails, photos sans consentement.
  5. Droits d'auteur Ne publiez que du contenu que vous avez le droit de partager. En citant, indiquez la source.
  6. Une seule identité Utilisez un seul pseudo et ne vous faites pas passer pour quelqu'un d'autre.
  7. Confirmation par e-mail Un commentaire n'est publié qu'après confirmation du lien envoyé par e-mail. L'adresse n'est pas affichée ni utilisée à d'autres fins ; les commentaires non confirmés sont supprimés après 48 heures.
  8. Modération Les commentaires contraires aux règles sont masqués ou supprimés sans préavis, et une discussion peut être verrouillée. Les décisions des modérateurs sont définitives.
  9. Responsabilité L'auteur est responsable du contenu de son commentaire. Les commentaires ne reflètent pas les positions d'ALDO TECH.
  10. Signalement Signalez tout contenu inapproprié à info@aldo.tech.