J’ai activé le roaming rapide, Windows a donc collectionné quinze refus d’association

Deux bornes MikroTik, CAPsMAN, 802.11k/v/r/w et un client Windows ont transformé un trajet chambre-bureau en six secondes de silence ; un SSID isolé a ramené la transition radio à 20 ms.

Partager
Un portable traverse un couloir entre deux bornes qui lui tendent le réseau, mais un guichet de sécurité tamponne une pile de quinze refus avant de lever la barrière.

Le roaming Wi-Fi possède la même promesse que beaucoup de protocoles domestiques : plusieurs appareils portent le même nom, donc l’utilisateur ne devrait jamais remarquer lequel travaille. En pratique, une borne ne transmet pas un client à sa voisine comme une antenne cellulaire. Elle diffuse des indices, suggère parfois une destination et attend que le client décide de partir. L’ordinateur reste souverain, y compris lorsqu’il exerce sa souveraineté en conservant un lien à −83 dBm face à une borne voisine à −45 dBm.

L’IEEE a pourtant livré une boîte à outils complète. 802.11k, publié en 2008, mesure et annonce le voisinage. 802.11r, publié la même année, accélère le changement de BSS en transportant une hiérarchie de clés. 802.11w a ensuite protégé les trames de gestion, puis 802.11v, en 2011, a enrichi la gestion du réseau et ses suggestions de transition. Quinze ans plus tard, les quatre normes fonctionnent très bien ensemble. Elles produisent seulement des résultats surprenants lorsque chaque acteur en choisit trois et demie.

À la maison, deux bornes MikroTik cAP ax couvraient la chambre et le bureau sous CAPsMAN. Une troisième desservait encore un autre sous-réseau. Les radios étaient volontairement sur 5 200 MHz en 20 MHz : une seule fréquence, une puissance limitée et aucun plan de canaux à inventer pour transformer une question de fiabilité en concours de coloriage spectral. ft, ft-over-ds et le domaine de mobilité 0xCAFE étaient actifs.

Le portable Windows, lui, perdait le réseau pendant les trajets. Le problème a commencé comme une plainte unique. Il contenait en réalité deux pannes, un commutateur matériel qui avalait les trames et un contrôleur qui protégeait si bien l’ancienne session qu’il refusait la nouvelle.

Le premier problème de roaming était un commutateur qui mangeait l’unicast

En arrivant au bureau, le portable affichait un excellent signal et aucun Internet. L’authentification SAE réussissait, l’association rendait le statut zéro et la poignée EAPOL en quatre temps se terminait en 170 ms. L’appareil obtenait son adresse puis demandait chaque seconde qui possédait la passerelle. La requête ARP partait ; aucune réponse ne lui revenait.

Le contraste diffusion/unicast a séparé le chemin :

diffusion vers le portable       passe
unicast venant du portable       passe
unicast destiné à sa MAC         disparaît

Un téléphone témoin, sur la même radio, la même borne et le même port Ethernet, recevait ses paquets. La box envoyait bien les trames vers la borne. Sur une salve de 600 paquets, 640 entraient dans la carte Ethernet du point d’accès, 60 seulement remontaient au pilote et 33 atteignaient la radio. La puce de commutation en avait absorbé environ 580 dans l’espace situé entre un compteur matériel et tous les instruments logiciels.

La capture initiale accusait la box parce qu’elle ne montrait aucune trame. Elle mentait par omission : le hw-offload traitait les paquets sous le point d’écoute du sniffer. Les compteurs de la box, de l’interface, du pilote, du pont et de la radio, confrontés au téléphone témoin, ont replacé la disparition dans la puce IPQ-PPE.

hw=no sur le port du pont a rendu le trafic descendant en neuf secondes. Sur trois allers-retours supplémentaires, sept arrivées et 49 échantillons, aucune nouvelle arrivée n’a présenté un descendant nul.

