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

f31fe1db143cbcbea9373594da656975.jpg

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

В этой статье рассмотрим, как 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-инфраструктуре вы можете получить в рамках практических онлайн-курсов от экспертов отрасли. В каталоге можно посмотреть список всех программ, а в календаре — записаться на открытые уроки.

© Habrahabr.ru