Фаерволы, NAT и проблемы MTU
Классическая жалоба: локально сервис отвечает, снаружи нет. Причин ровно три, и они проверяются по порядку за пару минут - адрес, на котором сервис слушает, фильтрация и маршрут. Отдельный сюжет рядом: порт, закрытый фаерволом (набором правил ядра о том, что пускать в машину, а что нет), при этом спокойно доступен из интернета, если это контейнер.
Стержень: сначала смотрят, на каком адресе висит сервис, потом кто фильтрует; публикация порта контейнера идёт мимо привычных правил фаервола.
// Формулировки: «локально отвечает, снаружи нет», «почему ufw не защитил порт контейнера?», «маленькие запросы проходят, большие висят»
Порядок локализации
Первое: на каком адресе слушает сервис. Команда ss с ключами -lptn показывает все слушающие сокеты. На этой машине висят 0.0.0.0:80, 0.0.0.0:443 и 0.0.0.0:22 - четыре нуля означают «на всех сетевых картах», такой порт доступен снаружи. А рядом 127.0.0.53:53 - служба имён, и она видна только с самой машины. Если сервис слушает на 127.0.0.1, снаружи он недоступен в принципе, и никакие правила фаервола тут ни при чём.
Второе: фильтрация. Тут выручает разница в поведении, которую я замерял отдельно. Отказ приходит мгновенно, за доли миллисекунды, и означает «до машины дошли, но на этом порту никто не слушает». Молчание до самого предела ожидания, у меня 3003 миллисекунды, означает, что ответа не было вовсе: пакет отбросили молча или потерялся обратный путь. Первое - неверный порт или упавший процесс, второе - фильтр где-то по дороге или неверный маршрут.
// Третье: чужие правила. В облаке поверх машины есть свои списки разрешений, и порт может быть закрыт там, а не на самой машине. Проверять полезно с обеих сторон сразу: запрос с соседней машины и просмотр приходящих пакетов на целевой - это моментально показывает, доходят ли они вообще.
- адрес прослушивания
- 0.0.0.0 означает все сетевые карты, 127.0.0.1 - только сама машина
- отказ против молчания
- мгновенный отказ - дошли и никого нет; тишина до предела - фильтр или маршрут
Публикация порта контейнера проходит мимо ufw
Правила фаервола разложены по цепочкам, и ядро проходит их в определённом порядке. Привычная надстройка ufw (в Ubuntu она пишет правила ядра за вас, чтобы не возиться с ними руками) кладёт свои запреты в ту цепочку, через которую идут пакеты, адресованные самой машине.
Пакет, адресованный контейнеру, идёт другим путём. Демон docker добавляет собственное правило подмены адреса назначения, и оно разбирается РАНЬШЕ, чем цепочка с запретами ufw: пакет заворачивается в контейнер, минуя её. Я смотрел эти правила на своём сервере - там строки вида «порт 443 перенаправить на 172.18.0.4:443». Результат прямой: порт, «закрытый» в ufw, доступен из интернета. Проверял и это - публикация без указания адреса отвечает и на внешний адрес машины тоже.
// Лечится тремя способами. Публиковать порт на конкретный адрес, например только на 127.0.0.1, и тогда снаружи его нет вовсе. Класть свои запреты в отдельную цепочку DOCKER-USER, которую демон читает раньше своих правил. Либо запретить демону трогать правила ядра и написать всё самому - надёжно, но дальше это твоя забота навсегда.
- цепочка правил
- список правил фаервола, который ядро проходит по порядку
- подмена адреса назначения
- правило, заворачивающее пакет с порта машины внутрь контейнера
- DOCKER-USER
- цепочка, которую демон читает раньше своих правил; туда кладут свои запреты
Маленькие запросы проходят, большие висят
Есть отдельный класс аварий, где рукопожатие проходит, короткие ответы приходят, а стоит отправить что-то крупное - тишина. Причина в предельном размере пакета: у каждой сетевой карты есть потолок, MTU (maximum transmission unit). Замерил на этой машине: пакет с 1472 байтами полезных данных проходит, а с 1473 - уже нет, и приходит ошибка «Frag needed». Всё сходится: 1472 плюс 28 байт служебных заголовков дают ровно 1500, а 1500 и стоит потолком на всех сетевых картах этой машины.
Обычно это никого не беспокоит: если пакет великоват, отправителю приходит служебное сообщение по протоколу ICMP (internet control message protocol) «нужна фрагментация», и он уменьшает размер. Ломается всё там, где такие служебные сообщения вырезаны фаерволом «на всякий случай». Тогда отправитель ничего не узнаёт и продолжает слать пакеты, которые молча пропадают.
// Особенно часто это ловят на туннелях. Туннель - это канал, внутри которого пакеты едут упакованными в другие пакеты; упаковка добавляет свои заголовки, поэтому реальный потолок внутри туннеля меньше обычного. Лечения два: выставить правильный потолок на интерфейсе или включить принудительное уменьшение размера сегмента, MSS (maximum segment size) clamping, - тогда стороны договорятся о меньшем размере ещё на рукопожатии. И перестать вырезать служебные сообщения о размере.
- MTU
- предельный размер пакета, который пролезает в сетевую карту
- MSS clamping
- принудительное уменьшение размера сегмента при установке соединения
Как отвечать: «В ufw порт закрыт, а контейнер доступен из интернета. Почему?»
Потому что публикация порта контейнера делается правилом подмены адреса назначения, а оно разбирается раньше той цепочки, куда ufw пишет свои запреты. Пакет заворачивается внутрь контейнера и цепочку с запретами просто не проходит, поэтому фаервол машины такой трафик не видит. Я это проверял на своём сервере: публикация без указания адреса отвечает и на внешний адрес тоже, а правила демона видно прямо в таблице подмены. Лечится тремя способами: публиковать порт на конкретный адрес, например только на локальный, и снаружи его не будет вовсе; писать свои запреты в цепочку DOCKER-USER, которую демон читает раньше своих; либо запретить демону управлять правилами и настроить всё самому.
Ответ объясняет путь пакета и даёт три рабочих варианта вместо общего «настройте фаервол правильно». Первый вариант закрывает вопрос в одну строку конфигурации.
На чём валятся
- −Закрывают порт в ufw и считают контейнер защищённым.
- −Не смотрят адрес прослушивания и ищут фильтр там, где сервис висит на локальном адресе.
- −Не различают мгновенный отказ и молчание до предела ожидания.
- −Списывают зависание больших запросов на приложение, не проверив предельный размер пакета.
- −Вырезают служебные сообщения о размере пакета и получают аварии на туннелях.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.
- Через туннель соединение устанавливается, короткие ответы ходят, а большие запросы висят и отваливаются по таймауту. Диагноз?A)Асимметричная маршрутизация: ответы уходят другим путёмB)Кончились conntrack-записи, и новые сессии рвутсяC)Чёрная дыра PMTU: крупные пакеты не проходят, а ICMP заблокированD)Переполнена очередь на интерфейсе, пакеты отбрасываются
показать ответ и разбор
+C)Чёрная дыра PMTU: крупные пакеты не проходят, а ICMP заблокирован// разбор: У туннеля MTU меньше, чем у обычного интерфейса. Отправитель шлёт пакет по-старому с флагом «не фрагментировать», промежуточный узел обязан вернуть ICMP «fragmentation needed», а этот тип часто вырезан фаерволом. В итоге рукопожатие (мелкие пакеты) проходит, а первый крупный сегмент теряется молча и уходит в бесконечные ретрансмиссии. Лечится MSS clamping или корректным MTU на интерфейсе.
- На хосте default-deny на входящие. Исходящее подключение к внешнему API работает, хотя «обратный» порт не открыт. Почему?A)Фаервол stateful: ответы на исходящее идут как establishedB)Исходящие подключения фаервол не фильтрует в принципеC)Внешний API сам пробивает обратный путь через NAT хостаD)Ответ приходит на тот же порт, что и запрос, потому и проходит
показать ответ и разбор
+A)Фаервол stateful: ответы на исходящее идут как established// разбор: Stateful-фаервол отслеживает соединения: ответный трафик на инициированное тобой подключение матчится как ESTABLISHED/RELATED и пропускается — эфемерный обратный порт открывать не нужно. Поэтому default-deny на входящие + allow established позволяет исходящим вызовам работать. В stateless-ACL пришлось бы вручную разрешать обратные порты.
- Клиент к сервису: то сразу connection refused, то висит и отваливается по таймауту. Что каждый случай говорит?A)И refused, и таймаут значат, что сервис перегруженB)refused — недоступна сеть, таймаут — неправильный DNSC)refused (RST) — порт закрыт; таймаут — пакет дропнутD)refused бывает только локально, а таймаут только по сети
показать ответ и разбор
+C)refused (RST) — порт закрыт; таймаут — пакет дропнут// разбор: connection refused = быстро пришёл RST: на порту никто не слушает или фаервол REJECT'ит. Зависание с таймаутом = на SYN нет ответа: фаервол молча DROP'ит, хост лежит или потеря пакетов. Быстрый отказ = «до хоста дошли, порт закрыт»; таймаут = «пакеты не доходят/дропаются». Разные лечения: поднять сервис/открыть порт vs разбираться с маршрутом/DROP/лежащим хостом.
- Под пиковой нагрузкой шлюз теряет пакеты, в dmesg — nf_conntrack: table full, dropping packet. Что произошло?A)Кончились эфемерные порты на исходящих подключенияхB)Переполнился ARP-кэш из-за множества соседей в сетиC)MTU слишком мал, из-за фрагментации пакеты и теряютсяD)Переполнилась таблица conntrack соединений
показать ответ и разбор
+D)Переполнилась таблица conntrack соединений// разбор: Stateful netfilter (фаервол/NAT) держит каждое соединение в таблице conntrack; под флудом новых соединений она переполняется, и ядро дропает новые пакеты («table full»). Симптом — перемежающиеся потери при высоком темпе подключений. Лечится: поднять nf_conntrack_max и hashsize, подтюнить таймауты (короче для TIME_WAIT/established), снизить churn (keep-alive/пул) или NOTRACK для трафика, которому отслеживание не нужно.
- Что делает межсетевой экран (firewall) на сервере?A)Шифрует весь исходящий трафикB)Пропускает или отбрасывает пакетыC)Ускоряет соединения за счёт кэшированияD)Проверяет содержимое файлов на вирусы
показать ответ и разбор
+B)Пропускает или отбрасывает пакеты// разбор: Правила решают, что делать с пакетом по адресу, порту и протоколу — пропустить, отбросить молча или отказать. Отсюда и разница в поведении клиента: явный отказ даёт мгновенное connection refused, а тихий сброс — таймаут.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.