La cause datait d’anciens essais de fast roaming : protocol-mode=none, ageing-time=1h et un nombre d’entrées illimité avaient été posés à la main sur une seule borne. Le premier réglage activait l’offload matériel. MikroTik documentait pourtant le support du pont IPQ-PPE comme encore incomplet et recommandait le pont logiciel avec RSTP. Les trois réglages avaient créé une asymétrie ; un seul provoquait la panne. RSTP et hw=no sont désormais deux gardes déclarées dans OpenTofu.

Le portable recevait à nouveau les paquets. Il lui restait à accepter de changer de borne avant d’atteindre l’équivalent radio d’un dernier souffle.

802.11r accélérait une décision que personne n’aidait à prendre

Le fast transition ne choisit pas la prochaine borne. Il raccourcit l’authentification une fois la cible choisie. Aucun profil de steering n’existait ; sans neighbor-group, les bornes ne savaient même pas lesquelles annoncer comme voisines.

Un profil a donc relié les deux BSSID du même SSID, avec RRM et WNM actifs. 802.11k donnait une carte de voisinage pour limiter les balayages ; 802.11v permettait une BSS Transition Management Request. Le client conservait le dernier mot, puisque la documentation MikroTik du steering décrit des candidats annoncés, pas une téléportation forcée.

Seize heures après la pose, le journal a produit ceci :

13:04:26  connecté au bureau, −41 dBm
14:35:40  perdu à −68, repris à −73
14:36:14  perdu à −69, repris à −72
14:36:19  perdu à −72, repris à −72
14:36:24  perdu à −74, repris à −69
14:36:30  perdu à −72
14:37:45  enfin associé dans la chambre à −46

Deux minutes et six secondes, six cycles, aucune ligne roamed to. Le steering était bien déclaré ; son effet restait invisible. RouterOS ne journalise ni les rapports 11k ni les suggestions 11v, et son sniffer ordinaire retire l’en-tête 802.11. Une capture en mode moniteur devenait nécessaire.

Elle a aussi trouvé un détail plus définitif. Sur la même radio, à la même seconde, un téléphone Android s’authentifiait en ft-wpa3-psk et le portable en wpa3-psk. Les balises annonçaient SAE et FT-SAE ; le domaine de mobilité fonctionnait. Windows refusait simplement l’AKM FT.

La documentation Microsoft fermait la piste : Windows prend en charge 802.11r sur les réseaux 802.1X, pas sur les réseaux PSK ni ouverts. La carte Intel savait faire 802.11r. Le système avait choisi de ne pas l’employer avec WPA3-Personal. Oublier le profil, changer de pilote ou revenir à WPA2-PSK aurait donc produit exactement le même AKM, avec éventuellement moins de sécurité pour occuper l’après-midi.

Vingt et une demandes ont toutes choisi le chemin lent

La première capture radio a duré trois minutes. Elle a surpris 21 demandes d’association du portable : 21 fois l’AKM 8 pour SAE, jamais l’AKM 9 pour FT-SAE. Quatre retours vers une borne déjà visitée portaient un PMKID et passaient par une authentification Open, signe que le cache PMKSA économisait l’échange SAE. Aucune borne inconnue ne recevait de PMKID ; l’espoir d’un cache opportuniste partagé entre bornes s’arrêtait donc côté client.

La surprise se trouvait dans les réponses. Douze des 25 réponses d’association portaient le statut 30, rejet temporaire. Une seconde capture, avec le téléphone Android comme témoin FT dans le même trajet, a rendu le tableau moins subtil :

portable Windows sans FT   15 refus en statut 30
téléphone Android en FT     0 refus

Trois déplacements du portable ont coûté quatre, cinq et six secondes de réseau mort. Les transitions du téléphone ont été acceptées du premier coup. Le même SSID, les mêmes deux radios et la même capture excluaient un mauvais domaine de mobilité ou une panne générale de CAPsMAN.

