Решил сделать второй скачок качества сервиса для своего ejabberd’a.
Первый был при переходе от какой-то древней версии, от которой на протяжении нескольких лет требовался только обмен сообщениями и поддержка конференций с доступом из одной-двух точек – к ejabberd 19.xx, где уже была поддержка MAM, что позволяло иметь общую историю на разных мобильных устройствах – например, телефонах, которыми я к тому времени обзавелся в количестве нескольких штук. Так было еще несколько лет, заброшенный и подглючивающий Xabber на телефоне сменился на Conversations и наличие в нем иконки звонка иногда не давало покоя. Как-то без особой настойчивости попытался воспользоваться функцией, но связь не была установлена и я идею на какое-то время забросил. Однако время все же нашлось и я решил сделать очередной апгрейд – поставить актуальный на сейчас ejabberd 21.12 с поддержкой всех необходимых для звонков функций.
Прежде всего, что требовалось? От сервера требовалось наличие поддержки XEP-0215 – external service discovery, также требовалось наличие STUN/TURN-сервера, но он мог быть и внешним компонентом (например, coturn, как это делается для Prosody). Поддержка XEP-0215 появилась только в ejabberd 20-й версии, так что в любом случае требовалось обновлять то, что есть. Следующая версия в Debian была 21.02 из buster-backports, однако так как ejabberd зависит только от Erlang’a, а тот, в свою очередь, практически не зависит от других компонентов, то легко удалось поставить ejabberd 21.12 из bullseye-backports, не развалив при этом полсистемы. В свежем ejabberd’e был, в том числе, и STUN/TURN-сервер, так что можно было обойтись без внешних компонентов. В целом, чем дальше, тем больше ejabberd превращается в комбайн из разных серверов – XMPP, SIP (давно уже появилась поддержка, но пользоваться не приходилось), STUN/TURN, MQTT (пока вообще не представляю, как использовать). Надеюсь, это не повлияет на его качество в целом.
Прежде всего в заметках и в TODO появилась эта ссылка: https://www.linux.org.ru/forum/security/15963073 – обсуждение конфигурации для Prosody и того, есть ли в звонках шифрование. Идея о том, что неплохо было бы запустить поддержку звонков, зародилась еще с этого треда.
При практических работах по настройке звонков перво-наперво отталкивался от статьи на github’e Conversations’a – https://gist.github.com/iNPUTmice/a28c438d9bbf3f4a3d4c663ffaa224d9. В статье идет отсылка к sample-конфигу для ejabberd’a, по нему и выполнил первоначальную настройку своего сервера. В итоге в одной сети звонок прошел, в разных сетях один раз тоже прошел, но больше это не работало. sample-конфиг был весьма базово расписан и продвинуться дальше мне помогла заметка в блоге у Process One: https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/. Как выяснилось, параметры модуля mod_stun_disco тоже надо было довольно тщательно расписать, чтобы STUN/TURN-сервер знал, что из сервисов и в каких случаях ему надо предоставлять “наружу”.
Для начала расписал секции для обычного STUN/TURN; попутно при попытке выяснить, почему не работает звонок, удалил добавленные ранее SRV-записи в DNS. Так или иначе, с добавленными секциями описания сервисов для mod_stun_disco (и без SRV-записей) все заработало, звонки начали проходить в любом из вариантов. Update: проверил в том числе между своим сервером и conversations.im – все работает.
В дальнейшем решил уже делать все красиво, поэтому были добавлены обратно SRV-записи; плюс добавил в listen-секцию порт, а в конфиг mod_stun_disco секции для работы шифрованных версий STUN/TURN. Эта часть заработала сходу. Смущала только ругань в логах (You have several virtual hosts configured, but option ‘auth_realm’ is undefined and ‘auth_type’ is set to ‘user’, so the TURN relay might not be working properly. Using linuxoid.in as a fallback) на отсутствие опции auth_realm для нешифрованного STUN’а – причем, звонки при этом корректно работали. Согласно посту, надо было заводить отдельные порты для каждого из виртуальных доменов, и отдельные секции для модуля, работающего с этими портами, а также отдельные SRV-записи в DNS, ссылающиеся на эти порты. Соответственно, клиенты, которые по какой-то причине не смогут прочитать SRV-запись, не смогут и подключиться к STUN-серверу на нестандартном порту. Решил указать опцию в виде auth_realm: “@HOST@” – сработало, ругань пропала, звонки работают. Не знаю, насколько корректно это, но все работает.
SRV-записи доставили немного хлопот – сначала делал по образцу руководства на сайте ejabberd’а, но сейчас понимаю, что часть записей там с ошибкой. На то, что я в самом начале пытался скормить админке DNS’а, та ругалась. На деле формат записей ничем не отличается от того, что я уже делал для _xmpp-client и _xmpp-server. Всего были добавлены такие записи:
- _stun._tcp.domain
- _turn._tcp.domain
- _stun._udp.domain
- _turn._udp.domain
- _stuns._tcp.domain
- _turns._tcp.domain
Дополнительно к основным были добавлены еще 2 записи для TLS-подключений в jabber’e согласно актуальным практикам:
- _xmpps-client._tcp.domain
- _xmpps-server._tcp.domain
Отмечу, что несмотря на то, что у меня разрешены только шифрованные подключения, указание для этих записей тех же портов, что и для обычных записей, не позволило пройти тест.
Еще одна статья на тему SRV и шифрованных подключений с использованием SSLH.
Немного о тесте. В процессе поисков статей по настройке сервера наткнулся сайте Conversations на XMPP Compliance Test – сайт, делающий подключение к указанному серверу и проверяющий его на соответствие стандартам и наличие актуальных функций. Для работы теста надо завести аккаунт на нужном сервере и добавить его на сайте. Что интересно, для одного из моих доменов аккаунт уже был создан, но не помню, чтобы это делал я – возможно, кто-то из знакомых. Так или иначе, именно из теста узнал про наличие XMPPS-записей в SRV, а также добавил контакты для жалоб, CORS Headers и Discovering Alternative XMPP Connection Methods – в общем-то, затрат это не требовало почти никаких (пару записей в конфиге ejabberd’а; закинуть пару файликов в каталог веб-сервера и включить поддержку установки header’ов в нем), зато позволяло поднять статус сервера. XEP 313 extended usage и XEP 402 в ejabberd’e вроде (1) пока не поддерживаются. Единственное, чего я не стал делать для повышения статуса – настройку proxy. Я особо не вижу в этом смысла – на сейчас самый толковый способ обмена файлами – это HTTP Fileupload, а он у меня работает. Прокси же – лишний сервис и возможные лишние проблемы.
2 полезные статьи, которые помогли в полной настройке ejabberd’a:
- How to configure ejabberd to get 100% in XMPP compliance test
- Check ejabberd XMPP server useful configuration steps
Из особенностей, всплывших при настройке, стало наследование разрешений от mod_register для mod_register_web. Т.е., если раньше регистрация в клиенте у меня была закрыта, а возможность заведения аккаунта была только через веб-страницу на отдельном порту (доступ к которой можно было бы перекрыть, например, в iptables), то на сейчас правила везде одни. Согласно changelog ejabberd’a, это появилось как раз в версии 21.12. Думаю, есть какой-то обходной путь, позволяющий расписать разрешения отдельно для регистрации через веб, но я пока просто разрешил оба варианта. Update: “mod_register
gets a new option, allow_modules
, to restrict what modules can register new accounts. This is useful if you want to allow only registration using mod_register_web
, for example.”
Из оставшихся на сейчас проблем – ругань в логах на “undefined function node_hometree_sql” и “undefined function node_pep_odbc_sql”, возникающие в логах при некоторых операциях – например, при попытке открытия pubsub.domain в браузере сервисов в джаббер клиенте.
Update: пофиксил ругань. Полезные ссылки: раз, два. Первым делом сделал “select * from pubsub_node;” для каждой из mysql-баз. Таки да, были записи pep_odbc и hometree_odbc. Обновил:
update pubsub_node set plugin = "hometree" where plugin = "hometree_odbc"; update pubsub_node set plugin = "pep" where plugin = "pep_odbc";
А дальше (согласно второй ссылке) вообще удалил записи про hometree:
delete from pubsub_node where plugin = "hometree" ;
Ругань в логах пропала, субдомен pubsub в браузере сервисов начал нормально дискавериться. В админке ejabberd’а в базе (Mnesia) есть несколько записей, где тоже присутствует hometree, но влияния они никакого не оказывают, так как дефолтная база MySQL. Возможно, записи остались еще с тех времен, когда использовалась только Mnesia. Purge’ить содержимое тоже пока не хочу: работает – не трогаю.
Еще мини-update: сразу после фикса ругани на pep потыкал все остальные опции, словил “[error] <0.1113.3>@ejabberd_hooks:safe_apply/4:240 Hook adhoc_local_commands crashed when running mod_configure:adhoc_local_commands/4:
** exception error: no match of right hand side value false”. Что делать – хз, но в целом – все равно не пользуюсь конфигурированием сервера через браузер сервисов. Как вариант – можно вообще попробовать отключить mod_configure (хотя в браузере сервисов есть потенциально используемые опции – например, добавление/удаление пользователей, смена паролей и различная статистика – возможно, оно зависит от того же модуля).
Решил убрать ругань в логах на deprecated “captcha_host” и ротацию логов (последняя делается средствами дистрибутива и не нужна в конфиге). Синтаксис опций для капчи одинаков, однако с captcha_url капча просто не генерируется. Пока нет идей, почему. Далее, макрос @HOST@ в пути к капче использовать нельзя – он в итоге напрямую передается в URL на веб-странице. Update: не работает исключительно mod_register_web, причем капча перестает грузиться даже если присутствуют обе опции.
Из интересных встретившихся проектов – веб-клиент ConverseJS. Есть поддержка чатов, конференций, OMEMO в переписке и многого другого. Вроде есть поддержка http_fileupload, но в том клиенте, что на указанном выше сайте, у меня выгрузить на свой сервер ничего не удалось. Для ejabberd 21.12 добавлен модуль, позволяющий легко интегрировать ConverseJS с ним. Думаю, в ближайшее время опробую его.
Update: опробовал и развернул ConverseJS у себя на сервере; все весьма просто, главное – не забывать, что этот веб-клиент выполняется в браузере, а не на сервере. В принципе, весь конфиг на стороне ejabberd’а можно посмотреть в блоге (либо на любой другой подобной странице документации). Стоит лишь помнить, что отданная ejabberd’ом индексная страница из mod_conversejs будет содержать именно те ссылки, что передаются в качестве параметра модулю – т.е., в данном случае, с отсылкой на localhost, а не на сервер. Поначалу не мог понять, почему вместо веб-клиента вижу белую страницу, потом разобрался. Из изменений у себя:
- Добавил в listener поддержку tls, т.е., теперь все общение с сервером идет по шифрованному каналу (меньше риск, что код загружаемого с сервера ConverseJS по пути подменят). Соответственно, обращение теперь идет через https.
- Убрал websocket с порта – он у меня уже объявлен на другом порту, сразу с шифрованием.
- В настройках mod_conversejs обращение на шифрованный веб-сокет идет через wss://, а не ws://.
- Дефолтный домен актуален разве что при регистрации новых аккаунтов, да и служит разве что фоном поля домена (т.е., все равно придется ввести его вручную).
- На веб-сервере сделал редирект с удобочитаемого домена на https-страничку с путем к ConverseJS.
В архиве с клиентом идут 2 варианта: обычный (converse.js) и minified-версия (т.е., оптимизированная по размеру, но практически нечитабельная). Разница примерно в 2,5 раза. В идеале, конечно, хостить файлы не через ejabberd (хотя бы пока там нет (?) поддержки gz-сжатия для http_fileserver), а через обычный веб-сервер с поддержкой сжатия отдаваемых файлов, тогда от minified-версии остается около 300 КБ, что может быть заметно при первой загрузке на слабых каналах.
Сам по себе клиент – скажем так, хоть как-то, но работает. Есть возможность переписки напрямую; конференций; OMEMO для переписки (вроде и для конференций, но завести не получилось). Баги: аплоад файлов не работает – появляется шкала загрузки, в логах есть “<0.489.0>@mod_http_upload:create_slot/7:834 Got HTTP upload slot (file: 41bcbec6-e7bf-40e7-888c-7efaa8c8af1a.jpg, size: 162082)” и на этом все заканчивается, а индикатор загрузки просто пропадает. В конференции почему-то показывает всех, кто там зарегистрирован, а не тех, кто там находится. Список получается весьма внушительный иногда. Настроек у модуля на стороне ejabberd’а пока практически нет, приходится пользоваться дефолтами – например, интерфейс на английском языке, но в целом это особо не мешает. Будем наблюдать за развитием.
Снова update: тема с аплоадом файлов – не баг, это особенности работы клиентов на JS. Об этом написано в том числе на этой странице (тут же, кстати, есть заметка и про особенности работы в браузере и разнице между подключениями через WS и BOSH). Вкратце: для какого-либо взаимодействия скрипта с одного домена с ресурсами на других доменах надо в упомянутых выше CORS-хедерах на стороне сервера сделать явное разрешение (1, 2). Wildcard @HOST@ в хедерах, как выяснилось, не работает, поэтому для mod_http_fileupload (для работы аплоада файлов из ConverseJS) и для mod_http_fileserver (для возможности подгрузки шрифтов, файла перевода, звуков и т.д.) для хедера Access-Control-Allow-Origin значение поставил “*” (убрав PUT для mod_http_fileserver – все равно он там не нужен).
Проблема с подгрузкой языка решилась слегка костыльно – у ConverseJS есть, я так понимаю, один из доступных для установки параметров – assets_path – который дефолтно установлен в “/dist” и который в существующей на данный момент реализации индексной страницы в mod_conversejs поменять нельзя. В итоге либо надо для mod_http_fileserver указывать в качестве корня каталог уровнем выше (т.е., чтобы смотрел не на dist, а на корень распакованного архива с ConverseJS) и скорректировать при этом остальные пути, либо сделать обходной путь – создать в dist ссылку dist->”.”.
Итого на сейчас из претензий к ConverseJS осталось отображение “мертвых душ” в конференциях и, как выяснилось, косячная подгрузка сообщений из архива. Для некоторых диалогов загрузка есть, для некоторых – чистая страница. Причем, есть написать в чате что-то длинное, чтобы появилась полоса прокрутки, а потом начать прокручивать диалог вверх по истории – начинается подгрузка (вероятно, в первом случае сообщения сохраняются в каком-то локальном кэше, поэтому не загружаются с сервера, зато формируется страница с прокруткой, помогающей выгружать более раннюю историю уже с сервера). Только вот проблема – я в итоге получил в истории диалог вообще с другим контактом. В общем, пока продолжаю наблюдение.
Из полезных найденных утилит – клиент для джаббера для проверки Websocket-подключений.
Пример настроек ConverseJS не через mod_conversejs. Автологин.
Update: еще один фикс. Попалась на глаза ругань в логах:
2022-04-20 08:22:11.506302+00:00 [error] <0.23329.1>@mod_pubsub:get_transaction_response/1:3942 Transaction aborted:
** exception error: no case clause matching {odbc,true}
in function pubsub_node_config:'-encode/3-lc$^0/1-0-'/3 (pubsub_node_config.erl, line 222)
in call from pubsub_node_config:'-encode/3-lc$^0/1-0-'/3 (pubsub_node_config.erl, line 423)
in call from pubsub_node_config:encode/3 (pubsub_node_config.erl, line 423)
in call from mod_pubsub:'-get_configure/5-fun-0-'/6 (mod_pubsub.erl, line 3313)
in call from mod_pubsub:'-transaction/4-fun-0-'/3 (mod_pubsub.erl, line 3881)
in call from mod_pubsub:'-do_transaction/4-fun-0-'/3 (mod_pubsub.erl, line 3903)
in call from ejabberd_sql:execute_bloc/1 (ejabberd_sql.erl, line 571)
in call from ejabberd_sql:run_sql_cmd/4 (ejabberd_sql.erl, line 488)
Бывала не часто, но бывала. При каких ситуациях – понять сложно – но, очевидно, что-то связанное с PEP 🙂 – а я как раз иногда игрался с OMEMO и всяким таким. В глаза бросилось название odbc – таким образом раньше была отсылка к работе с mysql, потом она была заменена на sql. Интересно; поискал на всякий случай в конфигах и в Mnesia – никаких упоминаний. Сделал дамп Mysql-базы – таки нашлось:
select * from pubsub_node_option where name = "odbc";
Запрос давал ряд записей с довольно старым ID. А аналогичный запрос с name = “sql” продолжал этот ряд ID. Судя по всему, ошибка возникала на некоторых функциях у старых Jabber ID – а мой как раз относился к таким. Хотел поспрашивать у знающих людей верны ли мои предположения, но таких не нашлось, поэтому решил привести все записи к напрашивающемуся варианту:
update pubsub_node_option set name = "sql" where name = "odbc";
В таблице pubsub_node одним из значений для моего JID’а как раз был список устройств для Conversations; если сложить одно с другим, то выходит, что проблемы в базе могли приводить к проблемам в работе OMEMO при использовании нескольких устройств. Буду наблюдать.
Update: в общем, таки решил добавить прокси для файлопередачи. http_upload – оно-то хорошо, но зачем ставить крест на других вариантах? Недавно буквально мне кидали фото через “традиционную” передачу, оно не сработало. Решил исправить. Теперь у меня сервер выдает 100% на compliance test 🙂
В процессе копания в разных клиентах-сервисах вскрылось то, что на conversations.im довольно старый сервер: .version conversations.im
[14:51:27]
[14:51:28] <Зверушко> на conversations.im ejabberd 19.05.13-conversations.im unix/linux 4.19.0
Однако при этом звонки работают и XEP-0215 дискаверится.
Нашел полезную команду – удаление старых сообщений из MAM-архива: /usr/sbin/ejabberdctl delete_old_mam_messages all 111, где “111” – число дней, старше которых сообщения надо удалять.
https://docs.ejabberd.im/admin/configuration/listen/#ejabberd-service – пример подключения транспортов, может пригодиться позже.
Словил ошибку подключения со стороны веб-клиента на movim.eu, в логах при этом такое:
2022-05-15 09:43:26.210547+00:00 [warning] <0.20867.5>@ejabberd_c2s:process_terminated/2:310 (tls|<0.20867.5>) Failed to secure c2s connection: TLS failed: SSL_CTX_use_certificate_file failed: error:02001002:system library:fopen:No such file or directory
Любой другой веб (и не веб)-клиент работает нормально. Что оно такое – идей пока нет.
https://github.com/movim/movim/wiki/Configure%20ejabberd – может пригодится что-нибудь.