Небольшое предисловие - у многих наверное стоят VPN сервера на
указанной выше связке, возможно без mppe/mppc патчей но все же
pptpd(poptop)+pppd потому что это единственный способ поднимать vpn
соединение с МД средствами этого же МД и не сильно сложно для
пользователя, я не исключение и у меня тоже есть такой сервер, стоял
он себе на debian woody, вышел sarge, апгрейднули до sarge, и тут
заметили такую неприятную особенность - если 1 linux клиент поднимает
2 и более соединения тоесть одна linux машина устанавливает 2
соединения с сервером то на сервере получается зомби процесс pppd а на
клиенте некоторое время висят 2 интерфейса, начали разбираться, пришли
к выводу что виноват pptpd, причем ситуация легко воспроизводится на
debian sarge системах, отправили багрепорт, начали смотреть дальше, с
МД в качестве клиента не воспроизводится, радиус не дает сделать 2
соединения что вобщем то правильно, специально так настроен, но если
клиент linux то почемуто это не срабатывает, один товарищ подсказал
что pptp соединение идентифицируется парой IP адрес и еще какоето Id
которое назначается сервером клиенту и должно быть уникальным, а linux
клиент как оказалось на это id внимания не обращает и оно у него
всегда одинаковое, это хоть как то объяснило ситуацию, получается что
сервер пытается использовать для нового соединения те же параметры что
и для первого поэтому радиус их не различает. Посмотрели версию pptpd,
увидели что в дебиане она мягко говоря старовата - 1.2.1 когда на
http://www.poptop.org/ текущая стабильная версия 1.2.3 которую
думаю и следует использовать на данный момент а экспериментальная
1.3.0 которую как оказалось тоже можно использовать после небольшой
обработки напильником.
Вобщем решили потестировать разные версии pptpd, попробовали с woody -
зомби не появляются, попробовали 1.2.3 - зомби тоже не появляются,
пришли к выводу что появляются только на 1.2.1 которая в debian sarge,
делать нечего решили обновиться, и попутно пришла в голову идея
потестировать всю эту связку на предмет скорости и попробовать
оптимизировать ее.
Потестировали, pppd 2.4.3 из дебиана с mppe-mppc ратчем с
http://mppe-mppc.alphacron.de/, и pptpd 1.2.3, машина:
cat /proc/cpuinfo
processor : 0
vendor_id : AuthenticAMD
cpu family : 6
model : 10
model name : AMD Athlon(tm) XP 2600+
stepping : 0
cpu MHz : 2080.646
cache size : 256 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 1
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat pse36 mmx fxsr sse syscall mmxext 3dnowext 3dnow
bogomips : 4154.98
соединение было с включенной MPPC компрессией без MPPE(чтото не
додумались включить, она у нас по дефолту выключена) из оставшихся
настроек pppd способных по моему мнению повлиять на производительность
стоит отметить mtu/mru (поправьте если я не прав) которые выставлены в
1490, ядро 2.4.32, тестировали путем скачивания фильма со своего же
ftp сервера, на нем стоит ограничение по скорости в 10 мегабит на одну
сессию поэтому запустили сразу несколько скачек(снимать ограничение
было неохото:)), примерно на 30-40 мегабитах загрузка процессора
оказалась на уровне 50-70% причем практически все это судя по топу
отъедала система (system) из чего сделали вывод что виновница этого
MPPC так как модули реализующие ее в ядре. Попробовали то же самое но
без компресси, получили загрузку в районе 1-2% что еще больше уверило
нас что таки это MPPC и ничего с этим поделать не представляется
возможным, но тем не менее решили попробовать оптимизировать то что
можно и заодно посмотреть на pptpd 1.3.0 потому что там заявляют
поддержку "prototype packet buffering and reordering" а у нас есть
некоторые нестабильные линки через которые пакеты иногда приходят не в
том порядке в каком были отправлены да и просто если тоннель через
интернет то это вполне нормальная ситуация, а pptpd такие пакеты
попросту отбрасывает с матюками в лог а тут появилась надежда что он
их не будет отбрасывать а корректно переставит и обработает.
Итак смотрим pptpd 1.3.0: Первое что видим директорию debian, как
оказалось автор pptpd тоже использует дебиан как и мы и в архиве
присутствует система сборки в deb пакет что порадовало:) Идем в
./debian/rules, правим параметры вызова configure скрипта на такие:
тоесть убираем впринципе не нужные нам --with-libwrap (позволяет
ограничивать доступ к pptpd посредством /etc/hosts.allow/hosts.deny,
но я привык делать такие вещи фаерволом) и --with-bcrelay (это
специальный демон который позволяет транслировать бродкасты приходящие
на определенный ethernet интерфейс на ppp интерфейс, надобности у нас
в нем нет поэтому тоже выключили), --with-pppd-ip-alloc включает режим
когда адреса назначает сам pppd что нам вобщем то и нужно, если не
включить то это будет делать pptpd отдавая адреса прописанные у него в
конфиге ppp через параметры при запуске и плюс при этом будет
ограничение в 100 конектов которое нам не нужно потому что клиентов
больше 100, --enable-facility=LOG_LOCAL3 просто для удобства, в
syslog.conf пишем:
local3.* /var/log/pptpd
и получаем все сообщения от pptpd в отдельном логе.
Далее делаем в директории с исходниками pptpd ./debian/rules binary,
это запустит процедуру сборки в deb пакет, у кого не дебиан могут
собрать традиционным путем запустив configure с теми же параметрами,
еще архиве есть система сборки в rpm но о ней я ничего не знаю и знать
не хочу поэтому те кто знают - разберутся сами:)
Собралось и поставилось без проблем, соединение установилось, пингую с
клиента сервер, и тут я в логах вижу такое:
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #1
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #2
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #3
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #4
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #5
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #6
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #7
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #8
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #9
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #10
Nov 26 02:23:00 ghhosthome pptpd[[ :]] GRE: accepting packet #11
Nov 26 02:23:10 ghhosthome pptpd[[ :]] GRE: accepting packet #12
Nov 26 02:23:11 ghhosthome pptpd[[ :]] GRE: accepting packet #13
Nov 26 02:23:12 ghhosthome pptpd[[ :]] GRE: accepting packet #14
Nov 26 02:23:13 ghhosthome pptpd[[ :]] GRE: accepting packet #15
Nov 26 02:23:14 ghhosthome pptpd[[ :]] GRE: accepting packet #16
Nov 26 02:23:15 ghhosthome pptpd[[ :]] GRE: accepting packet #17
Nov 26 02:23:16 ghhosthome pptpd[[ :]] GRE: accepting packet #18
Nov 26 02:23:17 ghhosthome pptpd[[ :]] GRE: accepting packet #19
Nov 26 02:23:18 ghhosthome pptpd[[ :]] GRE: accepting packet #20
Nov 26 02:23:19 ghhosthome pptpd[[ :]] GRE: accepting packet #21
Nov 26 02:23:20 ghhosthome pptpd[[ :]] GRE: accepting packet #22
Nov 26 02:23:21 ghhosthome pptpd[[ :]] GRE: accepting packet #23
ну и так далее, тоесть на каждый GRE пакет делается запись в лог:) Это
мне не очень понравилось ибо на боевом сервере пакетов будет много и
никаких дисков не хватит, да и думаю производительности такая фича не
прибавит а скорее наоборот, смотрим в /etc/pptpd.conf, debug выключен
а сообщения в лог всеравно идут, читаем man pptpd, находим там секцию
debugging, вдумчиво читаем, видим там в конце "Disable this line and
restart syslog after you are done debugging. See the syslog man pages
for more details.", понимаем что автор с юмором и выключить это путем
настроек не получится, но чтож, сами виноваты, написано же что
experimental, поэтому отдаем автору должное за его труд и пытаемся
делать чтото самостоятельно а не предьявлять претензии - ищем по
исходникам pptpd строку "accepting packet", находим в pptpgre.c строка
402:
пресобираем и пуреустанавливаем pptpd, пробуем занова, пинги есть,
строчек в логе нет, - уже хорошо, тестируем дальше на предмет проблем
с зомбями.
Поднимаем второе соединение с той же машины что и первое, смотрим на
сервере - зомбей нет что не может не радовать и этого нам впринципе
уже достаточно для сервера, но судя по логам запускается еще один
процесс pppd который затем убивается, на клиенте же появляется второй
интерфейс и ниодно соединение не работает, плюс ко всему в логах pptpd
видим следующее:
Nov 28 03:29:02 ghhosthome pptpd[[ :]] CTRL: Request to open call when call is already open, closing
из чего делаем вывод что pptpd всетаки видит ситуацию с двумя
конектами и каким то образом ее отрабатывает, напрашивается вопрос
каким же именно. Исходя из принципа the sources is a documentation
ищем в исходниках pptpd строку "Request to open call when call is
already open, closing", и находми ее в pptpctrl.c строка 365, смотрим
подробнее кусок кода:
syslog(LOG_WARNING, "CTRL: Request to open call when call is already open, closing");
if (gre_fd != -1) {
FD_CLR(gre_fd, &fds);
close(gre_fd);
gre_fd = -1;
}
if (pty_fd != -1) {
FD_CLR(pty_fd, &fds);
close(pty_fd);
pty_fd = -1;
}
}
/* change process title for accounting and status scripts */
my_setproctitle(gargc, gargv,
"pptpd [%s:%04X - %04X]",
inet_ntoa(inetaddrs),
ntohs(((struct pptp_out_call_rply *) (rply_packet))->call_id_peer),
ntohs(((struct pptp_out_call_rply *) (rply_packet))->call_id));
/* start the call, by launching pppd */
if (pty_fd > maxfd) maxfd = pty_fd;
/* wait for first packet from ppp before proceeding, thus
delaying outgoing call reply, and avoiding traffic
injection into the pty before echo has been turned off
by pppd */
if (PPP_WAIT) {
Судя по этому фрагменту, открывается новое соединение, запускается
pppd и затем оно благополучно убивается, что вобщем то подтверждается
логами, непонятно чем в данном случае руководствовался автор
обрабатывая такую ситуацию таким образом, может просто еще не дописал,
версия то экспериментальная, но первое что пришло в голову - зачем
запускать а потом убивать если можно просто не запускать?
if (gre_fd != -1 || pty_fd != -1) {
syslog(LOG_WARNING, "CTRL: Request to open call when call is already open, closing");
if (gre_fd != -1) {
FD_CLR(gre_fd, &fds);
close(gre_fd);
gre_fd = -1;
}
if (pty_fd != -1) {
FD_CLR(pty_fd, &fds);
close(pty_fd);
pty_fd = -1;
}
break;
}
/* change process title for accounting and status scripts */
my_setproctitle(gargc, gargv,
"pptpd [%s:%04X - %04X]",
inet_ntoa(inetaddrs),
ntohs(((struct pptp_out_call_rply *) (rply_packet))->call_id_peer),
ntohs(((struct pptp_out_call_rply *) (rply_packet))->call_id));
/* start the call, by launching pppd */
снова пересобираем и переустанавливаем pptpd и смотрим что получилось
- устанавливаем первое соединение, работает, пробуем установить второе
- не устанавливается, и вдобавок обрывается первое, более углубленное
изучение sources которые вдобавок the best documentation видим что
соединение не устанавливается но процедура убивания всеравно
отрабатывается, а постольку поскольку либо gre_fd либо pty_fd у этих
соединений получается одинаковое то закрывается первое соединение,
подумав и поэкспериментировав пришли к выводу что впринципе такое
поведение вполне приемлемо по двум причинам: Первая это то что адреса
у нас статические, соответственно 2 соединения будут иметь одинаковые
адреса и поэтому даже если их корректно установить то нормально
работать всеравно не будет. Вторая это то что при нормальной работе
двух соединений быть не должно, если происходит попытка установить
второе соединение при живом первом то тут на мой взгляд 2 варианта,
один это если из за проблем на несущей сети соединение со стороны
клиента уже отвалилось а со стороны сервера еще нет и клиент пробует
соединиться занова,и второй вариант это если клиент идиот и пытается
поднять второе соединение.
В первом варианте конечно наилучшим выходом было бы закрыть старое
соединение и установить новое, но реализовывать это неохото:) да и не
так много таких ситуаций возникает, должно быть стечение обстоятельств
- pptp клиент не обращающий внимание на id + повторное соединение с
сервером, всеравно клиент нажмет соединиться и в третий раз и тогда у
него это получится, а во втором варианте можно прибивать и
принципиально:) так что на этом оставим pptpd в покое и перейдем к
оптимизации pppd.
Итак, заранее берем исходники дебьяновского pppd - apt-get source ppp
(на самом деле мы это сделали давно потому что используем несколько
патчей которых в дебиане нету), и пока они берутся думаем чего там
может быть лишнего, и понимаем что лишними на сервере могут быть PPP
multilink и PPP filtering, уточняем в man pppd что PPP multilink нужен
для обьединения нескольких ppp соединений между двумя машинами в одно
виртуальное но с большей пропускной способностью, нам это на сервере
не нужно, попутно видим что для этого же самого нужна поддержка
TDB(это такая база в которой хранятся данные необходимые для
организации этого самого multilink), может на скорость это сильно и не
влияет но на размер самого pppd влиять должно, а учитывая то что их на
сервере тысячи то стоит выключить как multilink так и TDB в целях
экономии памяти. PPP filtering это фильтрация пакетов на ppp
интерфейсе, не путать с iptables, это фильтрация реализованная в самом
pppd, и насколько я понял фактически используется только для режима
dial-on-demand когда соединение устанавливается только при появлении
пакетов на ppp интерфейсе которые улавливаются какраз этой
фильтрацией, плюс вдобавок можно просто фильтровать пакеты, правила
фильтрации схожи с правилами задания фильтрации у tcpdump - man
tcpdump(1) и эту фильтрацию можно с тем же успехом обеспечить
средствами фаервола, а вот ресурсов такая фильтрация возможно может
отъедать прилично, dial-on-demand нам на сервере не нужно,
отфильтровать если понадобится можно и фаерволом поэтому относим
фильтрацию к тому что можно выключить.
Теперь смотрим как это все поотключать, разглядываем систему сборки
дебьяновского pppd, понимаем что при каждой сборке исходники
разархивируются из оригинального архива, далее накладывается ряд
патчей и уже на результирующем дереве запускается сборка, поэтому
смотрим в архиве оригинального ppp, читаем README и README.linux,
узнаем что сборка происходит на сонове Makefile который выбирается в
зависимости от системы скриптом configure, попутно узнаем что
реализация ppp протокола состоит из двух частей - одна часть в ядре
другая userspace, сам pppd какраз и есть userspace часть и нужен
только для установки соединения и согласования его параметров а в
процессе приема/передачи пакетов никак не задействован, этим
занимаются модули ядра что впринципе подтверждает тестирование
проведенное выше и обьясняет почему большую часть процессорного
времени откусила система (но забегая вперед скажу что не обьясняет
итоговых результатов, скорее наоборот вводит в заблуждение).
Попробовав запустить ./configure видим что он в качестве мэйкфайла
устанавливает pppd/Makefile.linux, соответсвенно смотрим в
pppd/Makefile.linux и находим какраз то что нужно - опции FILTER=y,
HAVE_MULTILINK=y, USE_TDB=y, причем над опцией HAVE_MULTILINK=y
находим следующую прозьбу разработчиков pppd:
# Linux distributions: Please leave multilink ENABLED in your builds
# of pppd!
Тоесть они просят дистрибьюторов linux не выключать поддержку
мультилинка, видимо из за жалоб пользователей у которых это вдруг
может не заработать, но мы то не пользователи, и жаловаться не
привыкли:) поэтому нужно закоментировать все эти три опции, а заодно
можно и HAS_SHADOW=y и USE_PAM=y если у вас авторизация как у нас
через радиус то они не нужны, а заодно и HAVE_INET6=y и CBCP=y если не
нужны IpV6 и каллбэк (каллбэк при pptp точно не нужен, но впринципе
тот же сервер может обслуживать и какойнить диалап при котором может
быть оно нужно, вобщем сами разберетесь), просто закоментировать можно
если ставить традиционным путем - ./configure;make;make install, а нам
нужен deb пакет поэтому у нас 2 пути - перепаковать архив с
оригинальными исходниками pppd подправив там мэйкфайл откуда их потом
достает дебьяновская система сборки, но тут есть опастность что не
наложатся какие либо патчи, или же наоборот сделать свой патч и
положить его к остальным дебьяновским, второй вариант на мой взгляд
более правильный, но чтобы сделать патч нужно иметь конечное дерево
исходников после наложения всех патчей на котором уже и происходит
сборка, чтобы его получить необходимо наложить все дебьяновские патчи.
Накладывать их руками мне было неочень охото, поэтому немного подумав
появилось предположение что по аналогии с другими пакетами если
собрать пакет то после сборки это самое дерево не удалится. Проверяем
- ./debian/rules binary, собирается и действительно - дерево соталось
в build-tree, соответсвенно копируем его куда нибудь, идем в
куданибудь/build-tree/ppp-2.4.3/pppd/Makefile.linux и коментируем все
что нам не нужно, далее делаем diff -ruN между оригинальным и
исправленным деревьями, результат обзываем как нибудь к примеру
noTDB-noNultilink-noFilter.diff и кладем в ./debian/patches в
исходниках pppd. у меня содержание получилось вот такое:
# Uncomment the next line to include support for PPP packet filtering.
# This requires that the libpcap library and headers be installed
# and that the kernel driver support PPP packet filtering.
-FILTER=y
+#FILTER=y
# Uncomment the next line to enable multilink PPP (enabled by default)
# Linux distributions: Please leave multilink ENABLED in your builds
# of pppd!
-HAVE_MULTILINK=y
+#HAVE_MULTILINK=y
# Uncomment the next line to enable the TDB database (enabled by default.)
# If you enable multilink, then TDB is automatically enabled also.
# Linux distributions: Please leave TDB ENABLED in your builds.
-USE_TDB=y
+#USE_TDB=y
HAS_SHADOW=y
#USE_PAM=y
если нехотите возиться можете просто скопировать и наложить, думаю
ляжет даже на оригинальные исходники без дебьяновских патчей. Дальше
делаем ./debian/rules clean затем ./debian/rules binary, все должно
пройти без ошибок, если произошли ошибки при наложении патчей то
скорее всего вы както неправильно сделали diff, вобщем после сборки
устанавливаем и пробуем тестировать.
Кстати чуть не забыл, поддержка PPP multilink и PPP Filtering еще есть
в ядре и там ее тоже поидее надо бы выключить, но мне на боевом
сервере перезагрузки устраивать не охото да и забегая вперед скажу что
полученных результатов пока вполне достаточно так что выключу при
следующем обновлении ядра или плановой перезагрузке:), multilink у
меня там итак выключен а вот filtering включен и все тесты
производились при включенной опции PPP filtering в ядре, возможно если
ее еще выключить и там то производительность будет еще больше.
Итак те же параметры соединения что и в предыдущем тесте, пробуем
скачать кино, смотрим top, и понимаем что чтото ничего не видно,
переспрашиваем (этот тест я уже делал не сам а из дома совместно с еще
одним человеком который собственно и запускал закачки сидя в
серверной), диалог был примерно такой: я- ты там качаеш?
он - качаю для верности даю ftpwho -vv на сервере - правда качает, на 10
мегабитах в секунду, но по top ничего не заметно загрузка процессора
1-2% и на фоне общей работы пользователей можно сказать что нагрузки
нет, следовательно стало явно быстрее, вопрос на сколько:)
Таки лезу в конфиг ftp сервера и убираю оттуда ограничение в 10
мегабит, пробуем снова, и получаем скорость практически на уровне
несущей сети - 100 мегабит в секунду с MPPC компрессией, при этом
загрузка процессора колебалась в пределах 50-70% что не может не
радовать:) учитывая что от количества клиентов нагрузка практически не
зависит, тоесть главное общий траффик который проходит по pptp запас
получается не плохой, как я уже говорил возможно если одноименные
опции выключить еще и в ядре станет еще лучше. Но тем не менее такой
результат я для себя обьяснить пока не могу, эти 50-70% CPU при 100
мегабитах в секунду заняты были системой, поидее если сам pppd в
процессинге пакетов не учавствует то его оптимизация не должна бы была
давать такой прирост, если только pptpctrl который занимается
инкапсуляцией пакетов использует чтото системное и его работа
отображается как работа системы тогда это как то обьясняет такой
результат, вообще хочу сегодня если у ребят будет время потестировать
получше и с включенным MPPE, если получится допишу результаты. Но тем
не менее на данный момент результат таков хоть и необьясним. может кто
прольет на это свет?