Привет, Хабр!
В этой статье рассмотрим, как NGINX обрабатывает TCP/UDP‑соединения: от принятия запроса до логирования.
Архитектура обработки TCP/UDP в NGINX
NGINX построен на неблокирующей модели ввода‑вывода с использованием событийного цикла (еще называют event loop). Это позволяет обрабатывать десятки тысяч соединений одновременно. На уровне TCP/UDP NGINX использует так называемые stream‑модули, которые работают независимо от HTTP‑блока. Основные фазы обработки TCP/UDP‑сессии следующие:
-
Post‑accept
-
Pre‑access
-
Access
-
SSL
-
Preread
-
Content
-
Log
Каждая из этих фаз выполняет свою задачу.
Post-accept: начало пути каждого соединения
Сразу после того, как ядро NGINX принимает новое соединение (через non‑blocking accept() и системные вызовы типа epoll), начинается фаза Post‑accept. Здесь запускается модуль ngx_stream_realip_module, который корректирует IP‑адрес клиента, если он пришёл через промежуточное устройство (например, балансировщик или обратный прокси).
Приём соединений осуществляется асинхронно, что позволяет избежать задержек при большом количестве параллельных подключений, а сам модуль ngx_stream_realip_module заменяет исходный IP на реальный, используя значения из специальных заголовков (например, X‑Real‑IP).
Пример конфига:
stream { server { listen 12345; # Используем модуль realip для определения реального IP real_ip_header X-Real-IP; set_real_ip_from 192.168.1.0/24; # Продолжаем маршрутизацию трафика proxy_pass backend; } }
Даже если клиент за NAT»ом, всегда можно будет узнать его настоящий адрес.
Pre-access: фильтрация
На фазе Pre‑access NGINX проверяет предварительные параметры соединения. Здесь срабатывают модули типа ngx_stream_limit_conn_module для ограничения количества соединений, а также ngx_stream_set_module для установки переменных и конфигурационных параметров.
shared memory‑зоны позволяют вести учёт активных соединений по ключу (например, IP‑адресу). Также можно задавать условия и правила для последующей обработки соединения.
Пример конфига:
stream { # Определяем shared memory зону для хранения информации о соединениях limit_conn_zone $binary_remote_addr zone=addr:10m; server { listen 12345; # Ограничиваем количество соединений с одного IP до 10 limit_conn addr 10; # Здесь можно задать дополнительные параметры через ngx_stream_set_module # Например, установка кастомных переменных для дальнейшей обработки proxy_pass backend; } }
Таким образом, ещё на этом этапе можно отбросить плохих парней и сэкономить ресурсы.
Access: контроль доступа
Фаза Access — это контрольный пункт, где решается, имеет ли клиент право на доступ к сервису. Модуль ngx_stream_access_module проверяет правила доступа, а если вы используете njs — применяется директива js_access.
Пример конфигурации с фильтрацией:
stream { server { listen 12345; # Разрешаем доступ только из определённой подсети allow 192.168.1.0/24; deny all; proxy_pass backend; } }
Пример с njs для динамического контроля:
stream { js_import my_access from js/my_access.js; server { listen 12345; js_access my_access.check; proxy_pass backend; } }
И в файле js/my_access.js:
function check(ctx) { // Если IP не начинается с нужного префикса, отклоняем соединение if (!ctx.remoteAddress.startsWith("192.168.1.")) { return ctx.error(403); } } export default { check };
Так можно выдумать какую‑нибудь логику, адаптированную под нужды.
SSL
Фаза SSL активируется, если настроили TLS/SSL для TCP/UDP‑соединений. Модуль ngx_stream_ssl_module занимается установлением защищённого канала, выполняет TLS‑рукопожатие, проверяет сертификаты и договаривается об алгоритмах шифрования.
Пример конфигурации SSL:
stream { server { listen 443 ssl; # Пути к SSL-сертификату и приватному ключу ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; # Настройки протоколов и шифров для обеспечения безопасности ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; proxy_pass backend; } }
Prerea
Фаза Preread — одна из самых интересных. Здесь NGINX читает первые байты входящего потока в специальный буфер. Зачем? Чтобы модули, такие как ngx_stream_ssl_preread_module, могли проанализировать данные до их полной обработки. Для пользователей njs это директива js_preread.
Первые N байт (обычно 1–2 КБ) считываются и сохраняются для предварительного анализа.
Пример конфигурации с включенным preread:
stream { server { listen 443; # Включаем предварительное чтение для анализа первых байт ssl_preread on; proxy_pass backend; } }
Пример с njs для кастомного анализа:
stream { js_import my_preread from js/my_preread.js; server { listen 443; js_preread my_preread.analyze; proxy_pass backend; } }
А в файле js/my_preread.js:
function analyze(ctx) { // Если первые байты содержат ключевое слово, задаем переменную для последующей маршрутизации if (ctx.buffer && ctx.buffer.startsWith("SPECIAL")) { ctx.variables.special = true; } } export default { analyze };
Так можно понимать заранее суть трафика и принимать решения еще до основной обработки.
Content
Фаза Content — это место, где осуществляется вся работа с данными. Здесь NGINX либо напрямую передает данные на бекенд через proxy_pass, либо модифицирует их с помощью njs‑скриптов (директива js_filter).
Пример простого проксирования:
stream { upstream backend { server 127.0.0.1:8000; server 127.0.0.1:8001; } server { listen 12345; proxy_pass backend; } }
Пример с динамической фильтрацией через njs:
stream { js_import my_filter from js/my_filter.js; upstream backend { server 127.0.0.1:8000; server 127.0.0.1:8001; } server { listen 12345; js_filter my_filter.process; proxy_pass backend; } }
А в файле js/my_filter.js:
function process(ctx) { // Пример: заменяем все вхождения "foo" на "bar" в передаваемом буфере if (ctx.buffer) { ctx.buffer = ctx.buffer.replace(/foo/g, "bar"); } return ctx; } export default { process };
Log
Финальная фаза, Log, отвечает за запись результатов работы сервера. Модуль ngx_stream_log_module фиксирует время обработки, статус соединения, IP клиента и другую полезную информацию. Плюс есть возможность задавать кастомный формат, добавляя нужные переменные, а сами логи можно отправлять в ELK‑стек, Prometheus, Grafana и другие инструменты.
Пример простой настройки логирования:
stream { server { listen 12345; access_log /var/log/nginx/stream_access.log; error_log /var/log/nginx/stream_error.log; proxy_pass backend; } }
Пример с njs для кастомного логирования:
stream { js_import my_logger from js/my_logger.js; server { listen 12345; js_log my_logger.customLog; proxy_pass backend; } }
В файле js/my_logger.js:
function customLog(ctx) { // Логируем важные параметры: IP клиента, длительность соединения, статус обработки console.log(`Client IP: ${ctx.remoteAddress}, Duration: ${ctx.duration}ms, Status: ${ctx.status}`); } export default { customLog };
Больше актуальных навыков по IT-инфраструктуре вы можете получить в рамках практических онлайн-курсов от экспертов отрасли. В каталоге можно посмотреть список всех программ, а в календаре — записаться на открытые уроки.
ссылка на оригинал статьи https://habr.com/ru/articles/889954/
Добавить комментарий