Le PMKID ne sauvait pas le portable. Il évitait de redériver la PMK mais la demande restait une ré-association complète. FT, lui, transportait les noms de clé de la hiérarchie existante dans l’authentification et la ré-association ; la continuité de session était prouvée avant l’ouverture du nouveau lien.

La nouvelle borne demandait à l’ancienne si le client était vraiment parti

Une demande d’association initiale ne peut pas être protégée par les clés de la session qu’elle cherche à créer. Avec WPA3-Personal, réussir SAE prouve que la station connaît la passphrase commune, pas qu’elle détient la PTK de l’ancienne session. Un participant autorisé qui usurpe une MAC connaît lui aussi cette passphrase.

CAPsMAN trouvait donc la même MAC encore active sur l’ancienne radio. Il refusait temporairement la cible, puis demandait à l’ancien lien de prouver qu’il vivait encore avec une SA Query protégée. La capture a rendu le dialogue trame par trame :

portable → nouvelle borne   SAE Commit + Confirm, statut 0
portable → nouvelle borne   Reassociation Request
nouvelle borne → portable   Reassociation Response, statut 30
ancienne borne → portable   SA Query Request, six salves espacées de 200 ms
portable → ancienne borne   aucune réponse
portable → nouvelle borne   nouvel essai, statut 0

La SAE avait réussi huit millisecondes avant le refus. Ce n’était pas un échec d’identité ; c’était une protection contre une seconde session portant la même MAC. Le champ Current AP Address de la demande nommait même explicitement la borne quittée. Le gestionnaire possédait les deux radios, la session antérieure et l’identité fraîchement prouvée. Il préférait néanmoins interroger l’ancien lien, parce que son contrat par défaut lui demandait précisément cela.

La SA Query terminait ses six salves en environ une seconde. Windows grillait quatre tentatives en 254 ms, puis se taisait 5,9 s. La borne était prête à t+1 s ; le client revenait vers t+6 s. Environ 85 % de la coupure venait du temporisateur de repli du portable.

Les réponses d’association ordinaires incluaient un Association Comeback Time d’environ 0,6 à 0,8 s. Les réponses de ré-association, celles qu’un client en déplacement emploie justement, l’omettaient systématiquement. Sans rendez-vous, Windows appliquait son propre délai. RouterOS accordait donc une heure de retour à ceux qui arrivaient et la refusait à ceux qui revenaient — une petite attention sémantique dont seul un pcap pouvait mesurer l’humour.

Le paramètre existait, il ne portait simplement aucun nom évoquant le roaming

J’ai affirmé trois fois qu’aucun réglage ne permettait d’éviter cette SA Query. J’avais parcouru les champs dont le nom parlait de délai, de PMF ou de roaming. Le mécanisme vivait sous connect-group et connect-priority.

La documentation RouterOS décrit le contrat : les AP d’un même groupe n’acceptent pas simultanément deux clients portant la même MAC, afin de limiter MacStealer et le déni de service par usurpation. Avec les priorités par défaut, accept == hold ; la nouvelle association n’est admise que si la MAC n’est plus joignable sur l’ancien AP. Les six SA Query mesuraient exactement cette injoignabilité.

Le réglage connect-priority = 0/1 inverse l’arbitrage : la priorité d’acceptation de la nouvelle borne devient supérieure à la priorité de conservation de l’ancienne, donc l’arrivée fait tomber l’ancienne session immédiatement. La prédiction était nette : les statuts 30 devaient disparaître.

L’appliquer au SSID familial entier aurait retiré la protection MacStealer à tous les clients. Le défaut concernait un portable Windows ; affaiblir le groupe complet pour lui aurait été la version réseau de couper le détecteur de fumée parce qu’un grille-pain est susceptible. La granularité disponible était le profil de sécurité, donc le SSID, pas une entrée d’access-list par appareil.

