Всеволод Стахов. Заметки.

25Май/100

Bitbucket.org

В связи с многочисленными проблемами работы с sourceforge, связанных, например, с невозможностью управления trac'ом, я решил, что лучше будет перенести публичный репозиторий на http://bitbucket.org. Это платформа для публикации кода, где все элементы помещаются в mercurial репозитории (например, wiki). Это делает очень удобным резервное копирование информации. Так что http://rspamd.sourceforge.net сейчас редиректит на bitbucket. Также я практически дописал основную документацию к rspamd, и теперь она доступна в том числе в wiki butbucket'а. Для конвертации из texinfo, который я использую для написания документации, в wiki формат (creole - http://www.wikicreole.org/) я написал небольшой скрипт на перле, который конвертирует основные элементы документации (лежит тут: http://cebka.pp.ru/stuff/info2wiki.pl). Разумеется, он конвертирует неполный набор тегов docbook и зачастую работает не совсем верно, но, насколько я знаю, это единственный способ конвертации, т.к. варианты texinfo->docbook->wiki также не реализованы нормально. Кроме этого, я, наконец, затегал 0.3.0. В планах к 0.3.1 написание smtp прокси для обработки спама на ранних стадиях, а также допиливание документации к lua API rspamd.

Связано с категорией: Work Нет комментариев
27Апр/100

Rspamd и xml

Замучавшись бороться с lex+yacc решил перевести конфигурацию rspamd в xml формат. Минусы старой системы довольно прозаичны: lex при переключении внутренних состояний парсера (lex states) не умеет при yyrestart'е переключаться в INITIAL state, что приводит к невозможности перечитывания конфига "на лету". Кроме этого, сами по себе lex+yacc предоставляют слишком много возможностей для генерации грамматик, что само по себе неплохо, но я ловлю себя на мысле, что bind like конфиг-файл зачастую не очень очевиден для пользователя, яркий пример, когда переменные rspamd на самом деле являются не переменными в полном понимании этого слова, а подстановками текста. Также такой конфиг крайне сложно парсить чем-то, отличным от оригинальной lex/yacc грамматики. Моя же идея была в расширении интерфейса управления кластера rspamd, давая возможность конфигурации машин в кластере более-менее атоматически. Выбор лежал между ini-like форматом и xml (yaml и json тоже рассматривались, но никаких существенных преимуществ, кроме уменьшения размера конфига, я не нашел), но у ini нет понятия уровней вложенности, а это мне было нужно для описания файлов статистики внутри classifier'а. Конечным решением системы, которая бы предоставляла компромисс между удобством ручного написания сложных правил и возможностью настройки параметров автоматически (через web интерфейс или же shell script), я выбрал lua + xml. То есть, логика правил описывается в lua, используя все возможности этого языка, включая, например, переменные, являющиеся функциями, а включаются эти правила, а также назначаются веса, описываются рабочие процессы в xml. Такое решение, на мой взгляд, позволяет отделить код правил от собственно процесса настройки системы. Поддержку старого формата я оставил, и теперь rspamd умеет конвертировать старый формат в xml (конвертировать правила в lua он, к сожалению, не умеет, но умеет представлять их в виде xml). Сразу же видимый профит - возможность "мягкого" рестарта с перечитыванием конфига. В будущем планируется введение динамических правил, которые можно было бы загружать в кластер через контроллер, не выполняя рестарта. Также анализ правил, заимствованных из spamassassin'а показал, что все это лучше делать через отдельные статистические файлы, которые после обучения поставлять вместе с rspamd. Ну и напоследок, если у кого-то вдруг появилось желание заменить SA или другую систему спам фильтрации на rspamd, но в rspamd не хватает какой-то функциональности, то я был бы рад выслушать подобные замечания, равно как и другие идеи по развитию проекта.

Связано с категорией: Work Нет комментариев
4Фев/100

Небольшой обзор возможностей rspamd

Так как до сих пор у меня не появилось идей, как рассказать легко и понятно о том, зачем и как использовать rspamd, я написал краткий обзор rspamd: фичи, установка, настройка и обучение. Надеюсь, он будет полезен тем, кто хочет использовать rspamd или тем, кто даже не знает о его существовании. Обзор тут:  http://cebka.pp.ru/why-rspamd.html.

Связано с категорией: Work Нет комментариев
31Авг/090

Windows resolver

Знаете, что сделает resolver винды, если его попросить отрезолвить url c base10 закодированным ip http://9715522259? Правильно, резолвер винды сможет даже 9 миллиардов превратить в ip адрес, тупо взяв младшие 32 бита от результата преобразования. Поэтому для парсинга url'ей, рассылаемых спамерами, которые содержат base10 encoded ip, надо преобразовывать его в uintmax_t, и брать младшие 32 бита.

Связано с категорией: Work Нет комментариев
13Апр/0917

Rspamd

Бета версия rspamd доступна для тестирования. Для сборки требуется cmake и gmime2.2. Сейчас rspamd работает примерно на порядок быстрее, чем spamassassin, но для окончательного релиза необходимо еще много тестирования. Буду признателен за любую информацию об использовании rspamd, а также о багах, в нем найденных. Rspamd доступен тут: http://cebka.pp.ru/trac

Связано с категорией: Work 17 Комментарии
20Фев/090

CMake + libperl

Начну с того, что встроенный модуль CMake никуда не годится:

SET(PERL_POSSIBLE_INCLUDE_PATHS
/usr/lib/perl/5.8.3/CORE
/usr/lib/perl/5.8.2/CORE
/usr/lib/perl/5.8.1/CORE
/usr/lib/perl/5.8.0/CORE
/usr/lib/perl/5.8/CORE
)

Естественно, пользоваться им нельзя. Кроме того, у перла до 5.8.9 есть очень гадкая особенность - DynaLoader.a, который есть в ldflags'ах. Если собирается приложение с -fPIC, то порядок линковки объектов важен, и DynaLoader имеет свойство ломать сборку. Я решил эту проблему копированием DynaLoader.a за угол с добавлением префикса lib. Выглядит это так:

# Find perl libraries and cflags
EXECUTE_PROCESS(COMMAND ${PERL_EXECUTABLE} -MExtUtils::Embed -e ccopts OUTPUT_VARIABLE PERL_CFLAGS)
EXECUTE_PROCESS(COMMAND ${PERL_EXECUTABLE} -MExtUtils::Embed -e ldopts OUTPUT_VARIABLE PERL_LDFLAGS)
STRING(REGEX REPLACE "[\r\n]" " " PERL_CFLAGS ${PERL_CFLAGS})
STRING(REGEX REPLACE " +$" "" PERL_CFLAGS ${PERL_CFLAGS})
STRING(REGEX REPLACE "[\r\n]" " " PERL_LDFLAGS ${PERL_LDFLAGS})
STRING(REGEX REPLACE " +$" "" PERL_LDFLAGS ${PERL_LDFLAGS})
# Handle DynaLoader
STRING(REGEX MATCH "/[^ ]*/DynaLoader.a" PERL_DYNALOADER ${PERL_LDFLAGS})
STRING(REGEX REPLACE "/[^ ]*/DynaLoader.a " "" PERL_LDFLAGS ${PERL_LDFLAGS})

IF(PERL_DYNALOADER)
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy ${PERL_DYNALOADER} ${project_BINARY_DIR}/compat/libdynaloader.a)
LINK_DIRECTORIES(${rspamd_BINARY_DIR}/compat/)
ENDIF(PERL_DYNALOADER)

