RE:NODE

Сеть13 мин чтения

Query-порты и A2S: запросы статуса сервера

На что отвечает query-порт, как устроены протокол A2S от Valve и его challenge, как опросить любой сервер Steam скриптом и почему запрос не проходит, хотя игра работает.

0 прочтений

Query-порт - это порт, на котором игровой сервер отвечает на вопрос «кто ты и насколько заполнен?» - для списков серверов, сайтов статуса, ботов в Discord и мониторинга. В играх Steam ответ приходит по A2S, небольшому UDP-протоколу от Valve: клиент отправляет фиксированный запрос, сервер отвечает своим названием, картой, числом игроков, максимумом игроков и несколькими флагами. Этот протокол отделён от игрового трафика, часто живёт на отдельном порту и намеренно работает без аутентификации. Понимая, как он устроен, вы сможете одной командой проверить видимость сервера, отличить проблему с портом от проблемы со списком и понять, почему один и тот же порт - это и то, как вас находят игроки, и то, как вас используют атакующие.

Зачем нужен query-порт#

Игровой трафик идёт только после того, как игрок решил подключиться. Всё, что до этого, - список в браузере серверов, число игроков на сайте, сообщение «сервер онлайн» в Discord - берётся из протокола запросов. У него три важных свойства:

  • Он отвечает незнакомцам. Любой, кто отправит правильно сформированный пакет, получит ответ. Иначе нельзя: браузер серверов - тоже незнакомец.
  • Он работает по UDP. Один запрос, один ответ (или несколько для больших ответов), никакого соединения.
  • Часто он на своём порту. Игры на движке Source отвечают прямо на игровом порту. Многие другие игры Steam отвечают на отдельном query-порту - обычно это фиксированное смещение от игрового порта или фиксированный номер вроде 27015.

Третий пункт стоит за большой долей жалоб «сервер работает, но его никто не видит». Игрок вводит игровой порт, который открыт; браузер спрашивает query-порт, который закрыт. Сторона этой истории, касающаяся списка, - в статье почему сервер не виден в списке серверов. Эта статья - о стороне протокола.

A2S в одной таблице#

Каждый пакет A2S начинается с четырёх байт FF FF FF FF, которые обозначают одиночный, неразбитый пакет. Дальше идёт однобайтовый тип. Сегодня важны три запроса:

ЗапросБайт типаБайт ответаЧто вы получаете
A2S_INFO0x54 (T)0x49 (I)Название, карта, игра, игроки, максимум игроков, боты, флаги, версия
A2S_PLAYER0x55 (U)0x44 (D)Имя, счёт и время подключения каждого игрока
A2S_RULES0x56 (V)0x45 (E)Правила сервера: публичные cvar или пары ключ-значение

A2S_INFO несёт строку Source Engine Query, за которой следует нулевой байт. Ответ - это последовательность полей в фиксированном порядке:

ПолеТипПримечания
ProtocolbyteВерсия протокола
Name, Map, Folder, GamestringsЗавершаются нулём; folder - каталог игры, например cstrike
IDshortApp id, обрезанный до 16 бит
Players, Max players, BotsbytesБоты входят в Players
Server typebyted - выделенный, l - listen (невыделенный), p - ретранслятор SourceTV
Environmentbytel - Linux, w - Windows, m или o - Mac
Visibilitybyte0 - публичный, 1 - защищён паролем
VACbyte0 - без защиты, 1 - защищён
VersionstringСтрока версии игры
Extra data flagbyteУказывает, какие необязательные поля идут дальше

Необязательные поля после флага включают игровой порт (флаг 0x80), Steam ID сервера (0x10), порт и имя SourceTV (0x40), ключевые слова или теги (0x20) и полный 64-битный game id (0x01). Благодаря полю игрового порта браузер Steam можно направить на query-порт, и он всё равно подключит игроков к правильному игровому порту.

Большие ответы - длинные списки игроков, длинные списки правил - разбиваются на несколько пакетов. Разбитые пакеты начинаются с FE FF FF FF, затем идут id, общее число частей и порядковый номер, а клиент собирает их обратно. Обычный случай - правила на сильно модифицированном сервере Source.

Два старых запроса, A2S_PING и отдельный запрос challenge, устарели, и многие серверы на них больше не отвечают. Не стройте на них инструменты.

Challenge и зачем его добавили#

