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

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