Automatiser les tests de découverte Bonjour sur un Mac cloud

Automatiser les tests de découverte Bonjour sur un Mac cloud

En télétravail, le fait qu’une application puisse se connecter à un nom d’hôte et à un port précis ne signifie pas que la découverte de services Bonjour fonctionne correctement. L’impression, la diffusion d’écran, les agents de débogage et les outils de collaboration sur le réseau local reposent souvent sur mDNS. Dès qu’un type de service, un enregistrement TXT ou une interface réseau change, l’interface se contente généralement d’indiquer que les « appareils à proximité » ont disparu. Ce type de problème se prête bien à une régression au niveau du protocole sur un Mac cloud : il suffit d’enregistrer un service temporaire, puis de vérifier sa détection, sa résolution et le nettoyage à la fin du test, plutôt que d’attendre la défaillance intermittente d’un appareil réel.

Définir clairement le périmètre du test

Bonjour utilise généralement le port UDP 5353 pour diffuser des paquets multicast mDNS sur le lien local. Un port TCP accessible ne lui permet pas de traverser automatiquement un routeur, un tunnel SSH standard ou une connexion de bureau à distance. Les tests dans le cloud doivent donc être divisés en deux niveaux :

  1. Sur un même nœud macOS, vérifier l’enregistrement, la détection, la résolution SRV et les enregistrements TXT.
  2. Sur le réseau local cible, effectuer un test distinct avec de vrais appareils afin de valider les commutateurs, le réseau Wi-Fi et les règles multicast.

Le premier niveau convient à une intégration dans la CI de chaque commit. Il permet de détecter un type de service mal orthographié, un port incorrect, des métadonnées manquantes ou un processus qui ne s’est pas arrêté. Le second relève de la validation réseau et ne peut pas être remplacé par les résultats du premier.

La réussite d’un test Bonjour prouve uniquement que le contrat de découverte de services est respecté dans le périmètre testé. Elle ne prouve pas que le multicast traverse les limites du réseau distant.

Il est recommandé d’attribuer au produit un type de service dédié, par exemple _myapp-test._tcp. N’utilisez pas _http._tcp, car d’autres services présents sur le nœud de test se retrouveraient alors dans les résultats. Le nom d’instance ne doit pas non plus être fixe : il doit inclure un identifiant unique propre à l’exécution en cours.

Créer une boucle de test minimale avec dns-sd

macOS fournit dns-sd nativement, sans dépendance supplémentaire à installer. Le script ci-dessous démarre un service HTTP temporaire, lance d’abord la détection, enregistre ensuite une instance Bonjour, puis résout cette instance et vérifie le contenu de son enregistrement TXT. Il peut être ajouté au dépôt sous le chemin ci/test-bonjour.sh.

#!/bin/bash
set -euo pipefail

RUN_ID="${CI_RUN_ID:-$(date +%s)}"
INSTANCE="vmcache-${RUN_ID}"
TYPE="_myapp-test._tcp"
PORT="${BONJOUR_TEST_PORT:-8088}"
WORK_DIR="$(mktemp -d)"
BROWSE_LOG="$WORK_DIR/browse.log"
RESOLVE_LOG="$WORK_DIR/resolve.log"

HTTP_PID=""
BROWSE_PID=""
REGISTER_PID=""
RESOLVE_PID=""

