- Le compteur d’horodatage TCP (
tcp_now) de macOS subit un débordement 32 bits environ 49,7 jours après le démarrage, ce qui fige l’horloge TCP interne - En conséquence, les connexions en état TIME_WAIT n’expirent plus et s’accumulent, empêchant la libération des ports éphémères
- Avec le temps, l’épuisement des ports éphémères fait échouer toutes les nouvelles connexions TCP, tandis que les connexions existantes restent maintenues
- ICMP (
ping) continue de fonctionner normalement, mais l’ensemble des fonctions TCP est paralysé, sans possibilité de récupération autrement qu’avec un redémarrage - Les serveurs macOS, machines de build et environnements CI fonctionnant sur de longues durées sont exposés à ce problème selon un cycle de 49 jours et 17 heures, ce qui impose des redémarrages périodiques tant qu’un correctif du noyau n’est pas disponible
Contexte : notions de base sur TCP
- Une connexion TCP ne disparaît pas immédiatement à sa fermeture : elle passe en état TIME_WAIT, une étape destinée à gérer les paquets retardés et à garantir une fermeture fiable
- Cela évite qu’anciens paquets soient interprétés à tort comme appartenant à une nouvelle connexion, et permet de gérer la retransmission en cas de perte du dernier ACK
- La durée de TIME_WAIT est définie comme 2 × MSL (Maximum Segment Lifetime) ; sur macOS, elle est réglée à environ 30 secondes
- Le MSL est la durée maximale pendant laquelle un segment TCP peut survivre sur le réseau ; la RFC 793 la fixe à 2 minutes, mais les systèmes modernes utilisent généralement des valeurs bien plus courtes
- Le débordement d’un entier non signé 32 bits correspond au retour à 0 après dépassement de la valeur maximale (4 294 967 295). Sur macOS, l’horodatage TCP (
tcp_now) est un compteur 32 bits incrémenté en millisecondes depuis le démarrage, et le débordement survient après 49 jours 17 heures 2 minutes 47,296 secondes
Découverte : interruption des connexions TCP après 49,7 jours
- Les serveurs Mac de Photon dédiés à la surveillance d’iMessage fonctionnaient 24/7, et le 30 mars 2026, exactement 49,7 jours après le démarrage, toutes les nouvelles connexions TCP ont commencé à échouer
- Les connexions existantes et ICMP (
ping) continuaient de fonctionner, mais il devenait impossible d’ouvrir de nouveaux sockets TCP
- Les connexions existantes et ICMP (
- La cause est un débordement du compteur d’horodatage TCP (
tcp_now) dans le noyau XNU : une logique de validation de croissance monotone bloque la mise à jour après le wraparound, ce qui fige l’horloge TCP interne - Les connexions TIME_WAIT n’expirent donc plus, les ports éphémères ne sont jamais libérés et s’accumulent, rendant toute récupération impossible sans redémarrage
- Après redémarrage, le même phénomène se reproduit à nouveau tous les 49,7 jours
Conception de l’expérience : comparer le comportement TCP avant et après le débordement
- Hypothèse : si le garbage collection de TIME_WAIT s’arrête après le débordement, le schéma de création de connexions TCP courtes doit différer avant et après celui-ci
- Avant le débordement : expiration normale de TIME_WAIT après 30 secondes
- Après le débordement : persistance indéfinie de TIME_WAIT
- Exécution d’un script de test en trois phases
- Phase de surveillance : enregistrement du nombre de TIME_WAIT toutes les 10 secondes, de 35 minutes avant le débordement à 5 minutes avant
- Phase d’explosion : création d’environ 15 connexions TCP courtes toutes les 2 secondes pendant 10 minutes autour du débordement
- Phase d’observation : surveillance de l’évolution de TIME_WAIT après arrêt de la génération de connexions
Résultats : stagnation de TIME_WAIT après le débordement
- Avant le débordement, le nombre de connexions TIME_WAIT oscillait de façon stable entre 0 et 200, confirmant un recyclage normal
- Juste après le débordement, le nombre de connexions TIME_WAIT augmente continuellement et n’expire plus
- Sur la machine B, 2 828 connexions TIME_WAIT n’avaient toujours pas été recyclées 84 secondes plus tard, et le cumul a continué ensuite
- Sur la machine A également, les vérifications manuelles ont montré une augmentation monotone du nombre de TIME_WAIT, sans récupération possible
Cause racine : débordement 32 bits de tcp_now dans le noyau XNU
tcp_nowest un compteur 32 bits en millisecondes défini dansbsd/netinet/tcp_var.h, utilisé pour suivre le temps écoulé depuis le démarrage- Dans la fonction
calculate_tcp_clock(), l’opération(uint32_t)now.tv_sec * 1000dépasse la valeur maximale après 49,7 jours, provoquant un wraparound - À cause de la condition
if (tmp < current_tcp_now), la valeur existante devient supérieure à la nouvelle après débordement, ce qui bloque la mise à jour et fige définitivementtcp_now - Comme l’expiration de TIME_WAIT est évaluée par rapport à
tcp_now, si l’horloge s’arrête, la condition d’expiration devient toujours fausse, empêchant tout recyclage
Effet en chaîne : propagation jusqu’à l’arrêt complet de TCP
- Après quelques minutes : l’arrêt du recyclage de TIME_WAIT provoque des problèmes progressifs sur les charges avec beaucoup de connexions courtes
- Après quelques heures : des milliers de TIME_WAIT s’accumulent, entraînant un épuisement des ports éphémères
- Une fois les ports épuisés : les nouvelles connexions TCP échouent en état SYN_SENT, et seules les connexions existantes restent actives
- Hausse brutale de la charge CPU : le noyau continue à scanner la file TIME_WAIT, augmentant la charge
- Au final, paralysie complète de TCP, tandis qu’ICMP continue de fonctionner normalement
- La seule méthode de récupération est le redémarrage, après quoi le compteur de 49,7 jours repart de zéro
Éléments supplémentaires et cas similaires
- La RFC 7323 précise qu’avec des horodatages 32 bits à 1 ms, l’enroulement du bit de signe se produit environ tous les 24,8 jours
- Dans le cas de macOS, il s’agit d’un débordement complet sur 32 bits (49,7 jours), soit un défaut local du noyau distinct des problèmes d’horodatage distant abordés par la RFC
- De nombreux cas identiques ont été signalés dans la communauté Apple et dans des projets open source
- Impossible d’établir des connexions TCP,
pingreste normal, seul un redémarrage résout le problème, après plusieurs semaines de fonctionnement - Le même schéma apparaît notamment dans la Podman issue #12495
- Impossible d’établir des connexions TCP,
- Points communs : seul TCP échoue, ICMP fonctionne, redémarrage nécessaire, cycle de survenue de plusieurs semaines
Périmètre d’impact
- Le problème peut survenir sur les systèmes macOS fonctionnant en continu plus de 49 jours et 17 heures
- Les utilisateurs classiques sont peu touchés, car les mises à jour périodiques entraînent généralement des redémarrages
- Environnements à haut risque
- Flottes de serveurs tournant sur de longues durées
- Serveurs de build CI/CD basés sur macOS
- Stations de travail Mac Pro
- Mac en colocation administrés à distance
- Clusters de Mac mini utilisés pour des fermes de build ou des infrastructures de test
Procédure de reproduction
- Calculer l’instant estimé du débordement à partir de l’heure de démarrage
- Surveiller le nombre de TIME_WAIT avant et après le débordement
- Générer un grand nombre de connexions TCP courtes au moment du débordement
- Si le nombre de TIME_WAIT ne diminue pas après 2 minutes, la reproduction du bug est réussie
État du système observé après 9,5 heures
- Aucune connexion TIME_WAIT n’a été recyclée, et leur nombre a continué à augmenter
- Plus de 3 000 connexions en échec à l’état SYN_SENT se sont accumulées
- Seules les connexions existantes étaient maintenues, toute nouvelle connexion étant impossible
- La charge moyenne de la machine B est montée jusqu’à 49,74, le noyau consommant excessivement du CPU pour scanner la file TIME_WAIT
Conclusion
- Un simple entier 32 bits et la condition
if (tmp < current_tcp_now)agissent comme une bombe à retardement capable d’arrêter complètement TCP après 49,7 jours - Il s’agit d’un type de défaut difficile à détecter en phase de développement, de test ou de revue de code, et qui ne se révèle qu’en conditions réelles d’exploitation
- Photon a reproduit le même phénomène sur plusieurs serveurs, en confirmant clairement un recyclage normal avant le débordement, puis une accumulation de TIME_WAIT après celui-ci
- Lorsque
tcp_nowse fige, l’horloge TCP du noyau s’arrête ; le système semble fonctionner en apparence, mais tous les ports TCP finissent par être épuisés - Les administrateurs de systèmes macOS à longue durée de fonctionnement doivent retenir l’échéance de 49 jours 17 heures 2 minutes 47 secondes ; des redémarrages périodiques sont nécessaires tant qu’un ajustement du noyau ou un correctif n’est pas disponible
- Photon développe actuellement une solution de contournement permettant de restaurer
tcp_nowsans redémarrage
1 commentaires
Avis Hacker News
Je comprends enfin pourquoi mon iMac n’avait parfois plus aucune connectivité
Je n’avais absolument pas réalisé que c’était à cause de l’uptime
En lisant l’article, j’ai eu une forte impression qu’il avait été écrit par une IA, et je me suis demandé si Apple avait réellement été contacté
Bien sûr, le bug est important, mais j’ai trouvé qu’il y avait beaucoup d’expressions exagérées
La plupart des utilisateurs seront probablement très peu affectés
On peut aussi sans doute éviter le problème en mettant le Mac en veille, puisque cela réinitialise la pile TCP
Apple finira par corriger ça, mais il n’y a pas lieu de paniquer pour l’instant
Mon MacBook, avec la veille automatique désactivée, était resté allumé environ 50 jours, et j’avais le phénomène suivant : le ping fonctionnait, mais aucune connexion TCP ne passait
Changer de Wi‑Fi ou passer en filaire n’a rien résolu, et tout est revenu à la normale dès le redémarrage
Il aurait dit qu’il développait une solution de contournement meilleure qu’un redémarrage et que, d’ici là, il fallait redémarrer périodiquement
C’est alors le bon moment pour redémarrer
En ce moment, les billets de blog écrits par une IA sont vraiment pénibles à lire
Le style est peu naturel et met beaucoup trop de temps à aller à l’essentiel
tcp_nowdébordeJe ne suis pas d’accord avec l’idée que « personne ne va tester pendant 50 jours »
En réalité, il suffit de faire des tests de simulation en accélérant le temps
jiffiesest initialisé au démarrage à une valeur juste avant le débordementDans ce cas, on peut vérifier le comportement en modifiant une fonction comme
calculate_tcp_clockpour passer l’uptime en argumentCe bug affecte non seulement OpenClaw, mais toutes les connexions TCP
Une fois que l’uptime de macOS dépasse 49,7 jours, toutes les connexions TCP commencent à être affectées
Plusieurs de mes machines macOS tournent depuis plus de 600 à 1000 jours, et les connexions TCP expirent normalement
Leurs versions du noyau sont respectivement 20.6.0 et 17.7.0
Donc ce bug semble n’apparaître qu’à partir de certaines versions
tcp_nowse fige juste avant le débordement, et un wraparound incorrect dans le calcul du timer la rend négative, ce qui fait échouer la comparaisonDes connexions TIME_WAIT peuvent s’accumuler pendant une brève période, mais le texte original surréagissait et donnait l’impression d’avoir été écrit par un LLM
Lien GitHub associé
Ce genre de problème se répète dans différents logiciels
Il y a longtemps, quelque chose de similaire était arrivé sur les serveurs de Guild Wars, et ils avaient testé en ajoutant une certaine valeur à
GetTickCount()pour provoquer plus vite le débordementCe bug rappelle le bug des 49,7 jours de Windows 95
Article associé
Je me demande quel est le rapport entre OpenClaw et ce bug
Ce problème rappelle le bug des 208 jours de l’ordonnanceur du noyau Linux
Lien de référence