Этот метод работает как при наличии DynaLoader.a, так и при его отсутствии. Достаточно в настройках таргета добавить следующее:

IF(PERL_DYNALOADER)
TARGET_LINK_LIBRARIES(target dynaloader)
ENDIF(PERL_DYNALOADER)
Связано с категорией: Work Нет комментариев
18Фев/091

Эффективная почтовая система на базе nginx+postfix+rmilter

Довольно часто возникает необходимость сделать высокоэффективную почтовую систему с возможностью масштабированияв плане увеличения mx'ов. Для этого можно воспользоваться такой связкой: на входе стоит nginx с policy, которая выполняет асинхронный резолвинг адресов и фильтрацию по rbl+regexp. На многих потоках почты, это снижает процент почты, доходящей до MTA, примерно в 2-3 раза. При этом policy выполняет балансировку между различными backend'ами на основании весов. MTA использует rmiter для выполнения различных проверок: clamav, spamd, ratelimits, greylisting, regexps. При этом, все данные greylisting'а и лимитов сохраняются в memcached, что обеспечивает очень быструю работу и возможность скалирования системы.
Итак, вначале скачиваем nginx версии 0.6.11: http://sysoev.ru/nginx/nginx-0.6.11.tar.gz Более свежие версии, к сожалению, работать не будут. Далее скачиваем libevent и применяем мой патч для поддержки TXT записей в резолвере:
libevent_txt.patch
Почему этой возможности до сих пор нет в libevent, мне не очень понятно.
После установки патченной версии libevent можно устанавливать nginx и policy.
Вначале патчим исходники nginx патчем для поддержки policy (также фиксится работа pipelining'а и включается режим работы без аутентификации). Патч лежит тут:
http://cebka.pp.ru/hg/hgwebdir.cgi/nginx-smtp-policy/file/ab33c3b8f1f1/patch-src_mail

После этого собираем nginx с поддержкой модуля mail.
Далее можно собрать policy. Скачиваем policy отсюда: http://cebka.pp.ru/hg/hgwebdir.cgi/nginx-smtp-policy/archive/tip.tar.gz
Собирается просто командой make+make install. При этом ставятся файлы nginx-smtp-policy и nginx-policy-watchdog с соответствующими стартовыми скриптами в /usr/local/etc/rc.d
Настройка policy предельно проста: в каждой строчке содержится переменная и ее значение. Строчки с # в начале считаются комментариями. Пример используемого мной конфига:

# nameserver - definition of nameserver (maybe multiply)
# special value resolv.conf is used to parse /etc/resolv.conf file
nameserver 195.19.37.129

# logfile - full path to logfile
# Default: /var/log/policy.log

logfile /var/log/policy/policy.log

# pidfile - full path to pidfile
# Default: /var/run/policy.pid

pidfile /var/log/policy/policy.pid

# backend_host - address of host that are backends for nginx (postfix address)
# Example host1:weight1,host2:weight2
# Default: 127.0.0.1

backends 127.0.0.1:1

# backend_port - port of backend
# Default: 25

backend_port 25

# listen - address of socket to listen on, maybe tcp or unix:
# tcp: listen tcp:host:port
# unix: listen unix:/path/to/sock
# Default: tcp:localhost:7070

listen tcp:localhost:7070

# loglevel - numeric value of logging verbocity
# 0 - only errors are logged
# 1 - warnings and errors are logged
# 2 - warnings, notices and errors are logged
# 3 - everything including debug messages is logged
# Default: 0

loglevel 2

# helo_regexp_file - specify path to helo regexp file and error code and error message string
# separated by ':' (maybe multiply)
# Example: helo_regexp_file /usr/local/etc/nginx-policy/helo.re:554 5.7.1:Bad address
# Default:

helo_regexp_file /usr/local/etc/postfix/maps/policy_helo_regexp:554 5.7.1:Helo rejected

# hostname_regexp_file - specify path to host regexp file and error code and error message string
# separated by ':' (maybe multiply)
# Example: hostname_regexp_file /usr/local/etc/nginx-policy/host.re:554 5.7.1:Bad address
# Default:
hostname_regexp_file /usr/local/etc/postfix/maps/policy_reverse_regexp:554 5.7.1:We do not accept mail form this dynamic pool

check_rbl xbl.spamhaus.org
check_rbl insecure-bl.rambler.ru

Файлы, которые я использую для этих регэкспов, можно найти в http://cebka.pp.ru/stuff/
Postfix ставится стандартным образом. Отдельно могу указать следующие вещи:
если мы на одном хосте пускаем policy и postfix, то в main.cf указываем
inet_interfaces = localhost
также для корректной работы rmilter нужно указать следующие опции:
milter_protocol = 4
smtpd_milters = unix:/var/run/rmilter/rmilter.sock
milter_default_action = accept
milter_mail_macros =  i {auth_type} {auth_authen} {auth_author} {mail_addr} {client_addr} {client_name}

для работы XCLIENT указываем, с каких хостов он разрешен (это хост, где работает policy)
smtpd_authorized_xclient_hosts = localhost

Далее настроим nginx:
mail {
      server_name  host.ru;
      auth_http    localhost:7070/nginxauth.cgi;

      smtp_auth         none;
      #smtp_capabilities "SIZE 28311552" PIPELINING 8BITMIME;
      smtp_capabilities "SIZE 28311552" 8BITMIME;
      xclient on;
      smtp_helo_required on;
      smtp_banner "mxi.icn.bmstu.ru ESMTP nginx\n\nSystem Info: This is a mail server at host.ru\n\n\n\nEmail contact: <abuse@host.ru>\n\n";

      proxy_pass_error_message  on;

      server {
          #listen             3525;
          listen             ip1:25;
          listen             ip2:25;
          protocol           smtp;
          timeout          &nbsp
; 300s;
      }
}

Rmilter для данной системы можно поставить из портов FreeBSD - mail/rmilter
Настройка rmilter детально описано в конфиге и странице rmilter (8). Memcached для работы мильтра можно использовать любой. Для общения с memcached лучше использовать tcp. Работа в режиме udp пока нестабильна из-за кучи ошибок в реализации udp в мемкешеде.
Работа этой связки проверялась в FreeBSD 6, FreeBSD 7 и FreeBSD 8-CURRENT. Если кому-то удастся завести эту связку на других системах, шлите патчи :) Также в http://cebka.pp.ru/stuff лежат примеры конфигов и используемые нами "белые" списки.

