[О блоге] [наверх] [пред] [2023-06-06 12:14:56+03:00] [f8e2e4dfc6870c4dad7e62be9bb02ae9dd73d180]
Темы: [bsd][ipv6][tip]

Многие годы мой firewall был совершенно некорректно настроен

https://docs.freebsd.org/en/books/handbook/firewalls/
Чуть ли не десять лет firewall/NAT у меня был в постыднейшем состоянии.
Он работал, но жутко сильно грузил процессор. С переездом на свой btrtrc
BitTorrent клиент (c2928fb18b27df55c19dd745753b2e76bdf2172b), который
значительно больше соединений поддерживает и UDP-транспорт, проблемы с
firewall/NAT стали очень видны. С переездом на новую FreeBSD
(0cfadc4b3f5ed39dba025a4aecf5cede864f9ad1), проблема ещё сильнее
усугубилась: если активно что-то начинает качаться, то я даже не могу
сделать SSH временами, отваливается NFS и всё в таком духе. Когда я
поднимал (6e7bec429305b04a664e01e6913d54d49fc0aaff,
394eb39744924cc673fcc8815eae1508440c9d44) публичный BitTorrent трэкер,
то всё было просто неюзабельно.

Причём при переезде (0aca426f64a513068a0bfffa563ab85f238a7d9f) с IPsec
на WireGuard я оставил IPv4 трафик вне туннеля зашифрованного, так как
IPv4 это всякая legacy Web-фигня и BitTorrent трафик -- штатно я уже не
помню когда пришлось бы явно руками что-то делать с IPv4 адресами. То
есть нагрузка на сетевую подсистему и CPU стала ещё меньше, но
отзывчивость системы ужасна.

Я понимаю что это явно косяк с моей стороны: ну не может жалкий 100Mbps
канал так нагружать всё, что 4-core Xeon не справляется. На 100Mbps
спокойно VIA C7 700-800MHz процессор с FreeBSD, NAT, ipf+ipfw+dummynet
справлялся без проблем.

tracker.cypherpunks.ru много где остаётся прописанным -- выключил его,
ибо не справляется ничего. Но стало "много" (речь про несколько тысяч
пакетов в секунду всего) трафика на DNS -- "решил" traffic shaper-ом.
Позже я просто drop-ал пакеты на firewall к порту трэкера. Полная фигня.

По top/*stat утилитам ничего не понятно что и где именно тормозит всё:
они показывают что на 4-х ядерном Xeon 80+% CPU ест исключительно ядро.
Где-то нашёл вот такой DTrace скрипт:

    callout_execute:::callout-start
    { self->cstart = timestamp; }

    callout_execute:::callout-end
    { @callouts[((struct callout *)arg0)->c_func] = sum(timestamp - self->cstart); }

    tick-1sec
    {
        printa("%40a %10@d\n", @callouts);
        clear(@callouts);
        printf("\n");
    }

    BEGIN
    { printf("%40s | %s\n", "function", "nanoseconds per second"); }

который показал что в dummynet модуле (traffic shaper) больше всего
времени проводится. Удалил shaping вообще -- хуже ничего не стало,
dummynet исчез, теперь libalias самый дорогой по вызовам. А это NAT.

Ещё вот такой простой DTrace скрипт интересен был:
    profile:::profile-1001hz
    /arg0/
    { @[ stack() ] = count(); }

И так было очевидно что дело в NAT-е. Просто отключая его, вижу что
нагрузка пропадает, всё тип топ. Пора начать за эти десять лет всё же
разбираться как корректно прописать правила в firewall.

Я использую ipfw и не хочу его менять, ибо привык (хотя и не научился
настраивать :-)), нравится ясность и простота правил (хоть и не логики
работы). Против PF ничего против не имею, знаю что там много есть
отсутствующего в ipfw, но мне не нужно оно.

          ------------------------ >8 ------------------------

Короче говоря, достаточно было *внимательно* читать самый обычный родной
FreeBSD handbook на тему ipfw (ага, в кой да веки за десять лет, RTFM).
Основная проблема это то, как я заруливал трафик в NAT:

    [...]
    $add nat 1 ip4 from any to $ip4 in via igb_wan
    $add nat 1 ip4 from 192.168.20.0/24 to any out via igb_wan
    $add check-state
    [...]
    $add allow [...]
    $add deny all from any to any

эти правила шли друг за дружкой. И только после "nat"-ов шли всякие
разрешающие по всяким портам и интерфейсам. Для stateful firewall
выглядело всё так, что весь трафик инициировался самим сервером. И
короче из-за всего этого и была полная жопа. Надо делать так, чтобы
сначала отрабатывал stateful firewall правила, а потом ещё в довесок на
выходе nat для исходящего трафика, как бы:

    [...]
    $add nat 1 ip4 from any to $ip4 in via igb_wan
    $add check-state
    $add allow [...] keep-state
    [...]
    $add nat 1 ip4 from 192.168.20.0/24 to any out via igb_wan
    $add deny all from any to any

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

    [...]
    $add nat 1 ip4 from any to $ip4 in via igb_wan
    $add check-state
    $add skipto 1234 [...] keep-state
    [...]
    $add 1234 nat 1 ip4 from 192.168.20.0/24 to any out via igb_wan
    $add deny all from any to any

но и тут засада в том, что будет отрабатывать deny правило всегда.
Поэтому nat+allow добавляется после deny:

    [...]
    $add nat 1 ip4 from any to $ip4 in via igb_wan
    $add check-state
    $add skipto 1234 [...] out via igb_wan keep-state
    [...]
    $add deny all from any to any
    $add 1234 nat 1 ip4 from 192.168.20.0/24 to any out via igb_wan
    $add allow all from any to any

Меня пугал этот последний allow, но штатно до него пакет не дойдёт. В
часть с 1234-им правилом можно попасть только через отработавшее skipto
правило, которое явно разрешило прохождение трафика.

И после этого нагруженность ядра упала чуть ли не до нуля.

          ------------------------ >8 ------------------------

Не то чтобы проблема, но обратил внимание на тысячи динамических правил
в stateful firewall создаваемых. И среди них почти все это DNS трафик от
сервера в ответ кому-то ещё. Явно их быть не должно, ибо DNS трафик
извне на 53-ий порт разрешён, а сам сервер отправляет данные точно так
же с этого же 53-го. Было у меня такое правило:

    $add allow { tcp or udp } from any to "table(mein)" domain
    [...]
    $add allow ip4 from $ip4 to any out via igb_wan keep-state
    $add allow ip6 from any to any out via gif_tb keep-state

и исходящий ответный DNS трафик создавал state-ы из-за последних правил.
Если же сделать:

    $add allow { tcp or udp } from any to "table(mein)" domain
    $add allow { tcp or udp } from "table(mein)" domain to any

то проблема решается конечно же. И динамических правил уже меньше тысячи
остаётся, почти все инициированные BitTorrent-ом с другого сервера.

          ------------------------ >8 ------------------------

Насколько же всё проще с IPv6, когда нет NAT.

    [оставить комментарий]