Tuesday, August 12, 2008

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

Во время работы в "Рамблере" я написал несколько вещей, которые могут быть полезны не только у нас, но и пригодиться другим. Поэтому я решил выложить их в виде 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'у, если данный проект получится удачным, то он тоже будет открыт, скорее всего.

Thursday, June 5, 2008

Использование 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

Friday, February 15, 2008

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.

Tuesday, February 5, 2008

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

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