Как автоматизировать проверку Bonjour на облачном Mac

Как автоматизировать проверку Bonjour на облачном Mac

Как автоматизировать проверку Bonjour на облачном Mac

При удалённой работе возможность подключиться к приложению по известному имени хоста и порту ещё не означает, что обнаружение служб Bonjour работает корректно. Печать, трансляция экрана, отладочные прокси и средства совместной работы в локальной сети часто зависят от mDNS. Если изменится тип службы, TXT-запись или сетевой интерфейс, пользователь обычно увидит лишь то, что «устройства поблизости исчезли». Такие проблемы удобно выявлять с помощью регрессионных тестов на уровне протокола, запущенных на облачном Mac: сначала зарегистрировать временную службу, затем проверить её обнаружение и разрешение, после чего корректно завершить все процессы — вместо того чтобы ждать случайного сбоя реального устройства.

Сначала определите границы тестирования

Bonjour обычно использует UDP-порт 5353 для отправки локального многоадресного трафика mDNS в пределах канального сегмента. Доступность TCP-порта не означает, что этот трафик автоматически пройдёт через маршрутизатор, обычный SSH-туннель или подключение к удалённому рабочему столу. Поэтому тестирование в облаке следует разделить на два уровня:

  1. На одном узле macOS проверить регистрацию, обнаружение, разрешение SRV и TXT-записи.
  2. Отдельно проверить взаимодействие с реальными устройствами в целевой локальной сети, включая коммутаторы, беспроводную сеть и политики многоадресной рассылки.

Первый уровень подходит для CI, запускаемого при каждом коммите. Он позволяет обнаружить опечатку в типе службы, неверный порт, отсутствие метаданных или незавершившийся процесс. Второй уровень относится к приёмочному тестированию сети, и результаты первого уровня не могут его заменить.

Успешная проверка Bonjour подтверждает лишь то, что контракт обнаружения служб выполняется в текущих границах теста. Она не доказывает, что многоадресный трафик проходит через границы удалённой сети.

Рекомендуется выделить продукту собственный тип службы, например _myapp-test._tcp. Не используйте _http._tcp, иначе в результаты попадут другие службы тестового узла. Имя экземпляра также не должно быть постоянным: включайте в него уникальный идентификатор текущего задания.

Создайте минимальный замкнутый тест с помощью dns-sd

В macOS уже есть утилита dns-sd, поэтому устанавливать дополнительные зависимости не требуется. Следующий скрипт запускает временный HTTP-сервер, начинает обнаружение, регистрирует экземпляр Bonjour, а затем разрешает его и проверяет содержимое TXT-записи. Его можно сохранить в репозитории как 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

Обнаружение необходимо запустить до регистрации, иначе короткое задание может пропустить событие добавления службы. Команды dns-sd для обнаружения и разрешения не запускаются здесь на переднем плане, поскольку они продолжают ожидать события. В CI нужно явно задать интервал наблюдения и затем завершить процессы.

Проверяйте именно контракт службы

Одной проверки появления имени экземпляра недостаточно. Стабильный регрессионный тест должен проверять как минимум четыре параметра: тип службы, имя экземпляра, порт и TXT-запись. В TXT-записи следует хранить только малочувствительные метаданные, которые действительно нужны на этапе обнаружения, например версию протокола, признаки поддерживаемых возможностей и путь проверки состояния. Токены, пароли и внутренние учётные данные размещать в ней нельзя.

Версию протокола рекомендуется записывать как api=2, а возможности — как features=sync,preview. Проверяйте каждый параметр отдельно, а не сравнивайте всю строку вывода. Вывод dns-sd содержит время и сведения об интерфейсе, поэтому снимок целой строки легко меняется при обновлении системы.

Если приложение должно отклонять устаревший протокол, можно дополнительно зарегистрировать экземпляр с api=1 и убедиться, что клиент действительно его игнорирует. Такой тест проверяет логику выбора, а не просто факт появления имени. После успешного разрешения следует также отправить минимальный запрос на фактический порт, чтобы исключить ложноположительный результат вида «в объявлении указан 8088, а служба слушает 8089».

Изолируйте параллельные задания, удаляйте остаточные процессы и ограничивайте доступ

Если на одном узле одновременно выполняется несколько заданий, необходимо изолировать и имена экземпляров, и порты. Добавление номера задания в имя экземпляра предотвращает смешивание результатов обнаружения, но не мешает двум HTTP-серверам претендовать на один порт. Планировщик CI должен заранее распределять порты либо перед запуском проверять занятость командой lsof -nP -iTCP:$PORT -sTCP:LISTEN.

Все фоновые процессы должны завершаться через trap. Если после неудачной проверки процесс регистрации останется активным, следующий тест может обнаружить старый экземпляр и дать трудно воспроизводимый ложноположительный результат. При очистке сначала следует остановить процессы разрешения и регистрации, а затем — обнаружение и тестовую службу.

Для сквозной проверки сервер в примере слушает на всех интерфейсах. Даже на выделенном облачном Mac необходимо соблюдать принцип минимальной доступности: открывайте тестовый порт только в контролируемой сети и лишь на короткое время. Если сетевой запрос не нужен, HTTP-сервер можно исключить и проверять только регистрацию и разрешение. Команду lsof полезно запускать до и после теста, чтобы убедиться в отсутствии оставшихся слушающих процессов.

Диагностируйте проблему по выводу неудачного теста

Если служба не обнаруживается, сначала убедитесь, что её тип совпадает полностью, включая начальное подчёркивание и суффикс _tcp, а затем проверьте, не завершился ли процесс регистрации раньше времени. Если служба обнаруживается, но не разрешается, обратите особое внимание на экранирование имени экземпляра, параметр домена и разрешение локального имени компьютера. Если разрешение выполняется, но подключиться к порту нельзя, проверьте адрес прослушивания, занятость порта, правила межсетевого экрана и порядок запуска службы.

CI должен сохранять browse.log, resolve.log и вывод процесса регистрации, но перед загрузкой эти данные необходимо обезличить. При сбое также записывайте результаты scutil --get LocalHostName, networksetup -listallhardwareports и lsof для соответствующего порта. Обычно этого достаточно, чтобы отличить проблему с именованием от проблемы сетевого интерфейса или прослушивания порта.

Даже при запуске таких тестов на выделенных физических узлах VMCache не следует считать, что канал удалённого подключения будет пересылать mDNS. Выполняйте регрессионную проверку на уровне протокола внутри узла, а обнаружение реальных устройств проверяйте отдельно при приёмке целевой локальной сети. Раздельная фиксация результатов делает границы сбоев понятными, а сами сбои — воспроизводимыми.

Часто задаваемые вопросы

Передаёт ли обычный SSH-туннель объявления Bonjour?

Нет. Bonjour обычно использует локальный multicast mDNS по UDP 5353, а стандартное перенаправление портов SSH не переносит такой групповой трафик между сетевыми сегментами.

Как исключить конфликты параллельных Bonjour-тестов?

Создавайте уникальное имя экземпляра и отдельный порт для каждого задания. Обработчик завершения должен останавливать регистрацию dns-sd, процесс поиска и тестовый сервис даже после ошибки.

Выделенный физический узел

Выберите облачный Mac для сборки, тестирования и инференса MLX

Выберите M4, объём памяти, хранилище, узел и срок аренды под свои задачи. Каждая аренда включает выделенный физический компьютер, а не виртуальную машину.

Выбрать тариф