Skip to content

feat(server): рейт-лимит на игровой вход — кап соединений и скорости per-IP (итер. 33) - #45

Merged
Azazlokus merged 1 commit into
mainfrom
feat/join-ratelimit
Aug 18, 2026
Merged

Azazlokus merged 1 commit into
mainfrom
feat/join-ratelimit

Conversation

@Azazlokus

Copy link
Copy Markdown
Owner

Что

Рейт-лимит на игровой вход (/ws и /rtc) — итерация 33 (лейн Инфра/прод).
Пер-IP шлюз joinGate до апгрейда: кап живых соединений (ARENA_JOIN_MAX_PER_IP,
деф. 16) — слот занят ровно пока жива сессия (обработчик блокируется весь её срок; защита
от удержания горутин/комнат/сокетов), и токен-бакет скорости новых соединений
(ARENA_JOIN_RATE_BURST/_WINDOW, деф. 30/1m; защита от лавины). Исчерпал — 429
(у рейта — с Retry-After), не доходя до рукопожатия и комнаты. Оба лимитера общие
для /ws и /rtc. Метрика arena_join_rejected_total{reason} (rate/concurrent).

Токен-бакет вынесен из auth-лимита (итер. 21) в новый чистый пакет internal/ratelimit
(Limiter + ConnLimiter + ClientKey; без HTTP/game/сети); internal/api рефакторнут в
тонкий HTTP-адаптер над ним (дубля алгоритма больше нет), алгоритм-тесты переехали в ratelimit.

Зачем

Auth-эндпоинты рейт-лимитятся с итер. 21, но игровой вход — нет: один IP мог лавиной
открывать WS-соединения или держать тысячи долгоживущих сессий (апгрейд + горутина + join +
вход в комнату), исчерпывая ресурсы. Кап живых соединений — главная защита именно игрового
сервера (соединения долгоживущие, в отличие от быстрых auth-POST'ов).

Как

  • internal/ratelimit/ — Limiter (токен-бакет, ленивый свип, инъектируемые часы),
    ConnLimiter (кап конкурентных слотов, Acquire/Release, карта не растёт), ClientKey
    (ключ из RemoteAddr / первого элемента доверенного заголовка). Оба конструктора при
    выключении дают nil, методы nil-безопасны. Конкурентно под мьютексом, без фоновых горутин.
  • internal/api/ratelimit.go — тонкий адаптер: clientKey(r) → ratelimit.ClientKey,
    middleware → Limiter.Allow. Поведение auth-эндпоинтов идентично прежнему.
  • cmd/server/joingate.go — middleware вокруг gateway/rtcGateway: Allow (rate) →
    Acquire/defer Release (conns) → next. За прокси — ARENA_JOIN_RATE_IP_HEADER.
  • Проводка в main.go (общие лимитеры на оба хэндлера), env в config.go, метрика в
    internal/metrics.

Тестирование

  • internal/ratelimit: Limiter (burst/дозаправка/кап/изоляция/свип/конкурентность -race,
    ClientKey), ConnLimiter (кап/изоляция/очистка карты/конкурентность -race — пик слотов
    не превышает max).
  • cmd/server: joingate_test.go (кап держит слот и отдаёт 429 «concurrent» до обработчика,
    освобождение слота, рейт+Retry-After «rate», сквозной путь при выключении, ключ из
    заголовка), TestJoinRateConfig (env), integration TestE2EJoinConcurrencyCap (реальный WS:
    второе одновременное соединение отклонено, после ухода первого — снова проходит).
  • make check (lint 0 issues + -race все пакеты) и make integration — зелёные.
  • Игровые стражи не гонялись (игра/провод/симуляция/Checksum не тронуты — прецедент
    итер. 21); вместо этого security-review — findings нет.

…per-IP (итер. 33)

Защищает /ws и /rtc от флуда соединений и удержания горутин/комнат/сокетов
одним IP. joinGate (cmd/server) — middleware до апгрейда: пер-IP кап живых
соединений (слот занят на время сессии) + токен-бакет скорости новых; оба
лимитера ОБЩИЕ для обоих транспортов, при исчерпании — 429.

Токен-бакет вынесен из auth-лимита (итер. 21) в новый чистый пакет
internal/ratelimit (Limiter + ConnLimiter + ClientKey; без HTTP/game/сети);
internal/api рефакторнут в тонкий адаптер над ним, алгоритм-тесты переехали.

Метрика arena_join_rejected_total{reason}. Env ARENA_JOIN_MAX_PER_IP(16) /
_RATE_BURST(30) / _RATE_WINDOW(1m) / _RATE_IP_HEADER. Игра/провод/симуляция
не тронуты; security-review — findings нет.
@Azazlokus
Azazlokus merged commit 53eea95 into main Aug 18, 2026
10 checks passed
@Azazlokus
Azazlokus deleted the feat/join-ratelimit branch August 18, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant