Почему IPv6-файрволл чаще ломают, чем защищают
В IPv4 многие привыкли к правилу: «ICMP можно прикрыть — и ничего страшного». В IPv6 это почти всегда приводит к странным, плавающим проблемам. Потому что ICMPv6 — не «только пинг», а часть базовой механики протокола.
Через ICMPv6 работают Neighbor Discovery (замена ARP), Router Discovery (RA/RS), Path MTU Discovery (PMTU) и часть сообщений об ошибках маршрутизации. Если их режете «по привычке» — сервер может быть доступен по SSH, но веб периодически подвисает, TLS-загрузки «залипают», а IPv6 то работает, то нет.
Главная мысль: в IPv6 безопасность достигается не запретом ICMPv6, а минимально достаточным набором разрешённых ICMPv6-типов плюс строгая политика для TCP/UDP и хорошая stateful-логика.
Ключевые принципы: что мы строим в nftables
Соберём шаблон, который подходит большинству серверов и VDS: общий фильтр в table inet (одна логика для IPv4/IPv6), вход по умолчанию закрыт, но «служебный» ICMPv6 разрешён корректно.
- Используем
table inet, чтобы не держать два разных набора правил для IPv4 и IPv6. - ICMP/ICMPv6 выносим в отдельную таблицу/цепочку с более высоким приоритетом, чтобы случайно не сломать сеть при рефакторинге.
- Для ND/RA (локальные протоколы) при желании добавляем ограничения по интерфейсу и link-local источникам.
В IPv6 «правильный минимум» ICMPv6 — это часть стабильности сети. Лучше разрешить несколько обязательных типов и держать строгий
inputдля TCP/UDP, чем отстрелить ICMPv6 целиком и потом ловить зависания.
Если вы поднимаете сервер на VDS, чаще всего у вас один внешний интерфейс и предсказуемый набор сервисов — поэтому такой шаблон проще поддерживать и отлаживать.
Дополнительно по типовым схемам адресации и автоконфигурации IPv6 (SLAAC/DHCPv6/маршруты) удобно свериться с отдельным разбором: SLAAC, DHCPv6 и маршрутизация IPv6 на сервере.

Какие ICMPv6 нужны почти всегда
Ниже — практический набор для большинства серверов: даже при статическом IPv6 Neighbor Discovery и PMTU всё равно обязательны. SLAAC может не использоваться, но ND и сообщения об ошибках нужны всегда.
Neighbor Discovery: не ломаем локальную связность
Neighbor Discovery — «IPv6-замена ARP». Если заблокировать эти типы, хост может перестать видеть шлюз или соседей в одном L2-сегменте.
nd-neighbor-solicit(NS)nd-neighbor-advert(NA)
В норме такие пакеты приходят с link-local адресов (fe80::/10) и имеют hop limit 255. Это можно использовать для ужесточения.
Router Discovery: когда нужны RA/RS
Если провайдер раздаёт параметры через Router Advertisement или вы используете SLAAC, на вход нужны:
nd-router-solicit(RS)nd-router-advert(RA)
На части VDS IPv6 полностью статический, и RA/RS действительно «не требуются». Но в реальных сетях RA иногда несёт важные параметры, поэтому безопаснее разрешить RA/RS ограниченно (по интерфейсу и от link-local источников), чем запретить и получить неочевидные отказы.
PMTU: чтобы соединения не «залипали» на больших пакетах
Обязательный тип:
packet-too-big
Если блокируется packet-too-big, TCP может установить соединение, но зависнуть на передаче данных. Чаще всего это проявляется на TLS, больших ответах, VPN/туннелях, «странных» аплинках и при несовпадении MTU по пути.
Ошибки и диагностика: минимум, который реально нужен
Обычно стоит разрешать ещё:
destination-unreachabletime-exceededparameter-problemecho-request/echo-reply— по желанию (под мониторинг, часто с rate-limit)
Это не «открывает порты», но помогает корректной работе стека и сильно упрощает диагностику. Пинг удобно ограничить лимитом или разрешить только с адресов мониторинга.
Шаблон nftables: inet table и отдельная обработка ICMPv6
Дальше — рабочий шаблон, где основной фильтр живёт в table inet filter, а ICMP/ICMPv6 вынесены в отдельную таблицу с более высоким приоритетом. Это снижает риск «случайно убить IPv6», когда вы редактируете правила для сервисов.
Важно: пример намеренно простой. Открывайте только нужные сервисы (SSH/HTTP/HTTPS и т.д.), а остальное оставляйте закрытым.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state invalid drop
ct state established,related accept
iifname "lo" accept
ip6 saddr ::/128 drop
ip6 saddr ::1/128 drop
tcp dport 22 accept
tcp dport { 80, 443 } accept
limit rate 10/second burst 20 packets log prefix "nft-in-drop " flags all counter
drop
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
drop
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table inet icmp {
chain input {
type filter hook input priority -5; policy accept;
ip protocol icmp icmp type { echo-reply, echo-request, destination-unreachable, time-exceeded, parameter-problem } accept
meta l4proto ipv6-icmp icmpv6 type {
destination-unreachable,
packet-too-big,
time-exceeded,
parameter-problem,
echo-request,
echo-reply,
nd-router-solicit,
nd-router-advert,
nd-neighbor-solicit,
nd-neighbor-advert
} accept
}
}
Почему отдельная таблица и более высокий priority
Цепочка ICMP висит на hook input с priority -5, то есть отрабатывает раньше, чем строгий policy drop в основной таблице. Практически это означает: вы можете спокойно усложнять inet filter (логирование, белые списки, порт-нодки и т.д.), не боясь, что ND/PMTU «случайно» окажутся ниже какого-то раннего drop.
Ужесточаем ND/RA: link-local, интерфейс и hop limit
Если хотите снизить поверхность атак на L2-сегменте (например, опасаетесь «левых» RA), ужесточайте не всё ICMPv6, а конкретно ND/RA:
- разрешайте ND/RA только на нужном интерфейсе (например,
eth0); - разрешайте ND/RA только от
fe80::/10; - дополнительно (по ситуации) можно проверять hop limit 255 через
ip6 hoplimit.
table inet icmp {
chain input {
type filter hook input priority -5; policy accept;
meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
iifname "eth0" ip6 saddr fe80::/10 meta l4proto ipv6-icmp icmpv6 type {
nd-router-solicit,
nd-router-advert,
nd-neighbor-solicit,
nd-neighbor-advert
} accept
meta l4proto ipv6-icmp icmpv6 type echo-request limit rate 5/second burst 10 packets accept
meta l4proto ipv6-icmp icmpv6 type echo-reply accept
}
}
Нюанс про RA Guard
Классический RA Guard — это скорее функция коммутатора/виртуальной сети. На хосте наиболее практичный минимум — ограничить RA по интерфейсу и link-local источнику. Это уже отсекает «посторонние» RA из внешних сегментов и снижает риск в пределах одного L2.
Проверка: как убедиться, что ND и PMTU живы
После применения правил проверяйте не только «открылся ли SSH», а базовую связность IPv6. Иначе ошибки всплывут позже под нагрузкой или в неожиданных сценариях.
Соседи, маршруты, шлюз
ip -6 route
ip -6 neigh show
Если соседи постоянно в INCOMPLETE или FAILED, чаще всего проблема в том, что режутся NS/NA, перепутан интерфейс в правилах или вы где-то дропаете link-local целиком.
Диагностика PMTU (Packet Too Big)
Симптомы блокировки packet-too-big — случайные зависания по IPv6 (особенно HTTPS). Для диагностики посмотрите правила и трафик ICMPv6:
nft list ruleset
nft -a list ruleset
tcpdump -n -i eth0 icmp6
Если по пути реально есть MTU-проблема, вы увидите ICMPv6 Packet too big. Ключевое: такие пакеты должны доходить до хоста и матчиться вашим разрешающим правилом, а не уходить в счётчики drop.
Когда проблема «не в ICMPv6»
Иногда IPv6 «ломается» из-за слушающих сервисов, bind на ::, настроек веб-сервера и фильтрации в нескольких местах сразу. Полезно держать под рукой разбор типовых ошибок публикации IPv6 в веб-стеке: AAAA-запись, Nginx/Apache и firewall: где чаще всего рвётся IPv6.

Типовые ошибки в IPv6 firewall и как их избежать
Ошибка 1: «Разрешил established/related — значит ICMPv6 не нужен»
PMTU и часть служебных сообщений не всегда укладываются в ожидания «всё важное придёт как established». В IPv6 не полагайтесь на stateful-правила как на замену корректному списку ICMPv6 типов.
Ошибка 2: Разрозненные table ip и table ip6
Так часто получается, что IPv4 правила обновляют регулярно, а IPv6 забывают. table inet дисциплинирует: общие правила пишутся один раз, а специфичные — точечно.
Ошибка 3: Политика drop в input, а ICMPv6 «где-то внизу»
Если у вас ранний drop или reject, то поздние правила ICMPv6 могут стать недостижимыми. Отдельная цепочка/таблица с более высоким priority — простой способ защититься от этой ошибки.
Ошибка 4: «Дропнуть весь fe80::/10»
Такой запрет почти гарантированно ломает ND/RA. Если хотите быть строже — ограничивайте по типам ICMPv6 и интерфейсу, а не рубите весь link-local.
Мини-шаблон только для IPv6 (если inet не нужен)
Если вы принципиально разделяете IPv4 и IPv6, вот компактный IPv6-вариант. Для большинства серверов всё же удобнее единый inet, но этот пример полезен как «срез» логики ICMPv6.
#!/usr/sbin/nft -f
flush ruleset
table ip6 filter {
chain input {
type filter hook input priority 0; policy drop;
ct state invalid drop
ct state established,related accept
iifname "lo" accept
meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
iifname "eth0" ip6 saddr fe80::/10 meta l4proto ipv6-icmp icmpv6 type { nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
tcp dport 22 accept
tcp dport { 80, 443 } accept
drop
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Чек-лист: безопасный IPv6 с nftables без сюрпризов
Разрешите минимум ICMPv6 для корректной работы:
packet-too-big,destination-unreachable,time-exceeded,parameter-problem.Разрешите Neighbor Discovery:
nd-neighbor-solicitиnd-neighbor-advert.Если используете SLAAC/RA или провайдер присылает параметры через RA — добавьте
nd-router-solicit/nd-router-advert(лучше с ограничениемfe80::/10и интерфейса).Разместите правила ICMPv6 выше общего
dropили вынесите в отдельную цепочку/таблицу с более высоким priority.После изменений проверяйте
ip -6 route,ip -6 neigh showи счётчикиnft -a list ruleset.
Лучший результат в проде даёт подход «малый, но правильный набор» вместо бесконечных исключений после очередного «IPv6 иногда тупит». В IPv6 корректный ICMPv6 — это не лишняя дыра, а обязательная часть нормальной сетевой работы.