A2S проектировали во времена, когда об отражённых атаках не задумывались. Запрос примерно в 25 байт с подделанного адреса источника порождал ответ в несколько сотен байт, отправленный на этот подделанный адрес, - усилитель, который мог навести кто угодно. Игровые серверы стали частым ингредиентом атак с отражением, подробно о которых рассказано в статье DDoS-атаки на игровые серверы.

Решение - challenge. A2S_PLAYER и A2S_RULES требуют его давно, а с конца 2020 года реализация Valve требует challenge и для A2S_INFO. Обмен выглядит так:

  1. Клиент отправляет запрос.
  2. Сервер отвечает коротким пакетом типа 0x41 (A) с 4-байтовым числом challenge.
  3. Клиент отправляет тот же запрос ещё раз, добавив в конец эти 4 байта.
  4. Сервер отправляет настоящий ответ.

Подделанный адрес источника никогда не видит шаг 2, поэтому никогда не получает шаг 4, и усиление исчезает. Практическое следствие для вас: старый скрипт статуса, который отправляет один A2S_INFO и ждёт ответа I, теперь получает ответ A, решает, что сервер сломан, и сообщает, что он офлайн. Если инструмент мониторинга замолчал за последние несколько лет, а больше ничего не менялось, причина, скорее всего, в этом. Серверы на старых движках или с собственными реализациями могут по-прежнему отвечать без challenge, поэтому инструменты должны поддерживать оба варианта.

Для A2S_PLAYER и A2S_RULES первый запрос несёт FF FF FF FF на месте challenge - это просьба выдать challenge.

Как опросить сервер самостоятельно#

Проверка с самого сервера ничего не доказывает, потому что loopback ничего не блокирует. Запускайте всё это с другой машины в другой сети.

С помощью python-a2s

Самый быстрый надёжный инструмент - пакет python-a2s, который сам разбирается с challenge и разбитыми пакетами:

bash
$ pip install python-a2s$ python3
python
import a2saddress = ("203.0.113.10", 27015)  # the QUERY port, not always the game portinfo = a2s.info(address, timeout=3.0)print(info.server_name, info.map_name, info.player_count, info.max_players)for player in a2s.players(address):    print(player.name, player.score, round(player.duration))print(a2s.rules(address))

socket.timeout означает, что ответа нет вовсе: порт закрыт, фильтруется, указан неверно или сервер не отвечает на запросы. Ответ с чужим названием сервера означает, что вы опрашиваете что-то другое на этом адресе - два сервера на соседних портах - частая ловушка.

Без всяких библиотек

Если ничего установить нельзя, протокол достаточно мал, чтобы говорить на нём вручную. Этот скрипт выполняет A2S_INFO с challenge и читает первые несколько полей; разбитые пакеты он игнорирует, но ответу info они почти никогда не нужны.

a2s_info.py
import socket, struct, sysdef read_str(buf, i):    end = buf.index(b"\x00", i)    return buf[i:end].decode("utf-8", "replace"), end + 1host, port = sys.argv[1], int(sys.argv[2])req = b"\xFF\xFF\xFF\xFFTSource Engine Query\x00"s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.settimeout(3.0)s.sendto(req, (host, port))data, _ = s.recvfrom(4096)if data[4] == 0x41:  # challenge: resend with it appended    s.sendto(req + data[5:9], (host, port))    data, _ = s.recvfrom(4096)i = 6  # skip the 4-byte header, the 0x49 type and the protocol bytename, i = read_str(data, i)map_name, i = read_str(data, i)folder, i = read_str(data, i)game, i = read_str(data, i)app_id, players, max_players, bots = struct.unpack_from("<HBBB", data, i)print(f"{name} | {map_name} | {players}/{max_players} ({bots} bots)")

Запускайте его так: python3 a2s_info.py 203.0.113.10 27015.

С помощью GameDig

GameDig - это библиотека для Node.js и консольная утилита, которая знает сотни игр, в том числе тех, что не говорят на A2S. Именно она работает внутри многих ботов статуса и сайтов.

bash
$ npm install -g gamedig$ gamedig --type valheim 203.0.113.10:2456

Идентификаторы игр и работа с портами менялись между мажорными версиями - некоторые id переименовали, а для многих игр GameDig сам применяет известное смещение от игрового порта к query-порту. Прежде чем решать, что таймаут означает мёртвый сервер, сверьтесь со списком игр для установленной у вас версии.

Query-порты по играм#

Смещение между игровым и query-портом - договорённость конкретной игры. Ниже значения по умолчанию; большинство из них настраивается, и последнее слово всегда за логом запуска сервера.