Связано с категорией: Work 1 комментарий
12Авг/082

Репозиторий некоторых софтин, которые я написал в «Рамблере»

Во время работы в "Рамблере" я написал несколько вещей, которые могут быть полезны не только у нас, но и пригодиться другим. Поэтому я решил выложить их в виде mercurial репозитария. Среди них:
- http://cebka.pp.ru/hg/rmilter/ - фильтр почты для sendmail/postfix, позволяющий делать различные проверки, в частности, spamassassin/clamav/dcc/spf, также имеются встроенные средства для грейлистинга и проверки лимитов (через memcached), также поддерживаются проверки по регэкспам. Мильтр конфигурируется при помощи конфигурационного файла, снабженного комментариями и описанного в странице man (rmilter.conf.5)
- http://cebka.pp.ru/hg/nginx-smtp-policy/ - асинхронный резолвер имен (dns resolver) для работы с nginx с патчем, изначально написанным Максом Дуниным (http://mdounin.ru/). Также nginx-smtp-policy осуществляет load balancing по различным mx'ам (согласно весам) и отсеивание нежелательной почты путем проверки RBL и мониторинг mx'ов, на которые осуществляется транспорт почты (nginx-smtp-watchdog). Данная программа предназначена для почтовых систем, обслуживающих очень большой поток почты и существенно снижает нагрузку на конечные mx'ы (патч, который есть в репозитории, работает с nginx до версии 0.6.11 включительно).
- http://cebka.pp.ru/hg/libevent/ - патчи, необходимые для работы nginx-smtp-policy. Существенно ускоряют резолвинг за счет использования вместо линейного списка DNS запросов хеш таблицу, а также патч, позволяющий резолвить TXT записи (для проверки RBL).