cleanup() {
  for pid in "$RESOLVE_PID" "$REGISTER_PID" "$BROWSE_PID" "$HTTP_PID"; do
    if [[ -n "$pid" ]]; then
      kill "$pid" 2>/dev/null || true
      wait "$pid" 2>/dev/null || true
    fi
  done
  rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM

python3 -m http.server "$PORT" --bind 0.0.0.0 \
  >"$WORK_DIR/http.log" 2>&1 &
HTTP_PID=$!

dns-sd -B "$TYPE" local. >"$BROWSE_LOG" 2>&1 &
BROWSE_PID=$!

dns-sd -R "$INSTANCE" "$TYPE" local. "$PORT" \
  "path=/" "run=$RUN_ID" >"$WORK_DIR/register.log" 2>&1 &
REGISTER_PID=$!

sleep 4
grep -F "$INSTANCE" "$BROWSE_LOG"

dns-sd -L "$INSTANCE" "$TYPE" local. >"$RESOLVE_LOG" 2>&1 &
RESOLVE_PID=$!
sleep 3
kill "$RESOLVE_PID" 2>/dev/null || true
wait "$RESOLVE_PID" 2>/dev/null || true
RESOLVE_PID=""

grep -E "path=/|run=$RUN_ID" "$RESOLVE_LOG"
grep -E "$PORT" "$RESOLVE_LOG"
curl --fail --silent --show-error "http://127.0.0.1:$PORT/" >/dev/null

La détection doit démarrer avant l’enregistrement, faute de quoi une tâche courte risque de manquer l’événement d’ajout. Ici, dns-sd n’est pas exécuté au premier plan, car les commandes de détection et de résolution restent en attente en continu. La CI doit donc définir explicitement une durée d’observation, puis arrêter les processus.

Aligner les assertions sur le contrat de service

Vérifier uniquement la présence du nom d’instance ne suffit pas. Un test de régression fiable doit contrôler au minimum quatre éléments : le type de service, le nom d’instance, le port et l’enregistrement TXT. Celui-ci doit contenir uniquement les métadonnées peu sensibles réellement nécessaires lors de la découverte, comme la version du protocole, les indicateurs de fonctionnalités et le chemin de contrôle d’intégrité. Il ne doit pas contenir de jetons, de mots de passe ni d’identifiants internes.

Il est recommandé d’indiquer la version du protocole sous la forme api=2 et les fonctionnalités sous la forme features=sync,preview. Le test doit vérifier chaque élément séparément au lieu de comparer une ligne entière. La sortie de dns-sd contient des informations d’horodatage et d’interface ; un instantané de la ligne complète risque donc de changer selon la version du système.

Si l’application doit refuser les anciennes versions du protocole, vous pouvez également enregistrer une instance avec api=1 et vérifier que le client l’ignore effectivement. Le test couvre alors la logique de sélection, et pas seulement la capacité à « voir un nom ». Une fois la résolution réussie, il faut aussi envoyer une requête minimale au port réel afin d’éviter un faux positif où l’annonce indique 8088 alors que le service écoute sur 8089.

Gérer la concurrence, les résidus et la surface d’exposition

Lorsque plusieurs tâches s’exécutent en parallèle sur un même nœud, le nom d’instance et le port doivent tous deux être isolés. Ajouter le numéro de tâche au nom d’instance évite seulement de confondre les résultats de découverte ; cela n’empêche pas deux services HTTP de tenter d’utiliser le même port. L’ordonnanceur CI doit attribuer les ports à l’avance ou vérifier leur disponibilité avant le démarrage avec lsof -nP -iTCP:$PORT -sTCP:LISTEN.

Tous les processus en arrière-plan doivent être pris en charge par trap. Si le processus d’enregistrement reste actif après l’échec d’une assertion, le test suivant peut détecter l’ancienne instance et produire un faux positif difficile à reproduire. Lors du nettoyage, arrêtez d’abord les processus de résolution et d’enregistrement, puis le processus de détection et le service de test.

Afin d’effectuer une vérification de bout en bout, l’exemple écoute sur toutes les interfaces. Même sur un Mac cloud dédié, il convient de respecter le principe d’exposition minimale : le port de test ne doit être ouvert que sur un réseau contrôlé et pendant une courte période. Si aucune requête réseau n’est nécessaire, le service HTTP peut être omis afin de vérifier uniquement l’enregistrement et la résolution. Vous pouvez exécuter lsof avant et après le test pour confirmer qu’aucun processus ne reste à l’écoute sur le port.

Diagnostiquer les échecs à partir des sorties

Si aucun service n’apparaît pendant la détection, vérifiez d’abord que le type de service correspond exactement, y compris le trait de soulignement initial et le suffixe _tcp, puis assurez-vous que le processus d’enregistrement ne s’est pas arrêté prématurément. Si le service est visible mais ne peut pas être résolu, examinez en priorité l’échappement du nom d’instance, le paramètre de domaine et la résolution du nom local. Si la résolution fonctionne mais que la connexion au port échoue, vérifiez l’adresse d’écoute, l’occupation du port, les règles de pare-feu et l’ordre de démarrage des services.

La CI doit conserver browse.log, resolve.log ainsi que la sortie du processus d’enregistrement, tout en supprimant les données sensibles avant leur téléversement. En cas d’échec, consignez également les résultats de scutil --get LocalHostName, de networksetup -listallhardwareports et de lsof pour les ports concernés. Ces informations suffisent généralement à distinguer un problème de nommage, d’interface ou d’écoute.

Même lorsque ces tests s’exécutent sur les nœuds physiques dédiés de VMCache, ne supposez pas que le chemin de connexion à distance relaie mDNS. Exécutez systématiquement la régression au niveau du protocole à l’intérieur du nœud, et réservez la découverte avec de vrais appareils à la validation sur le réseau local cible. En consignant séparément les deux séries de résultats, vous obtenez des limites d’échec claires et reproductibles.

Questions fréquentes

Un tunnel SSH transporte-t-il automatiquement la découverte Bonjour ?

Non. Bonjour repose généralement sur le multicast mDNS limité au lien local, tandis qu’un tunnel SSH classique transfère des ports TCP ou UDP ciblés, pas le trafic multicast UDP 5353.

Comment éviter les collisions entre plusieurs tests Bonjour en CI ?

Générez un nom d’instance unique par tâche, attribuez un port distinct à chaque exécution et utilisez un gestionnaire de sortie pour arrêter le service de test ainsi que tous les processus dns-sd.

Nœuds physiques dédiés

Choisissez un Mac cloud pour compiler, tester et exécuter des inférences MLX

Choisissez votre M4, votre mémoire, votre stockage, votre nœud et votre durée selon vos besoins. Chaque location correspond à une machine physique dédiée, et non à une machine virtuelle.

Choisir une formule de location