Le contournement est devenu un SSID dédié, avec une passphrase distincte, son propre connect-group et connect-priority = 0/1. Le portable y est seul. Un attaquant capable d’ouvrir une seconde session dans ce groupe devrait déjà connaître sa passphrase spécifique ; l’isolation remet donc le risque à la bonne taille. FT reste annoncé afin qu’un futur client capable ne perde pas la fonction, et le steering 11k/11v reste présent afin que rendre la transition rapide ne rende pas sa découverte lente.

Le prix existe : un BSSID de plus par radio, des balises supplémentaires et un troisième SSID dans un parc qui cherche à revenir à un seul. C’est un réseau de contournement, pas une nouvelle destination par défaut.

OpenTofu a créé la règle, puis absolument aucune interface

Le modèle du parc a dû apprendre qu’un SSID n’est ni « principal » ni « virtuel ». Un catalogue définit chaque SSID avec sa sécurité, son domaine de mobilité, son groupe de voisinage et son groupe de connexion ; chaque radio référence ensuite la liste des SSID qu’elle sert. La traduction choisit seule le profil maître imposé par RouterOS et les interfaces esclaves. Une contradiction entre deux copies du même SSID devient ainsi inexprimable.

tofu apply a correctement ajouté le profil et la règle de provisionnement. Aucune interface n’est apparue. CAPsMAN ne rejoue pas automatiquement le provisionnement lorsqu’une règle change ; il faut demander explicitement /interface/wifi/capsman/remote-cap/provision pour chaque CAP.

La borne distante a alors créé son interface. Le gestionnaire n’a rien créé sur sa propre radio : CAPsMAN ne la gère pas, elle reste en manager=local. La panne aurait été parfaite — un SSID de roaming présent sur une seule borne, avec une règle et un apply verts. Une interface locale explicite a donc été ajoutée sur la radio du gestionnaire, tandis que la même entrée du catalogue alimente le provisionnement des CAP. Si le rôle de gestionnaire déménage dans la topologie, la dérivation déplace aussi ce second chemin.

Terraform impose des références de provider statiques, donc trois blocs existent pour trois hôtes possibles au lieu d’une boucle. Le choix reste dérivé ; seule la syntaxe paie le prix de cette honnêteté.

Le nouveau SSID associait parfaitement le portable à aucun réseau

Les deux BSSID émettaient enfin. Le portable s’authentifiait, apparaissait dans la table d’enregistrement avec un bon signal et ne recevait aucun paquet. Deux réglages distincts manquaient :

datapath.bridge   dans quel pont vont les clients du profil
slaves-datapath   dans quel pont la CAP place les interfaces créées

Le premier seul ne suffisait pas. slaves-datapath n’acceptait pas un nom de pont ; il attendait un objet datapath nommé. Sans lui, l’interface esclave existait, montait et restait hors de tout pont. Les journaux disaient connected avec la sérénité habituelle des couches qui ont terminé leur part du désastre.

Une asymétrie a ensuite attiré mon regard : les ports Wi-Fi du gestionnaire étaient dynamiques, ceux de la CAP statiques. J’ai retiré les ports statiques, convaincu de supprimer des reliquats. Le nouveau SSID est resté hors pont et le SSID existant a cessé de fonctionner sur cette borne. L’hypothèse venait d’être invalidée avec une conséquence très pédagogique.

Le port de la radio maîtresse d’une CAP doit être statique. Le slaves-datapath ne couvre que les interfaces esclaves créées par le gestionnaire. Celles-ci doivent au contraire avoir des ports dynamiques, parce que leur nom change à chaque re-provisionnement : wifi3, wifi4, wifi5, jusqu’à ce qu’un futur script rencontre un jour une interface appelée -virtual1 et décide de l’interpréter comme une option.

La configuration finale conserve donc le port statique de la maîtresse et fait créer les ports dynamiques des esclaves par le datapath. L’objet qui ressemblait le plus à une dette manuelle était en réalité porteur. Il a été importé dans OpenTofu au lieu d’être supprimé une seconde fois pour confirmer.

Quinze refus sont devenus vingt millisecondes

