Сетевой экран в Linux встроен в ядро - это подсистема netfilter. Пользователь управляет ей через утилиты: исторически через iptables, сейчас всё чаще через nftables. Ниже - как устроены правила, как собрать рабочий набор для сервера или домашнего компьютера, чем DROP отличается от REJECT и как читать журналы отброшенных пакетов.

Инструкция подходит для Ubuntu 22.04/24.04, Debian 12/13, RHEL 9/10 и родственных: во всех них по умолчанию работает nftables.

Немного истории

В ядрах Linux 2.0 пакетами управляла утилита ipfwadm, в ядрах 2.2 - ipchains, в 2.4 появились netfilter и iptables, которые на долгие годы стали стандартом. Отдельные утилиты обслуживали IPv4 (iptables), IPv6 (ip6tables), ARP и мосты.

nftables пришёл на смену iptables в ядре 3.13. Одна утилита nft работает со всеми семействами протоколов, правила проще объединять, а изменения применяются атомарно. В современных дистрибутивах nftables используется по умолчанию, а команда iptables часто является совместимой обёрткой, которая на самом деле создаёт правила nftables. Проверить можно так:

bash
iptables -V

Если в выводе (nf_tables) - работает обёртка, если (legacy) - старый механизм. Смешивать в одной системе legacy-правила и nftables не стоит: они живут независимо, и разобраться, какое правило сработало, становится трудно.

Если вы привыкли к ipchains по старым статьям: цепочки input, output и forward в iptables сохранились, но пишутся заглавными буквами, а действие DENY заменено на DROP, MASQ - на MASQUERADE в таблице nat.

Как устроены правила

Вертикальный список правил, пакет останавливается на выделенном

Правила собраны в цепочки, цепочки - в таблицы. Для фильтрации важны три цепочки:

  • INPUT - пакеты, адресованные самому компьютеру;
  • OUTPUT - пакеты, которые компьютер отправляет;
  • FORWARD - пакеты, которые проходят через компьютер транзитом (если он работает маршрутизатором).

Пакет проверяется правилами цепочки сверху вниз. Первое подходящее правило с действием ACCEPT, DROP или REJECT решает судьбу пакета, дальше проверка не идёт. Если ни одно правило не подошло, применяется политика цепочки по умолчанию.

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

Базовый набор правил для сервера

Задача: всё входящее запретить, разрешить ответы на собственные соединения, локальный интерфейс, ping, SSH и веб-сервер.

Осторожно при работе по SSH. Если установить политику DROP раньше, чем разрешён SSH, соединение оборвётся. Сначала добавьте разрешающие правила, а политику меняйте последней. Для подстраховки можно заранее запланировать сброс правил: echo "iptables -P INPUT ACCEPT; iptables -F" | at now + 5 min - и отменить задание через atrm, когда убедитесь, что доступ есть.

iptables

bash
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -P INPUT DROP
iptables -P FORWARD DROP

Для IPv6 нужен такой же набор через ip6tables, с одним отличием: ICMPv6 обязательно разрешить целиком (ip6tables -A INPUT -p ipv6-icmp -j ACCEPT), без него IPv6 не работает. Если про IPv6 забыть, сервер останется открытым по второму протоколу.

Полезные команды:

bash
iptables -L INPUT -n -v --line-numbers # показать правила с номерами
iptables -D INPUT 5                     # удалить правило номер 5
iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT # вставить правило на 3-е место
iptables -F                             # удалить все правила (политика не меняется)
iptables-save                           # вывести все правила в виде команд

Правила iptables живут до перезагрузки. Чтобы сохранить их, в Debian и Ubuntu ставят пакет iptables-persistent, после чего:

bash
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

nftables

Тот же набор в nftables удобнее держать в файле /etc/nftables.conf. Семейство inet обрабатывает сразу IPv4 и IPv6:

text
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        iif lo accept
        ct state established,related accept
        ct state invalid drop
        meta l4proto ipv6-icmp accept
        icmp type echo-request accept
        tcp dport 22 accept
        tcp dport { 80, 443 } accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Проверить файл без применения, применить и включить загрузку при старте:

bash
nft -c -f /etc/nftables.conf
nft -f /etc/nftables.conf
systemctl enable nftables

Файл применяется целиком и атомарно: если в нём ошибка, старые правила остаются на месте.

Просмотр и правка на ходу:

bash
nft list ruleset
nft -a list chain inet filter input         # правила с номерами (handle)
nft add rule inet filter input tcp dport 8080 accept
nft delete rule inet filter input handle 12

Изменения, сделанные командой nft add, тоже живут до перезагрузки. Постоянные правила вносите в файл.

Простые оболочки

В Ubuntu есть ufw, в Fedora и RHEL - firewalld. Это надстройки, которые сами создают правила nftables или iptables. Для типового сервера их достаточно (ufw allow 22/tcp, firewall-cmd --add-service=http --permanent). Главное - не управлять одной системой одновременно через оболочку и вручную: оболочка при перезапуске перезапишет ваши правила.

Файрвол для домашнего компьютера

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

text
table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        iif lo accept
        ct state established,related accept
        meta l4proto ipv6-icmp accept
        udp sport 67 udp dport 68 accept
    }
}

Правило для портов 67 и 68 оставляет работать получение адреса по DHCP. Если вы пользуетесь общими папками, печатью по сети или игровыми серверами в локальной сети, добавьте для них разрешения, лучше только для домашней подсети: ip saddr 192.168.1.0/24 tcp dport 445 accept.

DROP или REJECT

Слева пакет падает в провал, справа от стены возвращается ответ

Оба действия не пропускают пакет, но по-разному ведут себя с отправителем:

  • DROP молча выбрасывает пакет. Отправитель не получает ответа и ждёт до истечения тайм-аута.
  • REJECT выбрасывает пакет и сообщает отправителю об отказе: по умолчанию ICMP-сообщением «порт недоступен». Для TCP правильнее отвечать сбросом соединения: -j REJECT --reject-with tcp-reset в iptables или reject with tcp reset в nftables.

Распространённое мнение, что DROP «прячет» сервер, верно лишь отчасти. Сканер видит, что порт не ответил, и помечает его как фильтруемый - это тоже информация. Зато DROP не тратит ресурсы на ответы и не превращает сервер в источник ICMP-трафика при сканировании или атаке с поддельных адресов.

Практическое правило: для входящего трафика из интернета используйте DROP в качестве политики по умолчанию. Для внутренней сети и для исходящего трафика лучше REJECT: программы сразу получают ошибку и не висят минутами в ожидании, а администратору проще понять, что соединение блокирует именно файрвол.

Счётчики пакетов

Три правила со счётчиками 1284, 0037 и 0000

Каждое правило считает пакеты и байты, которые под него попали. Это быстрый способ понять, срабатывает ли правило вообще.

В iptables:

bash
iptables -L INPUT -n -v      # счётчики в сокращённом виде (K, M, G)
iptables -L INPUT -n -v -x   # точные значения
iptables -Z                  # обнулить все счётчики
iptables -Z INPUT            # обнулить счётчики цепочки
iptables -Z INPUT 3          # обнулить счётчик одного правила

Счётчики можно обнулить одновременно с выводом: iptables -L -n -v -Z. Это удобно для замеров: обнулили, подождали минуту, посмотрели снова.

В nftables счётчик нужно явно добавить в правило словом counter:

text
tcp dport 22 counter accept

Значения видны в выводе nft list ruleset. Обнулить счётчики правил в свежих версиях nft можно командой nft reset rules (целиком или для одной цепочки: nft reset rules inet filter input), в старых - перезагрузкой набора правил из файла. Для учёта трафика удобны и именованные счётчики: их объявляют в таблице (counter ssh_in { }), ссылаются из правил (tcp dport 22 counter name ssh_in accept) и сбрасывают командой nft reset counters.

