La limitation de débit MQTT est la manière dont un broker se protège, et protège ses utilisateurs, contre les clients qui envoient trop, trop vite ou trop gros. Cet article explique les principaux schémas de déni de service sur MQTT, les limites que les brokers leur opposent, la façon dont ConnectMQ applique quotas et protection contre le flooding, et comment écrire des clients qui temporisent au lieu d'aggraver le problème. La plupart des incidents liés aux limites de débit sont causés par des firmwares bogués plutôt que par des attaquants ; les deux côtés comptent donc.
Pourquoi MQTT a besoin d'une limitation de débit
Un broker MQTT est une ressource partagée. Chaque connexion coûte de la mémoire, chaque message coûte du CPU et de la bande passante, et chaque abonnement ajoute du travail de routage. Sans limites, un seul client défaillant peut dégrader le service pour tous les autres clients du même broker.
La menace n'est pas seulement malveillante. Parmi les causes courantes de surcharge :
- Un bug de firmware qui publie dans une boucle serrée au lieu d'une fois par minute.
- Une boucle de reconnexion sans délai après un incident réseau, répétée sur tout un parc.
- Une caméra ou un enregistreur qui publie par erreur de gros blobs binaires.
- Un tableau de bord qui s'abonne à
#sur un broker chargé et démultiplie chaque message. - Des tentatives de devinette d'identifiants qui ouvrent connexion sur connexion.
La limitation de débit transforme ces défaillances en erreurs circonscrites et visibles plutôt qu'en pannes.
Les principaux schémas de DoS sur MQTT
| Schéma | Ce qui se passe | Défense typique |
|---|---|---|
| Flood de connexions | De nombreuses connexions TCP/TLS ouvertes rapidement, chacune coûtant une négociation et de la mémoire | Limites de débit de connexion par IP, nombre maximal de connexions par tenant, blocage d'IP |
| Tempête de reconnexions | Des milliers d'appareils se reconnectent au même moment après une panne | Backoff exponentiel avec jitter côté client |
| Tempête de publications | Un client envoie bien plus de messages par seconde que prévu | Limites de messages par client, déconnexion en cas d'abus |
| Payloads surdimensionnés | D'énormes messages consomment mémoire et bande passante | Taille maximale de paquet |
| Explosion d'abonnements | Un client crée de nombreux abonnements ou des wildcards larges | Nombre maximal d'abonnements par client, limites de profondeur de topic |
| Gonflement des retained ou des sessions | De nombreux topics retenus ou des sessions longues avec des messages en file | Limites de retained messages, session expiry |
| Consommateurs lents | Des abonnés qui ne suivent pas font grossir les files d'attente | Limites de file, choix de QoS, expiration des messages |
| Devinette d'identifiants | Échecs de connexion répétés | Ralentissement des échecs de connexion et blocage d'IP |
Comment les brokers appliquent les limites
Limites au niveau des connexions
L'endroit le moins coûteux pour arrêter un abus se situe avant l'authentification du client. Les brokers limitent le nombre de connexions qu'une même IP peut ouvrir par seconde, plafonnent le total des connexions par compte ou par offre et bloquent les sources qui échouent à répétition à l'authentification. Une négociation TLS est relativement coûteuse ; freiner à ce stade protège donc le CPU autant que la mémoire.
Limites au niveau des messages
Une fois le client connecté, le broker peut suivre les messages par seconde et les octets par seconde de chaque client. Lorsqu'un client dépasse son budget, le broker a plusieurs options : retarder le traitement, ignorer des messages, les refuser avec un reason code ou déconnecter le client. MQTT 5 rend tout cela bien plus transparent que MQTT 3.1.1.
Limites de taille de paquet
Chaque broker possède une taille maximale de paquet. MQTT 5 permet aux deux côtés de l'annoncer : le broker envoie Maximum Packet Size dans le CONNACK, et les clients ne doivent jamais envoyer davantage. Un message dépassant la limite est une erreur de protocole et entraîne généralement une déconnexion.
Limites d'abonnements et de topics
Les plafonds d'abonnements par client et de profondeur de topic empêchent les clients de créer un travail de routage illimité.
Les fonctionnalités de MQTT 5 qui aident
MQTT 5 a ajouté plusieurs mécanismes qui rendent les limites explicites plutôt que mystérieuses. La présentation des fonctionnalités de MQTT 5 les détaille davantage.
- Reason codes. Un broker peut répondre
0x97 Quota exceededdans un PUBACK ou un DISCONNECT, ou0x96 Message rate too high, au lieu d'ignorer silencieusement les données. Les clients peuvent journaliser le motif et ralentir. - Receive Maximum. Les deux côtés annoncent combien de messages QoS 1 et QoS 2 non acquittés ils acceptent en transit, ce qui fournit un contrôle de flux de base.
- Maximum Packet Size. Annoncé dans le CONNECT et le CONNACK pour que les messages surdimensionnés soient évités plutôt que rejetés.
- Expiration des messages. Les anciens messages sont supprimés au lieu de s'accumuler pour les abonnés hors ligne.
- Session expiry. Les sessions abandonnées sont nettoyées automatiquement.
- Server Keep Alive. Le broker peut imposer sa propre valeur d'intervalle keep-alive au client.
Avec MQTT 3.1.1, un broker qui applique des limites ne peut généralement qu'ignorer des messages ou fermer la connexion, et le client ne voit qu'une erreur réseau générique.
Les limites sur ConnectMQ
ConnectMQ, un broker MQTT managé, applique des limites à plusieurs niveaux pour protéger les tenants de ses clusters partagés :
- Quotas par offre. L'offre gratuite Serverless autorise jusqu'à 25 connexions, une QoS jusqu'à 1 et des messages de 256 Ko. L'offre Serverless payante porte cela à 1 000 connexions, QoS 2 et des messages de 1 Mo. Les déploiements Dedicated et BYOC fonctionnent sur une infrastructure isolée.
- Signalement des quotas. Lorsqu'un quota est dépassé, les clients MQTT 5 reçoivent le reason code 0x97 (quota exceeded), ce qui vous permet de distinguer une limite d'une défaillance réseau.
- Protection contre le flooding face aux schémas de connexions et de messages abusifs.
- Blocage d'IP après des échecs de connexion répétés.
- Isolation des tenants, pour que le trafic d'un tenant ne puisse pas atteindre les topics d'un autre.
Le serveur MQTT public gratuit, broker.connectmq.com, réservé aux tests et aux prototypes, applique des limites par client plus strictes, car il est ouvert à tous : 64 Ko par message, 10 messages par seconde par client, 10 abonnements par client, 8 niveaux de topic et une session expiry de 30 minutes au maximum. Si un test atteint ces limites, c'est un signe clair qu'il faut déplacer la charge vers un broker privé. L'article consacré au serveur MQTT public gratuit en donne le détail.
Écrire des clients qui respectent les limites
Les limites côté serveur ne sont que la moitié de l'histoire. Un parc de clients bien élevés est la meilleure protection contre les DoS que vous puissiez avoir.
- Temporiser à la reconnexion. Utilisez un backoff exponentiel avec un jitter aléatoire pour que les appareils ne se reconnectent pas tous en même temps après une panne.
- Regrouper et agréger. Envoyez un message contenant dix mesures plutôt que dix messages, lorsque la latence le permet.
- Publier sur changement, pas à chaque boucle. Ajoutez des bandes mortes pour que de minuscules fluctuations ne génèrent pas de trafic.
- Respecter la taille de paquet annoncée. Lisez
Maximum Packet Sizedans les propriétés du CONNACK. - Gérer les reason codes. Journalisez 0x97 et 0x96 et réduisez votre débit de publication au lieu de réessayer immédiatement.
- Garder des abonnements étroits. Abonnez-vous aux topics dont vous avez besoin, pas à
#. - Utiliser des client IDs uniques. Deux appareils partageant un ID s'éjectent mutuellement dans une boucle sans fin qui ressemble à un flood.
Voici un client Python utilisant paho-mqtt 2.x qui applique un backoff, gère les reason codes de quota et limite son propre débit de publication :
import json
import random
import time
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 8883
CLIENT_ID = f"rate-demo-{random.randint(1000, 9999)}"
TOPIC = f"connectmq/demo/{CLIENT_ID}/telemetry"
MAX_RATE = 2.0 # messages par seconde, bien en dessous de la limite du broker public
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID,
protocol=mqtt.MQTTv5)
client.tls_set()
# Pour un broker privé, ajouter : client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.reconnect_delay_set(min_delay=1, max_delay=120) # backoff exponentiel
slow_down_until = 0.0
def on_connect(c, userdata, flags, reason_code, properties):
print("connected:", reason_code)
if properties and hasattr(properties, "MaximumPacketSize"):
print("broker max packet size:", properties.MaximumPacketSize)
def on_publish(c, userdata, mid, reason_code, properties):
global slow_down_until
if reason_code.is_failure:
print(f"publish {mid} refused: {reason_code}")
if reason_code.value in (0x96, 0x97):
slow_down_until = time.time() + 30 # pause de 30 s
def on_disconnect(c, userdata, flags, reason_code, properties):
print("disconnected:", reason_code)
client.on_connect = on_connect
client.on_publish = on_publish
client.on_disconnect = on_disconnect
client.connect(HOST, PORT, keepalive=60)
client.loop_start()
interval = 1.0 / MAX_RATE
try:
while True:
if time.time() < slow_down_until:
time.sleep(1)
continue
payload = json.dumps({"t": round(20 + random.random(), 2), "ts": int(time.time())})
client.publish(TOPIC, payload, qos=1)
time.sleep(interval)
except KeyboardInterrupt:
client.loop_stop()
client.disconnect()
Les mêmes principes s'appliquent au firmware. Sur un ESP32, ajoutez un délai avec jitter avant chaque tentative de reconnexion et n'appelez jamais publish() depuis une boucle sans minuterie ; le tutoriel MQTT ESP32 présente un modèle non bloquant.
Surveiller les problèmes de limitation de débit
Guettez ces signaux dans les journaux de vos clients et les tableaux de bord du broker :
- Des pics de déconnexions sur de nombreux appareils au même moment.
- Des reason codes 0x97 ou 0x96 répétés provenant du même client.
- Des appareils qui se connectent, publient et se déconnectent selon un cycle de quelques secondes.
- Un débit de messages qui ne correspond pas à l'intervalle de remontée configuré.
Sur ConnectMQ, le tableau de bord du portail et l'explorateur de topics en direct vous aident à repérer un client trop bavard, et $SYS/broker/connection/{clientId}/state vous permet de suivre l'état de connexion d'un client donné.
Quand passer à une offre supérieure
Si c'est un trafic légitime, et non un bug, qui atteint les limites, la réponse est la capacité plutôt que des clients plus bridés. Les signes incluent une croissance régulière du nombre d'appareils connectés, un besoin de QoS 2 ou des payloads supérieurs à 256 Ko. Le guide du broker MQTT managé explique en quoi les options serverless et dédiées diffèrent, et l'article associé sur les brokers MQTT exposés montre pourquoi des brokers auto-hébergés sans limites représentent un risque.
Démarrer avec ConnectMQ
Créez un broker sur l'offre gratuite Serverless sur app.connectmq.com, sans carte bancaire, connectez le client ci-dessus avec vos identifiants générés et observez en pratique le comportement des quotas et des reason codes. Pour des conseils plus larges, lisez les bonnes pratiques de sécurité MQTT.
Questions fréquentes
Que signifie le reason code MQTT 0x97 ?
Il signifie « Quota exceeded » (quota dépassé). Le broker indique au client qu'il a dépassé une limite autorisée ; le client doit donc ralentir plutôt que réessayer immédiatement.
MQTT 3.1.1 prend-il en charge un retour sur les limites de débit ?
Pas directement. MQTT 3.1.1 ne dispose pas de reason codes dans le PUBACK ; un broker ne peut donc qu'ignorer des messages ou déconnecter. Passez vos clients à MQTT 5 pour recevoir un retour explicite.
Faut-il aussi limiter le débit sur l'appareil ?
Oui. La limitation côté client, le regroupement des messages et le backoff de reconnexion évitent la plupart des incidents et vous maintiennent largement sous les limites du broker.
Qu'est-ce qu'une tempête de reconnexions ?
Elle se produit lorsque de nombreux appareils perdent la connexion au même moment et réessaient tous immédiatement. Un backoff exponentiel avec jitter aléatoire étale les reconnexions dans le temps.



