Перейти к содержанию

Устройство conntrack и отслеживания соединений

Отслеживание соединений (conntrack) — подсистема ядра, делающая возможным stateful-файрвол — именно она позволяет правилу вроде ct state established,related accept (из статьи про файрвол) знать, какие пакеты принадлежат уже разрешённому наружу соединению. Это также часто упускаемый источник реальных продакшн-инцидентов, когда её таблица заполняется.

Как это устроено

Что именно отслеживает conntrack

Каждое TCP-соединение и каждый UDP-«поток» (у UDP нет настоящих соединений, но conntrack эвристически отслеживает пары запрос/ответ по таймауту) получает запись в таблице внутри ядра: исходный/целевой IP, исходный/целевой порт, протокол и текущее состояние.

cat /proc/sys/net/netfilter/nf_conntrack_max
# 262144

Это максимальное число отслеживаемых соединений, которое ядро будет держать одновременно. Каждая запись потребляет небольшой, фиксированный объём памяти ядра (примерно 300-350 байт в зависимости от версии ядра) — 262144 записей это порядка 80-90МБ RAM, немного для современного сервера, поэтому это значение по умолчанию часто оказывается слишком низким для нагрузок с высоким числом соединений (загруженный обратный прокси или NAT-шлюз может исчерпать его за минуты под реальной нагрузкой).

Просмотр таблицы

sudo conntrack -L | head -5

Типичный вывод:

tcp 6 431999 ESTABLISHED src=10.0.0.5 dst=203.0.113.10 sport=51234 dport=443 \
    src=203.0.113.10 dst=10.0.0.5 sport=443 dport=51234 [ASSURED] mark=0 use=1

Читаем: протокол (tcp), номер протокола (6), оставшийся таймаут в секундах (431999), состояние (ESTABLISHED), затем оба направления потока — исходное направление и ожидаемое направление ответа, именно та информация, на которую опираются правила ct state established,related. [ASSURED] означает, что хотя бы один пакет был замечен в обоих направлениях, отмечая это как настоящее, двунаправленное соединение (в отличие от одностороннего — возможно, просто скан или соединение, сброшенное до завершения).

sudo conntrack -C                        # текущее число записей в таблице
sudo conntrack -L -p tcp --dport 443     # фильтр: только HTTPS-записи

Режим отказа: исчерпание таблицы

dmesg | grep -i "conntrack: table full"

Когда таблица заполняется, ядро молча отбрасывает новые попытки соединения — без ошибки, которую клиент мог бы осмысленно интерпретировать, просто как если бы пакет никогда не пришёл. Это даёт очень специфичный, сбивающий с толку симптом: сервис, нормально работающий при низкой нагрузке, вдруг загадочно начинает отказывать в новых соединениях (пока существующие продолжают работать) как только число одновременных соединений пересекает некий порог — классическое исчерпание conntrack, легко ошибочно принимаемое за баг приложения.

Увеличение лимита

# Runtime (теряется при перезагрузке):
sudo sysctl -w net.netfilter.nf_conntrack_max=1048576

# Постоянно:
echo "net.netfilter.nf_conntrack_max = 1048576" | sudo tee -a /etc/sysctl.d/99-conntrack.conf
sudo sysctl --system

Разумный подход к расчёту размера: оцените реалистичное пиковое число одновременных соединений для нагрузки, умножьте на запас прочности (2-3x), и выставите лимит соответственно — вместо выбора произвольно большого числа, которое просто резервирует больше памяти ядра, чем нужно.

Намеренный обход conntrack (когда он не нужен)

Для очень высокопроизводительного, по сути безсостояточного трафика (некоторые DNS-резолверы, определённые конфигурации балансировщиков нагрузки) иногда правильно явно исключить конкретный трафик из отслеживания соединений целиком через цель NOTRACK (iptables) или notrack (nftables), снижая накладные расходы на пакет — но только после подтверждения, что ничего нижестоящее (stateful-правило файрвола, NAT) не зависит от того, что этот трафик отслеживается.

Различия Ubuntu/Debian и RHEL-подобных систем

Сама подсистема conntrack идентична в коде ядра во всех дистрибутивах. Утилиты пространства пользователя могут быть не установлены по умолчанию: пакет называется conntrack в Debian/Ubuntu (sudo apt install conntrack) и conntrack-tools в RHEL-подобных системах (sudo dnf install conntrack-tools).

Практическое задание

  1. Проверьте текущий nf_conntrack_max и текущее использование: cat /proc/sys/net/netfilter/nf_conntrack_max и sudo conntrack -C.
  2. Откройте несколько исходящих соединений одновременно (например, пачку параллельных запросов curl к разным хостам) и убедитесь, что счётчик в conntrack -C соответственно растёт.
  3. Отфильтруйте таблицу, чтобы показать только записи, относящиеся к одному из этих соединений, через conntrack -L -p tcp --dport <port>.

Как проверить, что всё сделано правильно

  • Шаг 2: счётчик conntrack -C должен вырасти примерно на число открытых новых соединений, и осесть обратно по мере их закрытия и истечения таймаута их записей в таблице.
  • Шаг 3: отфильтрованный вывод должен показать именно записи, совпадающие с вашими тестовыми соединениями, не пустой результат (подтверждая, что синтаксис фильтра корректен) и не всю таблицу (подтверждая, что фильтр реально ограничивает, а не совпадает со всем).

Типичные ошибки

  • Диагностика перемежающегося «connection refused под нагрузкой» как бага приложения без проверки dmesg на сообщения о заполненной таблице conntrack — это симптом уровня ядра, который логи приложения вообще не покажут.
  • Выставление nf_conntrack_max в чрезмерно большое число «на всякий случай» без учёта (скромной, но ненулевой) стоимости памяти на системах с ограниченной памятью.
  • Забыть про постоянный sysctl-конфиг, исправив симптом только до следующей перезагрузки, которая молча откатит его на старое значение по умолчанию.

⚠ Опасные команды

  • conntrack -F (flush) сбрасывает состояние каждого отслеживаемого соединения сразу. Трансляции NAT теряются, поэтому установленные соединения через NAT рвутся немедленно. Обычные TCP-потоки чаще всего подхватываются заново как ESTABLISHED по следующему пакету (nf_conntrack_tcp_loose=1, значение по умолчанию), но UDP-потоки и строгие наборы правил могут внезапно начать терять пакеты. На загруженном шлюзе/файрволе это может массово разорвать легитимный трафик.

Связанные статьи

Контрольные вопросы

  1. Почему заполнение таблицы conntrack даёт «молчаливый сброс», а не явную ошибку клиенту?
  2. Что означает [ASSURED] в записи conntrack -L, и почему это важно?
  3. Почему сервис может нормально работать при низком числе соединений и внезапно начать отказывать новым соединениям только после пересечения определённого порога нагрузки?
  4. В чём практический риск запуска conntrack -F на продакшн-хосте файрвола/шлюза с набором правил, зависящим от established-состояния?