iptables — это не файрвол сам по себе. Файрвол встроен в ядро Linux и называется netfilter. Это несколько «крючков» (hooks) — точек на пути каждого сетевого пакета, где ядро останавливает пакет и спрашивает: «что с ним делать?». iptables — merely утилита командной строки, которая загружает в ядро правила: «в такой точке, для таких пакетов — делай то-то».
Два следствия, которые надо понять сразу:
netfilter-persistent save, который записывает их на диск, и netfilter-persistent при старте системы загружает обратно.Каждый пакет, приходящий на сетевую карту, проходит через машину по фиксированному маршруту. Вот он целиком:
пакет пришёл на сетевую карту
│
▼
[1] PREROUTING (таблица nat) ← DNAT работает ЗДЕСЬ: подмена адреса НАЗНАЧЕНИЯ
│
▼
РЕШЕНИЕ О МАРШРУТИЗАЦИИ
«этот пакет адресован мне или его надо передать дальше?»
│
┌────┴─────────────┐
│ мне │ мимо (на другой хост)
▼ ▼
[2] INPUT [3] FORWARD (таблица filter)
│ │
▼ ▼
локальная [4] POSTROUTING (таблица nat) ← MASQUERADE работает ЗДЕСЬ:
программа │ подмена адреса ИСТОЧНИКА
(ssh, nginx...) ▼
уходит с сетевой карты
И отдельно для пакетов, рождённых самим сервером (исходящих):
локальная программа → OUTPUT (filter) → POSTROUTING (nat) → сетевая карта
Что важно понять из этой схемы:
У netfilter несколько таблиц, нам нужны две:
| Таблица | Назначение | Цепочки, которые в ней есть |
|---|---|---|
filter | Пропустить / выбросить пакет (собственно файрвол) | INPUT, FORWARD, OUTPUT |
nat | Переписать IP-адреса в заголовке пакета | PREROUTING, OUTPUT, POSTROUTING |
(Есть ещё mangle и raw — для экзотики вроде маркировки пакетов, мы их не трогаем.)
Если в команде не указано -t <таблица>, iptables считает, что вы имеете в виду filter. Поэтому iptables -A INPUT ... — это правило в filter, а iptables -t nat -A PREROUTING ... — в nat.
Возьмём правило из скрипта джампхоста и разберём каждое слово:
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 3.3.3.3
| Фрагмент | Что означает |
|---|---|
iptables | запустить утилиту настройки netfilter |
-t nat | работать с таблицей nat (переписывание адресов) |
-A PREROUTING | Append — добавить правило в конец цепочки PREROUTING |
-p tcp | условие: протокол TCP |
--dport 443 | условие: порт назначения (destination port) = 443 — то, куда стучатся |
-j DNAT | jump: если всё выше совпало — прыгнуть в обработчик DNAT (Destination NAT) |
--to-destination 3.3.3.3 | параметр DNAT: на какой адрес заменить назначение |
DNAT переписывает в IP-заголовке пакета поле «адрес получателя». Было «кому: 2.2.2.2, порт 443» → стало «кому: 3.3.3.3, порт 443». Порт сохраняется, потому что в --to-destination мы указали только IP без порта. Сам пакет при этом физически остаётся тем же — меняется только адрес на конверте, и ядро дальше маршрутизирует его уже на 3.3.3.3.
Другие флаги, которые встречаются в скриптах:
-s 1.1.1.1 — условие по адресу источника (от кого пакет)-d 3.3.3.3 — условие по адресу назначения-i lo — условие по входному интерфейсу (lo — loopback, внутренний интерфейс 127.0.0.1)--sport — порт источника-p icmp / -p udp — другие протоколы-P INPUT DROP — Policy: действие по умолчанию для пакетов, которые дошли до конца цепочки и ни под одно правило не подошли-F — Flush, очистить цепочку от всех правил-D — удалить правило (та же команда с -D вместо -A)-I — вставить правило в начало цепочки (вместо конца)Это то, что делает Linux-файрвол «умным» (stateful). Ядро автоматически записывает в таблицу (conntrack table) каждое соединение: кто, кому, по каким портам, каким протоколом. У пакета с точки зрения этой таблицы есть состояние:
NEW — первый пакет соединения (например, TCP SYN)ESTABLISHED — пакет уже существующего, разрешённого соединенияRELATED — побочный трафик другого соединения (классика: ICMP-ошибки, FTP data-канал)INVALID — мусор, не относящийся ни к чемуОтсюда правило, которое есть в обоих скриптах:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Читается так: «пропускай без вопросов всё, что является ответом на соединение, которое уже было разрешено». Именно поэтому нам не надо писать зеркальные правила для обратного трафика: клиент подключился к SSH (NEW — проверили, оператор, пропустили), и все дальнейшие пакеты этой сессии в обе стороны летят как ESTABLISHED — моментально, без повторных проверек.
Второе следствие conntrack, критичное для NAT: правила nat применяются только к первому пакету соединения. Когда первый пакет прошёл через DNAT+MASQUERADE, ядро запомнило трансляцию, и все остальные пакеты этого соединения (и в одну, и в другую сторону) переписываются автоматически, по таблице. Правило nat — это не «конвейер для каждого пакета», а «инструкция, что делать с новым потоком».
sysctl -w net.ipv4.ip_forward=1sysctl — это переключатель параметров ядра. По умолчанию Linux в режиме «я не роутер»: любой пакет, пришедший не на мои адреса, выбрасывается ещё до цепочки FORWARD. Этот флаг включает форвардинг — разрешение ядру передавать транзитные пакеты дальше. Без него всё остальное бессмысленно.
iptables -t nat -A PREROUTING -p tcp --dport "$p" -j DNAT --to-destination "$BACKEND"
Пакет пришёл на 2.2.2.2:443. Ещё до решения о маршрутизации ядро переписывает адрес назначения на 3.3.3.3. Маршрутизация смотрит: «3.3.3.3 — не я, надо передать через такой-то интерфейс» → пакет становится транзитным и идёт в FORWARD.
iptables -t nat -A POSTROUTING -p tcp -d "$BACKEND" --dport "$p" -j MASQUERADE
Последняя точка перед выходом. MASQUERADE переписывает источник: было «от: случайный-клиент», стало «от: 2.2.2.2». Зачем — смотрите сценарий B ниже, без этого ответы бэкенда не находят дорогу обратно. (MASQUERADE — это SNAT, который сам берёт IP выходного интерфейса; классический SNAT с зашитым IP быстрее, но MASQUERADE проще и работает при динамическом IP.)
iptables -A FORWARD -p tcp -d "$BACKEND" --dport "$p" -j ACCEPT
Пропуск в фильтре для транзитного трафика. На многих системах (особенно с Docker) политика FORWARD = DROP, и без явного ACCEPT проброс молча не работал бы, хотя nat-правила верны.
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Обратный трафик (бэкенд → клиент) — тоже транзитный, тоже проходит FORWARD. По нему правило выше не матчится (у него destination = клиент, а не бэкенд), поэтому пропускаем его как ESTABLISHED.
iptables -F INPUT # очистить только цепочку INPUT в filter
iptables -A INPUT -i lo -j ACCEPT # loopback
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -s "$OPERATOR" -p tcp --dport "$JH_SSH_PORT" -j ACCEPT
iptables -P INPUT DROP
iptables -F # очистить все цепочки таблицы filter
iptables -P INPUT DROP # всё входящее по умолчанию — выбросить
iptables -P FORWARD DROP # этот сервер ничего не форвардит, он не роутер
Дальше — короткий белый список: loopback, ответы на исходящие соединения, пинги только с 2.2.2.2 и 1.1.1.1, SSH на 228 только с них же, рабочие порты только с них же. Заметьте: про цепочку OUTPUT мы ничего не пишем — её политика остаётся ACCEPT, сервер сам может инициировать любые исходящие (DNS, обновления). Входящий к нему доступ закрыт, а не он из интернета.
У файрвола два способа сказать «нет»:
Для задачи «конечный сервер должен быть невидим» выбран DROP по всей цепочке: посторонний пинг — тишина, постороннее TCP-подключение — тишина, никаких признаков жизни.
1.1.1.1 → 2.2.2.2:22 приходит на сетевую карту.lo не подходит; conntrack-правило — нет (это NEW); icmp — нет; а вот -s 1.1.1.1 --dport 22 — совпадение → ACCEPT.Посторонний на этом же месте дойдёт до конца INPUT и упадёт в политику DROP — тишина.
клиент → 2.2.2.2:443 (первый пакет — TCP SYN).клиент → 3.3.3.3:443. Conntrack запоминает: «поток клиент↔3.3.3.3:443 на входе выглядит как клиент↔2.2.2.2:443».–d 3.3.3.3 --dport 443 ACCEPT — совпадение, пропускаем.2.2.2.2 → 3.3.3.3:443.3.3.3.3 → 2.2.2.2. Этот пакет приходит на джампхост, conntrack узнаёт его как часть уже учтённого потока, автоматически выполняет обратную трансляцию (источник становится 2.2.2.2:443... точнее, назначение — обратно клиенту), пакет идёт в FORWARD, матчится как ESTABLISHED → уезжает клиенту.Что было бы без MASQUERADE (шаг 5): на бэкенд пришёл бы пакет с настоящим источником (клиент), бэкенд ответил бы клиенту напрямую, мимо джампхоста. Клиент ждал ответ от 2.2.2.2, а пришло что-то от неизвестного 3.3.3.3 — ядро клиента такой пакет отбрасывает, handshake не завершается. Это классическая ошибка «DNAT есть, SNAT забыли» — соединение виснет на SYN.
ICMP echo-request от постороннего IP приходит на бэкенд → INPUT → правила по -s 2.2.2.2 и -s 1.1.1.1 не матчатся (источник чужой) → конец цепочки → политика DROP → пакет выброшен молча. Никакого echo-reply, сканер видит таймаут.
То же самое, только TCP: SYN не матчится ни с одним правилом → DROP → нет ни RST, ни ответа → порт выглядит «filtered», сказать, жив ли хост, невозможно.
-s 1.1.1.1 -p tcp --dport 228 — явное разрешение. Оператор — исключение из невидимости.
Пакет идёт по цепочке сверху вниз и останавливается на первом совпавшем правиле. Если бы в бэкенд-скрипте первым стояло «принимать всё с 2.2.2.2», а ниже «запретить 2.2.2.2 куда-то» — запрет был бы мёртвым: пакеты до него не доходили бы. -A добавляет в конец (порядок = порядок написания), -I — в начало, если когда-нибудь понадобится правило-исключение поверх общего.
# посмотреть filter-таблицу со счётчиками (сколько пакетов/байт легло на каждое правило)
iptables -L -v -n --line-numbers
# посмотреть nat-таблицу (там наши DNAT и MASQUERADE)
iptables -t nat -L -v -n
# увидеть живую таблицу conntrack (все активные трансляции)
conntrack -L # пакет conntrack: apt install conntrack
# удалить одно правило — повторить его с -D вместо -A
iptables -t nat -D PREROUTING -p tcp --dport 443 -j DNAT --to-destination 3.3.3.3
# или по номеру строки
iptables -L INPUT --line-numbers
iptables -D INPUT 3
# безопасное применение с автооткатом, если вы не подтвердили за 60 секунд
iptables-apply -t 60 script.sh
Счётчики (-v) — лучший инструмент отладки: если под правилом проброса ноль пакетов, а соединение не работает — трафик до правила не доходит (смотрите интерфейс, адреса, доходит ли вообще до хоста).
Последнее: оба скрипта меняют политики на DROP — применяйте их либо с физической консолью, либо через iptables-apply, либо с фоновым dead-man switch (sh -c 'sleep 120; iptables -P INPUT ACCEPT; iptables -F' &), особенно на 3.3.3.3, где SSH ходит по нестандартному порту 228.
comments (0)