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

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 комментарий
16Янв/090

Даунгрейд FreeBSD 8 (CURRENT) до 7-STABLE

В ходе даунгрейда выплыла проблема с невозможностью установить мир. Установка затыкалась на mtree, который не мог работать с libc от семерки. Проблема решилась ручной установкой usr.bin/find и usr.sbin/mtree набрав в этих директориях make install. Также stass@ посоветовал способ с исправлением таргета bootstrap-tools из src/Makefile1.inc. Этот способ я не пробовал, но он тоже должен работать. После установки find и mtree, installworld работает нормально. Система, обновленная таким образом тоже нормально работает.

Связано с категорией: FreeBSD Нет комментариев
14Янв/092

Новая версия патча для поддержки exim-xclient

По пожеланиям ряда людей добавил поддержку нового nginx (0.7.x) в реализацию XCLIENT'а для exim'а. Отличие - понимание переданного параметра LOGIN и установка authenticated_id.
patch-exim-xclient

(Обновлено 03.02.2009, исправлен баг с неверным хранением строк, получаемых в команде xclient, спасибо Максиму Дунину)
Связано с категорией: Patches 2 Комментарии
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 Нет комментариев
5Фев/080

Миграция с jabberd2 на openfire

Используя плагин экспорта/импорта у openfire можно заводить
пользователей, используя достаточно простой xml код. Хотя этот плагин
работает достаточно нестабильно (xml файл, содержащий 4к пользователей
он не переварил, вываливаясь с NullPointerException), по крайней мере у
меня, но, возможно, кому-то это окажется полезным.
jabber_to_xml.pl

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

Memcached UDP fix

Следующий патчик исправляет ошибку в обработке udp запросов memcached'ом, которая приводила к падению его в корку при посылке пакета с неверным заголовком, а также не освобождала буфер чтения при ошибочной команде или же ненайденном ключе.
patch-memcached-udp

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

XCLIENT в Exim

По просьбе Андрея Зверева написал патчик, позволяющий работу команды XCLIENT в exim 4. Описание этой команды можно найти тут. По умолчанию XCLIENT запрещен для всех хостов, но его можно включить, задав опцию xclient_allow_hosts в конфигурационном файле, например:

xclient_allow_hosts = 127.0.0.1 : 192.168.1.1

Пример SMTP диалога:
Connected to localhost.
Escape character is '^]'.
220 dhcp-ng2 ESMTP Exim 4.68 Mon, 10 Dec 2007 19:26:44 +0300
XCLIENT NAME=spike.porcupine.org ADDR=168.100.189.2 HELO=blah
220 XCLIENT success
MAIL FROM:<wietse@porcupine.org>
250 OK
RCPT TO:<user@example.com>
550 relay not permitted

patch-exim-xclient

Связано с категорией: Patches 2 Комментарии
7Дек/070

Memcached UDP

Хотя memcached поддерживает формально работу по udp протоколу, но пользоваться им я не рекоммендую. Более того, если мемкешеду послать udp пакет с неправильным заголовком, то он (memcached) падает в корку. Далее, даже при посылке правильных пакетов у мемкешеда через некоторое время выплывает проблема неочистки командного буфера, и при посылке очередной команды мемкешед начинает "вспоминать" куски старых команд. Сейчас я пытаюсь исправить эти баги, и если все удастся, то будет патч совместно с библиотекой работы с мемкешедом, которая умеет udp (tcp тоже, но исходной задачей было написание thread-safe библиотеки для работы с мемкешедом). При использовании udp для извлечения большого количества маленьких ключей из мемкешеда должно весьма существенно ускориться.
Данные тестирования:
UDP:
Results of memcached stress test:
Total number of connections: 10000
Number of seconds for test: 2.23
Number of successfull connections: 10000
Connections per second: 4494.25
TCP:
Results of memcached stress test:
Total number of connections: 10000
Number of seconds for test: 4.38
Number of successfull connections: 10000
Connections per second: 2280.87

При этом, использовалось 100 одновременных коннекций к серверу, каждая из которых выполняла 3 операции над своим ключом: set, get и delete.

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