В настоящее время я работаю над созданием быстрой альтернативы spamassassin'у, если данный проект получится удачным, то он тоже будет открыт, скорее всего.

Связано с категорией: Work 2 Комментарии
5Июн/082

Использование dmalloc

Так как использовать dmalloc не так просто, то записать основные принципы на будущее не бесполезно. Итак, dmalloc линкуется статически, например, так:
.ifdef DMALLOC
CFLAGS+=    -DDMALLOC -DDMALLOC_FUNC_CHECK
LIBS+=    -ldmalloc
.endif

Далее, самый простой способ использовать библиотеку:
определяем функции malloc и free, чтобы иметь представление, из какой строчки кода они вызываются:
#ifdef DMALLOC
#include <dmalloc.h>
#define mymalloc(x) dmalloc_malloc(__FILE__, __LINE__, (x), DMALLOC_FUNC_MALLOC, 0, 1)
#define myrealloc(x, y) dmalloc_realloc(__FILE__, __LINE__, (x), (y), DMALLOC_FUNC_REALLOC, 0, 1)
#define myfree(x) dmalloc_free(__FILE__, __LINE__, (x), DMALLOC_FUNC_FREE)
#define mystrdup(x) dmalloc_strndup(__FILE__, __LINE__, (x), strlen((x)), 1)
#else
#define mymalloc(x) malloc((x))
#define myrealloc(x, y) realloc((x), (y))
#define myfree(x) free((x))
#define mystrdup(x) strdup((x))
#endif

Далее в коде пользуемся только этими функциями для работы с памятью.
Для инициализации библиотеки можно пользоваться переменными окружения. Но в ряде случаев это не работает (cgi, su и прочее). Поэтому я решил делать инициализацию так:

#ifdef DMALLOC
    char dmalloc_log[PATH_MAX];
    snprintf (dmalloc_log, sizeof (dmalloc_log), "debug=0x4f47d03,log=%s/dmalloc-%%p.log", DMALLOC_LOG_PATH);
    dmalloc_debug_setup (dmalloc_log);
#endif

Лог файлы создаются автоматически, DMALLOC_LOG_PATH - это каталог, куда должна быть разрешена запись пользователю, из-под которого запускается интересующая нас программа.

Логирование неосвобожденной памяти тоже лучше выполнять самому, чтобы потом не бояться потерять информацию при падении программы где-то. На мой взгляд, лучше всего использовать марки:
в начале секции кода, где может утекать память ставим:
#ifdef DMALLOC
        unsigned int mark;
        mark = dmalloc_mark();
