Files
stun/README.md
T
2026-09-29 20:25:15 +03:00

113 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Высоконагруженный STUN-сервер на Go
Данный проект представляет собой сверхбыстрый, отказоустойчивый и оптимизированный
под экстремальные нагрузки STUN-сервер (Session Traversal Utilities for NAT),
написанный на языке Go (Golang). Сервер разработан в соответствии со стандартами
RFC 5389 и RFC 8489 и предназначен для использования в инфраструктурах реального
времени (WebRTC, VoIP, SIP, онлайн-игры) с целью определения внешних IP-адресов
и портов клиентов, находящихся за NAT.
Проект полностью контейнеризирован с помощью Docker и Docker Compose, а также
защищен на уровне ядра ОС Linux, что позволяет ему обрабатывать тысячи запросов
в секунду (RPS) на минимальных серверных мощностях.
------------------------------
## 🚀 Архитектурные особенности и ключевые фишки
Архитектура проекта сфокусирована на достижении максимальной пропускной способности
и минимального времени отклика (в тестах задержка составляет всего 20–30 мс):
1. Концепция True Zero-Alloc (Нулевое выделение памяти в куче):
* Вместо аллокации нового буфера под каждый входящий UDP-пакет, каждый независимый
* воркер создает фиксированный срез байт (buf := make([]byte, 1500)) единожды
* перед запуском бесконечного цикла.
* Ответ клиенту формируется в том же самом массиве данных поверх исходного
запроса (buf = buf[:cap(buf)]).
* Сборщик мусора Go (Garbage Collector) вообще не нагружается во время
обработки сетевого флуда. Это исключает микропаузы (STW) сервера под нагрузкой.
2. Использование современного пакета net/netip:
* Взамен старого типа net.IP (который является срезом и всегда аллоцируется в куче),
в коде применен пакет net/netip.
* Объект netip.AddrPort хранит данные в виде легковесной структуры фиксированного
размера, которая обрабатывается исключительно на уровне быстрого стека процессора.
3. Многопоточный пул воркеров (Worker Pool):
* Сервер автоматически определяет количество доступных логических ядер процессора
(runtime.NumCPU()) и запускает строго эквивалентное количество горутин-воркеров.
* Ядро Linux равномерно распределяет сетевые прерывания от UDP-сокета,
утилизируя процессор на все 100%.
4. Мягкое завершение работы (Graceful Shutdown):
* Сервер перехватывает системные сигналы остановки от операционной системы
или Docker-демона (SIGINT, SIGTERM).
* При получении команды на выключение сокет закрывается, воркеры корректно
дорабатывают текущую итерацию, а sync.WaitGroup гарантирует завершение процессов
без потери обрабатываемых пакетов.
------------------------------
## 🐳 Контейнеризация и Безопасность в Docker
Проект поставляется со специализированной конфигурацией развертывания,
обеспечивающей максимальную производительность и изоляцию:
* Многоэтапная сборка (Multi-stage Build): Образ собирается в официальном
контейнере golang:alpine, где происходит компиляция бинарника со флагами
-ldflags="-s -w" (вырезаются отладочные символы, что уменьшает размер файла
в 2 раза).
* Супер-легковесный и безопасный финальный слой (scratch): Финальный образ
собирается на базе абсолютно пустого слоя FROM scratch. Внутри контейнера
нет Linux-дистрибутива, нет пакетных менеджеров, утилит (sh, bash, ls)
и зависимостей. Вес готового Docker-образа составляет всего ~15-20 МБ.
* Принцип Non-Root: Внутри контейнера процесс STUN-сервера запускается
от имени непривилегированного системного пользователя stunuser.
В случае гипотетической уязвимости в коде, злоумышленник не сможет выйти на
хост-систему или получить права суперпользователя.
* Сетевой режим network_mode: "host": Контейнер подключен к сетевой карте
VPS напрямую. Это исключает прослойку docker-proxy и встроенную маршрутизацию
Docker, которая является главным «узким горлышком» для UDP-трафика и приводит
к потере пакетов под нагрузкой.
------------------------------
## 🛠️ Системный тюнинг ОС Linux под Highload UDP
По умолчанию буферы сетевых сокетов в Linux настроены на низкую нагрузку.
При шторме UDP-запросов буфер быстро переполняется, и ядро ОС начинает молча
дропать (терять) пакеты еще до того, как Go-приложение успеет их прочитать.
Для решения этой проблемы в проекте применяется сетевой тюнинг ядра через
конфигурацию sysctl. Максимальные и дефолтные буферы приема (rmem) и отправки
(wmem) сетевых сокетов расширяются до 25 Мегабайт (26214400 байт):
* net.core.rmem_max=26214400
* net.core.wmem_max=26214400
Эти настройки жестко фиксируются в системном файле /etc/sysctl.conf хост-машины,
благодаря чему сервер гарантированно выдерживает кратковременные всплески трафика
(микро-штормы), удерживая пакеты в памяти ОС до момента их вычитки воркерами.
------------------------------
## 📋 Структура конфигурационных файлов проекта
Проект имеет модульную и простую структуру, состоящую из следующих компонентов:
1. main.go — Весь исходный код сервера. Содержит логику инициализации,
пул воркеров, парсинг сырых байт STUN-заголовка, генерацию XOR-маппинга для
обхода NAT и отправку ответов.
2. go.mod — Файл описания Go-модуля (проект STUN на базе Go 1.27.1) с
автоматической фиксацией низкоуровневых системных зависимостей (golang.org/x/sys).
3. Dockerfile — Инструкция сборки безопасного микро-контейнера на базе
scratch от лица non-root пользователя.
4. docker-compose.yml — Манифест для оркестрации: настраивает запуск в
режиме host, прокидывает переменные окружения, ограничивает ресурсы
(Limits: 2 CPU, 128MB RAM) и защищает диск от переполнения логами
(ротация логов ограничена 3 файлами по 10МБ).
5. .env — Файл конфигурации переменных среды, где хранится внешний публичный
IPv4-адрес сервера (PUBLIC_IP), необходимый для корректного заполнения
STUN-аттрибутов ответа.
------------------------------
## 📈 Результаты тестирования пропускной способности
В ходе стресс-тестирования методом асинхронного UDP-флуда
(параллельный запуск тысячи клиентов turnutils_stunclient), сервер показал
следующие результаты:
* Линейная масштабируемость: Обработка пакета из 2000 одновременных
запросов выполняется за ~1.19 сек, демонстрируя чистую скорость ~1550-1700 RPS
(запросов в секунду) на один клиентский поток тестирования.
* Параллельная утилизация ядер: Время работы в пользовательском пространстве
(user) и пространстве ядра (sys) превышает реальное время теста (real),
что доказывает идеальную параллельную работу пула воркеров на всех доступных
ядрах процессора хоста.