Как NGINX обрабатывает TCP/UDP

от автора

Привет, Хабр!

В этой статье рассмотрим, как NGINX обрабатывает TCP/UDP‑соединения: от принятия запроса до логирования.

Архитектура обработки TCP/UDP в NGINX

NGINX построен на неблокирующей модели ввода‑вывода с использованием событийного цикла (еще называют event loop). Это позволяет обрабатывать десятки тысяч соединений одновременно. На уровне TCP/UDP NGINX использует так называемые stream‑модули, которые работают независимо от HTTP‑блока. Основные фазы обработки TCP/UDP‑сессии следующие:

  1. Post‑accept

  2. Pre‑access

  3. Access

  4. SSL

  5. Preread

  6. Content

  7. 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *