MPPC/MPPE в FreeBSD
2013-09-13 04:28При использовании mpd для организации pptp/l2tp-туннелей чаще всего применяют MPPC и MPPE для компрессии и шифрования трафика туннеля. При этом mpd использует ядерную netgraph-ноду ng_mppc, реализующую нужные алгоритмы. У кода в этой ноде есть одно ужасное свойство. Из-за переупорядочивания или потери сжатых туннельных пакетов у алгоритмов может произойти срыв синхронизации, что приводит к необходимости сторонам произвести re-keying: пересоздать криптографические ключи. Код ng_mppc считает эту процедуру настолько "тяжелой" в смысле загрузки CPU, что при накоплении определенного количества ошибок тупо отключает работу этого туннеля. Внешне это выглядит так: туннель установлен, системные интерфейсы выглядят "как живые", только вот полезный трафик по туннелю ходить перестаёт совсем и требуется переустановка туннеля для возобновления нормальной работы - до следующего срыва. При этом в dmesg попадает сообщение такого вида:
ng_mppc_decompress: too many (4094) packets dropped, disabling node 0xc7020900!
Такое "решение" было мотивировано "защитой от DoS" в 2000-м году, из которого растут ноги этого кода. Не стану обсуждать разумность такого решения в то время, но теперь, когда FreeBSD почти всегда работает на машинах с безумными мегагерцами и гигагерцами, хотелось бы более стабильной работы туннелей.
Попалась мне пара машин под FreeBSD с mpd, которые устанавливают между собой l2tp-туннель, который через несколько секунд после начала работы стабильно воспроизводит эту проблему. Сделал патч для ng_mppc.c, который вводит новый sysctl net.graph.mppe_max_rekey (а так же loader tunnable) со значением по умолчанию 1000 - тот самый порог, после которого непатченная нода ng_mppc прекращает работать. Если присвоить этому sysctl отрицательное значение, то код перестанет блокировать свою работу по достижении порога, а только будет писать в лог указанное выше сообщение. Вместо блокирования туннеля в этом случае mpd производит сброс протокола CCP в туннеле и работа его продолжается, а в логе mpd на одной стороне появляется строка (если, конечно, включено логирование ccp):
CCP: SendResetReq #3 link 0 (Opened)
На второй стороне туннеля:
CCP: rec'd Reset Request #3 (Opened)
Провел тест: запустил ping длинными пакетами по 10 пакетов в секунду, поймал момент превышения порога - в момент сброса CCP потерялось 4 пакета, то есть заминка в работе туннеля составила около 0.4 секунды, вместо полной блокировки.
Если у вас нода ng_mppc.ko подгружается динамически при старте mpd (а не вкомпилирована статически в ядро), то net.graph.mppe_max_rekey=-1000 нужно писать в /boot/loader.conf, а не в /etc/sysctl.conf, так как в момент обработки sysctl.conf при загрузке системы переменной net.graph.mppe_max_rekey ещё не будет существовать. loader.conf задаёт для неё значение по умолчанию, которое ng_mppc использует при своей инициализации.
Применять патч:
fetch http://www.grosbein.net/freebsd/patches/ng_mppc.c.diff cd /usr/src patch < /path/to/ng_mppc.c.diff
После этого, если ng_mppc статически собирается с ядром, нужно пересобрать ядро. А если он подгружается динамически, то делаем так:
cd sys/modules/netgraph/mppc make obj depend && make all install
После чего можно выгрузить модуль командой kldunload ng_mppc (предварительно остановив mpd, если он запущен) и загрузить его заново: kldload ng_mppc.
Update: обновил патч (URL тот же). Вместо одного sysctl net.graph.mppe_max_rekey вводится три:
net.graph.mppe.block_on_max_rekey- блокировать ноду по достижению порога ошибок (1) или нет (0), по умолчанию не блокировать (то есть, с примененным патчем никаких настроек для исправления проблемы делать не надо);net.graph.mppe.log_max_rekey- писать ли в лог сообщения о достижении порога (1) или нет (0), по умолчанию 1;net.graph.mppe.max_rekey- задаёт порог (1000 по умолчанию, как и без патча).
Update: Патч принят в основное дерево и даже бекпортирован в 10-STABLE, 9-STABLE и 8-STABLE. Будет в 9.3 и в 10.1. Спасибо Александру Мотину (mav).
no subject
Date: 2013-09-13 05:46 (UTC)