La prédiction avait été écrite avant l’essai : zéro statut 30 et une ligne roamed to signifieraient que connect-priority était bien la cause. Le portable a traversé le logement sur le SSID isolé. Le journal a rendu roamed to dans les deux sens, sans un seul statut 30. L’AKM restait wpa3-psk ; Windows n’avait toujours pas appris 802.11r.

Une capture radio et une capture Ethernet simultanées ont fermé la chaîne :

t+0,00  authentification Open, PMKID présenté
t+0,01  demande de ré-association SAE, ancienne BSSID nommée
t+0,02  réponse statut 0
t+0,13  ancienne borne : désauthentification

La transition 802.11 prenait 20 ms, contre quatre à six secondes avant. Sur le fil, le canal CAPsMAN en ethertype 0x88bb produisait habituellement 10 à 12 trames toutes les trois secondes. Les deux changements ont créé des pics de 16 et 24 trames, alignés à la seconde avec les deux roamed to. Le contenu du protocole propriétaire restait opaque ; sa forme suffisait à dater la décision.

Le signal d’arrivée valait −36 puis −35 dBm. Le portable ne descendait plus à −75 avant de partir, mais deux transitions ne prouvent pas que 11k ou 11v a causé ce départ précoce. Les trames d’action sont protégées par PMF et leur catégorie devient illisible dans la capture. Le coût de la transition est clos ; le mécanisme exact qui en choisit l’instant reste ouvert.

Une coupure d’environ une seconde a été ressentie au premier essai, alors que le pcap bornait la radio à 20 ms. Elle vivait donc plus haut — pile Windows, ARP ou réapprentissage de MAC — et n’a pas été isolée par la sonde à 200 ms. Un speedtest maintenu pendant le trajet n’a ensuite montré aucun accroc. La différence est cohérente avec un commutateur qui réapprend sur la première trame montante ; elle n’en constitue pas la preuve.

Le réseau fonctionne, ce qui laisse enfin distinguer ce qu’on ignore

Le trajet utile est maintenant sans coupure observable sous charge. Les informations à garder pour le refaire tiennent à plusieurs couches qu’aucun bouton « fast roaming » ne rassemble : 11k et 11v aident à choisir, 11r authentifie la continuité, un PMKID seul ne supprime pas la ré-association, connect-group peut déclencher une SA Query, et connect-priority = 0/1 transfère immédiatement la session au prix d’une protection à replacer par l’isolation du SSID.

Côté MikroTik, une règle modifiée doit être suivie d’un provisionnement réel ; la radio locale du gestionnaire demande son propre chemin ; le profil et la CAP ont chacun un datapath ; la maîtresse distante garde un port statique et les esclaves des ports dynamiques. Côté mesure, un sniffer logiciel ne voit pas ce que commute l’offload, une capture radio ne voit pas le canal CAPsMAN filaire et un journal ne voit ni 11k ni 11v. Les compteurs, le pcap aérien, le pcap Ethernet et le témoin FT ont chacun apporté une pièce qu’un autre instrument ne pouvait pas fournir.

Le vrai FT sous Windows demanderait 802.1X. Le service RADIUS existe déjà sur le gestionnaire, mais une PKI, son déploiement et son expiration coûtent plus qu’une case cochée ; ce chantier attend la phase de sécurisation. Un client qui sait déjà faire FT ne doit pas rester sur le SSID dégradé, où il abandonnerait la protection MacStealer sans bénéfice. Cette alerte attend encore que les journaux des bornes atteignent durablement le collecteur.

Le chantier a coûté deux journées, quatre captures, un réseau coupé par une fausse bonne idée et un SSID nommé d’après le système qui le rend nécessaire. Le portable traverse maintenant le logement pendant un speedtest sans broncher. Il ne fait toujours pas de fast transition. Après quinze refus, six salves de SA Query et un détour par un commutateur qui mangeait l’unicast, le roaming rapide fonctionne donc parfaitement — à condition de ne pas lui demander d’utiliser le protocole de roaming rapide.