ИграИгровой портQuery-портПротокол
CS2, TF2, Garry's Mod, Left 4 Dead 227015тот же портA2S
Counter-Strike 1.627015тот же портA2S (GoldSrc)
Valheim24562457 (игровой + 1)A2S
Arma 323022303 (игровой + 1)A2S
Unturned2701627015 (настроенный Port)A2S
Palworld821127015A2S
Project Zomboid16261тот же портA2S
Rust28015тот же портA2S
Minecraft Java25565тот же порт (статус по TCP)Server List Ping

Что стоит запомнить: игры на Source отвечают на игровом порту, а игры, которые прикрутили серверный API Steam к другому движку, обычно отвечают на втором порту. Когда переносите игровой порт, проверьте, переезжает ли вместе с ним query-порт (смещение) или остаётся на месте (фиксированная настройка). Если поменять одно и забыть другое, получится сервер, на который можно зайти, но которого не видно. В панели должны быть выделены оба номера - на RE:NODE они входят в число портов тарифа, а дополнительные добавляются на вкладке Network, - и собственная настройка игры должна совпадать с выделенным. Более длинная таблица, включая RCON и вспомогательные порты, - в статье порты игровых серверов.

Minecraft делает это по-своему#

Minecraft Java не использует A2S. У него два механизма, и их легко спутать.

Server List Ping - это то, чем пользуется экран сетевой игры. Он работает по TCP на игровом порту: клиент отправляет handshake с запросом статуса, затем запрос статуса, и сервер отвечает JSON - версия, число игроков онлайн и максимум, выборка имён игроков, MOTD и иконка сервера. Управляется он параметром enable-status в server.properties, который по умолчанию включён. Если его выключить, сервер в списке будет выглядеть офлайн, но продолжит принимать игроков, которые знают адрес.

Query - более старый UDP-протокол, который по происхождению часто называют GameSpy4 query. По умолчанию он выключен, а управляют им enable-query и query.port. Включённый, он возвращает больше подробностей, чем пинг статуса, в том числе список плагинов на некоторых серверных сборках. Иногда его просят сторонние сайты статуса; почти ничему другому он не нужен.

python
from mcstatus import JavaServer  # pip install mcstatusserver = JavaServer.lookup("play.example.com:25565")status = server.status()print(status.players.online, status.players.max, round(status.latency))

lookup также следует за SRV-записью, поэтому проверяет адрес так, как его вводят игроки. Эта часть объяснена в статье SRV-записи для Minecraft.

Что с этим делают сайты статуса, боты и списки#

Каждый сайт со списком серверов, каждый виджет «игроков онлайн» и почти каждый бот статуса в Discord - это клиент запросов на таймере. Примерно раз в минуту они отправляют A2S (или аналог для игры) и сохраняют ответ. Из этого следуют три вещи.

Во-первых, они видят только то, что говорит query-порт, и ничего больше. Если query-порт закрыт, они пишут, что вы офлайн, даже когда люди играют. Во-вторых, имена игроков, которые вы видите на сайте-трекере, берутся прямо из A2S_PLAYER, поэтому они публичны для всех - это стоит знать, если вы держите сервер для людей, которые не хотели бы попадать в списки. В-третьих, бот статуса, опрашивающий раз в тридцать секунд, безвреден; полсотни таких ботов плюс все сайты-списки плюс сканеры складываются в постоянный фон query-трафика, который будет виден в счётчиках пакетов даже на пустом сервере.

Если вы хотите сообщение о статусе в Discord без стороннего бота, в статье вебхуки Discord для статуса сервера показано, как публиковать его из скрипта вроде приведённого выше.

Ограничения частоты и что выдаёт запрос#

Поскольку query-порт отвечает всем, это самая дешёвая для флуда часть сервера. У серверов на движке Source есть встроенные ограничения со значениями по умолчанию, подходящими для обычного использования:

server.cfg
sv_max_queries_sec 3          // queries per second from one addresssv_max_queries_window 30      // window over which that rate is averagedsv_max_queries_sec_global 60  // total queries per second answered

Снижение глобального лимита защищает сервер во время флуда ценой того, что для части легитимных браузеров он будет выглядеть неотвечающим. Потом верните значение обратно, иначе неделю будете гадать, почему сервер выпадает из списка.

Кроме того, запрос выдаёт больше, чем ожидают люди. A2S_RULES на сервере Source возвращает каждый cvar, помеченный как публичный, а на модифицированном сервере это часто включает названия и версии плагинов - бесплатная инвентаризация для любого, кто ищет плагин с известной уязвимостью. Некоторые игры и админ-фреймворки позволяют скрыть правила или список игроков; где это возможно, подумайте об этом для приватного сервера. Статья что мы делаем с атаками рассматривает query-порт с точки зрения защиты.