Журналирование

Чтобы видеть, что отбрасывает файрвол, перед финальным запретом добавляют правило журналирования. Пакет при этом не останавливается и идёт к следующему правилу.

В iptables:

bash
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "FW-DROP: " --log-level 4

В nftables - в конце цепочки input:

text
limit rate 5/minute burst 10 packets log prefix "FW-DROP: " level warn

Ограничение частоты обязательно: без него сканирование или флуд мгновенно заполнят журнал и диск. Префикс помогает отличать записи файрвола от прочих сообщений ядра.

Записи попадают в журнал ядра:

bash
journalctl -k | grep FW-DROP
dmesg | grep FW-DROP

В системах с rsyslog они также оказываются в /var/log/kern.log (Debian, Ubuntu) или /var/log/messages (RHEL и родственные). В Debian 12 и новее rsyslog по умолчанию не устанавливается, и записи есть только в journalctl -k. Если записей много, их можно вынести в отдельный файл правилом rsyslog по префиксу либо использовать действие NFLOG (log group 1 в nftables) вместе со службой ulogd2.

Формат записи

Типичная строка выглядит так:

text
FW-DROP: IN=eth0 OUT= MAC=52:54:00:12:34:56:52:54:00:ab:cd:ef:08:00 SRC=198.51.100.23 DST=203.0.113.10 LEN=60 TOS=0x00 PREC=0x00 TTL=52 ID=41532 DF PROTO=TCP SPT=51234 DPT=3389 WINDOW=64240 RES=0x00 SYN URGP=0

Что означают поля:

  • IN, OUT - входящий и исходящий интерфейсы. Для входящего пакета OUT пустой, для исходящего пустой IN.
  • MAC - MAC-адрес получателя (6 байт), MAC-адрес отправителя (6 байт) и тип протокола (08:00 - IPv4, 86:dd - IPv6). Отправитель здесь - последний маршрутизатор, а не исходный компьютер в интернете.
  • SRC, DST - IP-адреса источника и назначения.
  • LEN - длина IP-пакета в байтах.
  • TOS, PREC - поля типа обслуживания и приоритета. Обычно нули, для анализа редко нужны.
  • TTL - оставшееся время жизни пакета. По нему можно грубо оценить, сколько маршрутизаторов прошёл пакет и какая ОС его отправила: Linux обычно начинает с 64, Windows - со 128.
  • ID - идентификатор IP-пакета, DF - флаг «не фрагментировать».
  • PROTO - протокол: TCP, UDP, ICMP.
  • SPT, DPT - порт источника и порт назначения. DPT показывает, к какой службе пытались подключиться: в примере - 3389, удалённый рабочий стол Windows.
  • WINDOW - размер окна TCP, RES - зарезервированные биты.
  • SYN, ACK, FIN, RST, PSH - флаги TCP. Одиночный SYN - попытка открыть новое соединение.
  • URGP - указатель срочных данных, почти всегда 0.

Для ICMP вместо портов будут TYPE и CODE (например, TYPE=8 - эхо-запрос, ping), для UDP - порты без флагов TCP.

Разбор журнала

Самые активные источники отброшенных пакетов:

bash
journalctl -k | grep FW-DROP | grep -o 'SRC=[^ ]*' | sort | uniq -c | sort -rn | head -20

Самые популярные порты, к которым стучатся:

bash
journalctl -k | grep FW-DROP | grep -o 'DPT=[^ ]*' | sort | uniq -c | sort -rn | head -20

Обычная картина для сервера в интернете - постоянные попытки подключиться к SSH, удалённому рабочему столу, базам данных и веб-панелям. Само по себе это не атака лично на вас, а фоновый шум сканеров. Если же видно, что какой-то адрес настойчиво перебирает порты или пароли, узнать владельца сети и контакт для жалоб можно командой whois.