#endif
в конце секции:
#ifdef DMALLOC
          dmalloc_log_changed(mark,
                            1 /* log unfreed pointers */,
                            0 /* do not log freed pointers */,
                            1 /* log each pnt otherwise summary */);
#endif

В логе имеем что-то вида
1212677815: 289299: Dumping Not-Freed Pointers Changed Since Mark 289178:
1212677815: 289299:  memory table is empty
Или же указатели на память:

1212677788: 288383:  not freed: '0x80144af88|s1' (24 bytes) from 'unknown'
1212677788: 288383:  not freed: '0x80144afc8|s5' (40 bytes) from 'unknown'
1212677788: 288383:  not freed: '0x80144be88|s1' (24 bytes) from 'unknown'
1212677788: 288383:  not freed: '0x80144bec8|s1' (40 bytes) from 'unknown'

Можно уронить процесс в корку (kill -SEGV, например) и посмотреть эти адреса в gdb:
(gdb) x/40c 0x80144bec8

Связано с категорией: Work 2 Комментарии
15Фев/080

Race в коде закрытия unix сокета в FreeBSD 6.x

Выглядит race следующим образом (6.3-SMP):

 48976 nginx    1202926950.760500 CALL  recvfrom(0xad,0x1b2ab38,0x400,0,0,0)

 48976 nginx    1202926950.760511 GIO   fd 173 read 122 bytes

       "HTTP/1.0 200 OK\r

        Client-Host: [UNAVAILABLE]\r

        Auth-Status: OK\r

        Auth-Server: 10.8.2.19\r

        Auth-Port: 25\r

        Connection: close\r

        \r

       "

 48976 nginx    1202926950.760521 RET   recvfrom 122/0x7a

 48976 nginx    1202926950.760533 CALL  close(0xad)

 48976 nginx    1202926950.760548 RET   close -1 errno 57 Socket is not
 connected

Похоже, race возникает тут:

sys/kern/uipc_socket.c:

int soclose(so)

{

...

        if (so->so_state & SS_ISCONNECTED) {

                if ((so->so_state & SS_ISDISCONNECTING) == 0) {

                        error = sodisconnect(so);

}

int sodisconnect(so)

{

...

 if ((so->so_state & SS_ISCONNECTED) == 0)

                return (ENOTCONN);

}

so_state для unix сокета устанавливается следующим образом:

sys/kern/uipc_usrreq.c:

static void

unp_disconnect(struct unpcb *unp)

{

        struct unpcb *unp2 = unp->unp_conn;

        struct socket *so;

        UNP_LOCK_ASSERT();

        if (unp2 == NULL)

                return;

        unp->unp_conn = NULL;

        switch (unp->unp_socket->so_type) {

        case SOCK_DGRAM:

                LIST_REMOVE(unp, unp_reflink);

                so = unp->unp_socket;

                SOCK_LOCK(so);

                so->so_state &= ~SS_ISCONNECTED;

                SOCK_UNLOCK(so);

                break;

        case SOCK_STREAM:

                soisdisconnected(unp->unp_socket);

                unp2->unp_conn = NULL;

                soisdisconnected(unp2->unp_socket);

                break;

        }

}

Насколько я понял, для sock_stream закрываются оба конца соединения.
Закрываются так:

sys/kern/uipc_socket2.c:

void

soisdisconnected(so)

        register struct socket *so;

{

        /*

         * XXXRW: This code assumes that SOCK_LOCK(so) and

         * SOCKBUF_LOCK(&so->so_rcv) are the same.

         */

        SOCKBUF_LOCK(&so->so_rcv);

        so->so_state &=
~(SS_ISCONNECTING|SS_ISCONNECTED|SS_ISDISCONNECTING);

        so->so_state |= SS_ISDISCONNECTED;

        so->so_rcv.sb_state |= SBS_CANTRCVMORE;

        sorwakeup_locked(so);

        SOCKBUF_LOCK(&so->so_snd);

        so->so_snd.sb_state |= SBS_CANTSENDMORE;

        sbdrop_locked(&so->so_snd, so->so_snd.sb_cc);

        sowwakeup_locked(so);

        wakeup(&so->so_timeo);

}

То есть, не исключена ситуация, когда в функции soclose у одного из
сокетов состояние SS_ISCONNECTED, а в функции sodisconnect этот флаг уже
успевает убраться. Тогда может вернуться ENOTCONN. Как вариант фикса можно попробовать убрать проверку

 if ((so->so_state & SS_ISCONNECTED) == 0)

                return (ENOTCONN);

из функции sodisconnect, например, возвращая в этом месте 0.

Связано с категорией: Patches, Work Нет комментариев