После предыдущего поста у меня в голове не совсем укладывалось, как именно идут пакеты в контейнеры и обратно. Почему они проходят именно такие цепочки и именно в такой последовательности?

Я разложил для себя все по полочкам и собрал в статью. Получилось несколько нудно, но может быть такая детализация будет кому-то полезна.

Пост больше о работе iptables на роутере.

Итак, как же проходит трафик через iptables в контейнеры докера и из контейнеров докера? Как докер модифицирует iptables, чтобы вся система работала?

Ментальная модель

Самая простая для понимания ментальная модель такая. Представьте, что контейнеры докера - это отдельные компьютеры во внутренней сети. Каждый из них имеет свой внутренний айпишник (обычно 172.17.0.Х). А Docker Host - это компьютер, соединяющий внутреннюю сеть с интернетом.

docker bridge network scheme

Поскольку наш Docker Host подключен к двум сетям, у него есть две "сетевые карты", два сетевых интерфейса - eno1 подключен к внешней сети, а docker0 - ко внутренней.

Конечно, в вашем случае названия интерфейсов и их количество могут различаться, но принцип не меняется.

И вот тут самое главное: комьютер Docker Host работает как роутер для "компьютеров" внутренней сети. Он пересылает пакеты с одной сетевой карты на другую, а пути делает разные модификации, чтобы все работало - MASQUERADE, DNAT, SNAT.

Мы не будем погружаться в механизмы трансляции адресов (nat). Для нас важно понять только тот факт, что весь трафик в конейтеры и обратно является FORWARD-трафиком. То есть, он не адресован компьютеру, на котором запущен демон докера.

А где фильтруется пересылаемый трафик?

Фильтрация трафика в iptables

iptables так устроен, что правила фильтрации трафика прописываются только в одном месте. Это очень удобно и очень просто.

Точнее таких места два - цепочка INPUT и цепочка FORWARD. Но они не пересекаются.

В цепочку INPUT попадают пакеты, адресованные локальным программам.
В цепочку FORWARD - пакеты, которые пересылаются.

Поскольку контейнеры докера - это отдельные компьютеры во внутренней сети, как мы помним из нашей ментальной модели ☝️, то весь трафик в контейнеры докера проходит только цепочку FORWARD.

Давайте посмотрим, какие именно правила добавляет докер в таблицу FORWARD:

$ sudo iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT

Пользовательские правила располагаются в цепочке DOCKER-USER. Если вы в нее ничего не добавили, то там нет никакой фильтрации. О настройке этой цепочки читайте мою статью: как настроить пользовательские правила iptables для docker.

Трафик из контейнеров матчится правилами -i docker0. Помните, мы разбирали путь пакетов выше? Входят через интерфейс docker0, а выходят через какой-то другой во внешнюю сеть.

container -> -i docker0 -o eno1 -> internet

Трафик в конейнеры попадает под действие правил -o docker0.

internet -> -i eno1 -o docker0 -> container

Посмотрим несколько примеров, чтобы вы понимали, какие конкретно правила проходят пакеты.

Пакеты из контейнеров наружу

Пакеты, которые идут из контейнеров во внешний мир, никак не задерживаются благодаря вот этому правилу:

-A FORWARD -i docker0 ! -o docker0 -j ACCEPT

То есть любые исходящие соединения разрешены без каких-либо ограничений.

Пакеты из интернета в контейнеры на опубликованные порты

Если мы опубликуем порт, то в цепочку DOCKER таблицы filter добавится новое правило. Например, я запустил контейнер nginx и опубликовал порт -p 7777:80. Добавилось новое правило:

$ sudo iptables -S DOCKER
-N DOCKER
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT

То есть пропускать на порт 80 --dport 80 все пакеты, которые идут в контейнеры -o docker0 в конкретный контейнер с айпи -d 172.17.0.2

Пакеты из интернета в контейнеры по ответным соединениям

Допустим, мы изнутри контейнера пингуем гугл. Исходящие пакеты не задерживаются, это мы уже разобрали выше:

# сначала проходит цепочку (она пустая)
-A FORWARD -j DOCKER-USER

# потом еще какую-то (она тоже ничего не делает)
-A FORWARD -j DOCKER-ISOLATION-STAGE-1

# исходящий пакет разрешен к передаче
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT

Но когда от гугла приходит ответный пакет, как он попадает в систему? Вот так:

# так же проходит две цепочки
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1

# дальше по цепочке DOCKER, но там нет матча
-A FORWARD -o docker0 -j DOCKER

# и в конце срабатывает вот это правило:
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

Поскольку ответ на пинг является RELATED-соединением, он пропускается.

Люблю IPTABLES

P. S. Сейчас подумал, что не очень эффективно дублировать все эти правила в DOCKER-USER, как сделано в предыдущей статье. Может быть будет проще дописать туда только явно запрещающие входящие правила - вроде "запрещать входящий трафик в контейнеры на любые порты, кроме 80, 443, ХХХ". Результат!