🌐 Задача-ловушка: Пропавший трафик после настройки iptables
Условие:
На сервере настроен простой iptables-фильтр для блокировки всего входящего трафика, кроме SSH:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
После применения этих правил вы проверяете, что SSH соединения работают нормально (локально и удалённо). Сервер отвечает на пинги, всё ок.
На следующий день вы добавляете локальный контейнер (например, Docker), запускаете его с пробросом порта:
docker run -d -p 8080:80 nginx
❗️ Проблема: Снаружи контейнер не доступен. В браузере или curl получаете Connection refused. Но в ss -tlnp порт 8080 виден и слушает.
Дополнительно, если выполнить:
curl http://localhost:8080
— сервер отвечает нормально. Но с других машин — нет.
❓ Вопрос:
Почему порт 8080 недоступен извне? В чём подвох с iptables? Как починить проблему?
🔍 Подсказка:
Docker использует nat таблицы и PREROUTING`/`FORWARD цепочки для проброса портов.
✅ Разбор:
💥 Ловушка:
Ваш iptables-правила:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
запрещают ВСЁ, кроме SSH. Вы думаете, что входящие соединения через Docker должны идти через INPUT, но это не так!
Когда Docker пробрасывает порт с хоста (например, 8080), схема такая:
- Входящий пакет попадает в цепочку PREROUTING (nat)
- Потом через FORWARD, если пакет не адресован хосту напрямую, а перенаправлен внутрь контейнера
И тут главная ловушка: даже если контейнер слушает порт на 0.0.0.0, Docker обрабатывает проброс трафика через FORWARD, а у вас политика по умолчанию:
iptables -P FORWARD DROP
Поэтому трафик до контейнера блокируется именно на этапе FORWARD.
🔧 Как проверить:
Запустить:
iptables -L -v -n
Вы увидите, что счётчик пакетов в FORWARD показывает дропы.
🛠 Как починить:
Добавить правило разрешения проброса пакетов для Docker:
iptables -A FORWARD -o docker0 -j ACCEPT
Или (более строго):
iptables -A FORWARD -i eth0 -o docker0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i docker0 -o eth0 -j ACCEPT
✅ Вывод:
• В Linux iptables цепочки работают по строгой логике:
- INPUT — для пакетов к хосту
- FORWARD — для пакетов, которые переходят через хост (например, контейнеры или маршрутизаторы)
• Даже если кажется, что контейнер слушает на 0.0.0.0, проброс портов Docker работает через FORWARD, а не напрямую через INPUT.
💡 Бонус-вопрос:
Что изменится, если вы запустите контейнер с флагом --network=host? Какие цепочки будут задействованы тогда?