Запрос как проверка работоспособности#

Запрос - лучшая дешёвая проверка работоспособности, какая есть у игрового сервера, лучше ICMP-пинга или голого TCP-подключения, потому что ответить на него может только работающий процесс игры. Машина, которая включена, но с упавшим или зависшим сервером, на пинги всё ещё отвечает, а на A2S - нет. При правильном использовании такая проверка ловит именно те сбои, которые важны игрокам.

Несколько правил делают её надёжной:

  • Проверяйте снаружи. Проверка, запущенная на той же машине, говорит о процессе, а не о пути, которым пользуются игроки. Запускайте её откуда-нибудь ещё, в идеале из другой сети.
  • Делайте поправку на один потерянный пакет. UDP-ответы теряются. Прежде чем засчитывать сбой, повторите запрос один раз с большим таймаутом, а тревогу поднимайте после трёх сбоев подряд, а не после одного.
  • Проверяйте содержимое, а не только факт ответа. Сравнивайте название сервера и максимум игроков с ожидаемыми. Ответ от чужого сервера - или от сервера, который перезапустился с конфигом по умолчанию, после того как обновление затёрло ваш, - это сбой, который простая проверка «ответил ли он» пропустит.
  • Смотрите на тренд, а не на момент. Число игроков, падающее до нуля каждую ночь в одно и то же время, - это плановый перезапуск. Падающее до нуля в случайные моменты и возвращающееся через две минуты - это цикл падений, и дальше стоит прочитать статью почему игровой сервер постоянно перезапускается.
  • Держите разумный интервал. Раза в минуту более чем достаточно. Ваш собственный монитор, опрашивающий каждую секунду, неотличим от небольшого флуда и может упереться в лимит частоты сервера.

Запрос доказывает, что процесс отвечает. Он не доказывает, что игроки смогут завершить подключение, - это зависит ещё от игрового порта, версии и модов. Считайте его первой линией мониторинга, а не единственной; остальное описано в статье мониторинг, который что-то сообщает.

Когда запрос не проходит, а игра работает#

СимптомВероятная причина
Таймаут, но игроки могут зайтиQuery-порт не выделен, не проброшен или UDP заблокирован
Ответ от чужого сервераОпрашивается не тот порт на общем адресе
Старый инструмент пишет офлайн, новый работаетСтарый инструмент не умеет challenge для A2S_INFO
Иногда работает, под нагрузкой таймаутДостигнут лимит частоты запросов, часто во время флуда
Список игроков пуст, число правильноеИгра скрывает A2S_PLAYER или плагин его обнуляет
Работает с вашего ПК, но не с сайтаСайт использует другой порт или старый протокол

Разбирайтесь снаружи: опросите порт, который вы считаете query-портом, затем игровой порт, затем прочитайте первые строки лога сервера, чтобы узнать, к какому порту он действительно привязался. В любом споре прав лог.

FAQ#

Query-порт - это то же самое, что порт RCON?

Нет. Query-порт отвечает на анонимные запросы статуса по UDP. RCON - это удалённая консоль с аутентификацией, в играх на Source - по TCP, и выставлять её наружу шире необходимого не следует. Об этом - статья безопасный RCON.

Нужен ли открытый query-порт, если сервер приватный?

Игроки могут зайти на приватный сервер по прямому адресу и без него. Но избранное Steam, боты статуса и внутриигровой список - все пользуются query-портом, поэтому, закрыв его, вы сделаете сервер офлайн с точки зрения всех них. Большинство оставляет его открытым и полагается на пароль.

Почему мой инструмент запросов показывает не то число игроков, что игра?

Боты входят в число игроков в A2S_INFO и отдельно указываются в поле ботов. Одни игры считают подключающихся игроков, другие - только полностью появившихся в мире. Небольшая разница нормальна; число, застрявшее на нуле, указывает на мод или неверный порт.

Можно ли использовать A2S для мониторинга аптайма?

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

Поддерживает ли A2S каждая игра Steam?

Большинство выделенных серверов на серверном API Steam отвечают на A2S, но не все, а некоторые отвечают только частично. Игры, которые публикуются в списках через Epic, PlayFab или собственный сервис, часто не отвечают на A2S вовсе. Если сомневаетесь, опросите и посмотрите.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000