Distribute routing of MUC messages accross all CPU cores

Also relay as less stanzas as possible through mod_muc workers
This commit is contained in:
Evgeny Khramtsov 2019-07-05 10:35:31 +03:00
parent 05461d1686
commit b3caade0a0
3 changed files with 399 additions and 259 deletions

View File

@ -22,20 +22,19 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_muc).
-author('alexey@process-one.net').
-protocol({xep, 45, '1.25'}).
-behaviour(gen_server).
-ifndef(GEN_SERVER).
-define(GEN_SERVER, gen_server).
-endif.
-behaviour(?GEN_SERVER).
-behaviour(gen_mod).
%% API
-export([start/2,
stop/1,
start_link/3,
reload/3,
room_destroyed/4,
store_room/4,
@ -66,7 +65,9 @@
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4,
get_subscribed_rooms/2]).
get_subscribed_rooms/2,
procname/2,
route/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@ -76,18 +77,15 @@
-include("xmpp.hrl").
-include("mod_muc.hrl").
-include("translate.hrl").
-include("ejabberd_stacktrace.hrl").
-record(state,
{hosts = [] :: [binary()],
server_host = <<"">> :: binary(),
access = {none, none, none, none} :: {atom(), atom(), atom(), atom(), atom()},
history_size = 20 :: non_neg_integer(),
max_rooms_discoitems = 100 :: non_neg_integer(),
queue_type = ram :: ram | file,
default_room_opts = [] :: list(),
room_shaper = none :: ejabberd_shaper:shaper()}).
-record(state, {hosts :: [binary()],
server_host :: binary(),
worker :: pos_integer()}).
-type access() :: {acl:acl(), acl:acl(), acl:acl(), acl:acl(), acl:acl()}.
-type muc_room_opts() :: [{atom(), any()}].
-export_type([access/0]).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok.
-callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}.
@ -116,23 +114,159 @@
%% API
%%====================================================================
start(Host, Opts) ->
gen_mod:start_child(?MODULE, Host, Opts).
case mod_muc_sup:start(Host, Opts) of
{ok, _} ->
MyHosts = gen_mod:get_opt_hosts(Opts),
Mod = gen_mod:db_mod(Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
RMod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
load_permanent_rooms(MyHosts, Host, Opts);
Err ->
Err
end.
stop(Host) ->
Proc = mod_muc_sup:procname(Host),
Rooms = shutdown_rooms(Host),
gen_mod:stop_child(?MODULE, Host),
supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
supervisor:delete_child(ejabberd_gen_mod_sup, Proc),
{wait, Rooms}.
reload(Host, NewOpts, OldOpts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}).
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(ServerHost, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
NewRMod = gen_mod:ram_db_mod(NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
OldRMod = gen_mod:ram_db_mod(OldOpts, ?MODULE),
NewHosts = gen_mod:get_opt_hosts(NewOpts),
OldHosts = gen_mod:get_opt_hosts(OldOpts),
AddHosts = NewHosts -- OldHosts,
DelHosts = OldHosts -- NewHosts,
if NewMod /= OldMod ->
NewMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
true ->
ok
end,
if NewRMod /= OldRMod ->
NewRMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
true ->
ok
end,
lists:foreach(
fun(I) ->
?GEN_SERVER:cast(procname(ServerHost, I),
{reload, AddHosts, DelHosts, NewHosts})
end, lists:seq(1, erlang:system_info(logical_processors))),
load_permanent_rooms(AddHosts, ServerHost, NewOpts),
shutdown_rooms(ServerHost, DelHosts, OldRMod),
lists:foreach(
fun(Host) ->
lists:foreach(
fun({_, _, Pid}) when node(Pid) == node() ->
Pid ! config_reloaded;
(_) ->
ok
end, get_online_rooms(ServerHost, Host))
end, misc:intersection(NewHosts, OldHosts)).
depends(_Host, _Opts) ->
[{mod_mam, soft}].
start_link(Host, Opts, I) ->
Proc = procname(Host, I),
?GEN_SERVER:start_link({local, Proc}, ?MODULE, [Host, Opts, I],
ejabberd_config:fsm_limit_opts([])).
-spec procname(binary(), pos_integer() | {binary(), binary()}) -> atom().
procname(Host, I) when is_integer(I) ->
binary_to_atom(
<<(atom_to_binary(?MODULE, latin1))/binary, "_", Host/binary,
"_", (integer_to_binary(I))/binary>>, utf8);
procname(Host, RoomHost) ->
Cores = erlang:system_info(logical_processors),
I = erlang:phash2(RoomHost, Cores) + 1,
procname(Host, I).
-spec route(stanza()) -> ok.
route(Pkt) ->
To = xmpp:get_to(Pkt),
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
route(Pkt, ServerHost).
-spec route(stanza(), binary()) -> ok.
route(Pkt, ServerHost) ->
From = xmpp:get_from(Pkt),
To = xmpp:get_to(Pkt),
Host = To#jid.lserver,
Access = mod_muc_opt:access(ServerHost),
case acl:match_rule(ServerHost, Access, From) of
allow ->
route(Pkt, Host, ServerHost);
deny ->
Lang = xmpp:get_lang(Pkt),
ErrText = ?T("Access denied by service policy"),
Err = xmpp:err_forbidden(ErrText, Lang),
ejabberd_router:route_error(Pkt, Err)
end.
-spec route(stanza(), binary(), binary()) -> ok.
route(#iq{to = #jid{luser = <<"">>, lresource = <<"">>}} = IQ, _, _) ->
ejabberd_router:process_iq(IQ);
route(#message{lang = Lang, body = Body, type = Type, from = From,
to = #jid{luser = <<"">>, lresource = <<"">>}} = Pkt,
Host, ServerHost) ->
if Type == error ->
ok;
true ->
AccessAdmin = mod_muc_opt:access_admin(ServerHost),
case acl:match_rule(ServerHost, AccessAdmin, From) of
allow ->
Msg = xmpp:get_text(Body),
broadcast_service_message(ServerHost, Host, Msg);
deny ->
ErrText = ?T("Only service administrators are allowed "
"to send service messages"),
Err = xmpp:err_forbidden(ErrText, Lang),
ejabberd_router:route_error(Pkt, Err)
end
end;
route(Pkt, Host, ServerHost) ->
{Room, _, _} = jid:tolower(xmpp:get_to(Pkt)),
case Room of
<<"">> ->
Txt = ?T("No module is handling this query"),
Err = xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)),
ejabberd_router:route_error(Pkt, Err);
_ ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
Proc = procname(ServerHost, {Room, Host}),
case whereis(Proc) of
Pid when Pid == self() ->
route_to_room(Pkt, ServerHost);
Pid when is_pid(Pid) ->
?DEBUG("Routing to MUC worker ~p:~n~s", [Proc, xmpp:pp(Pkt)]),
?GEN_SERVER:cast(Pid, {route_to_room, Pkt});
undefined ->
?DEBUG("MUC worker ~p is dead", [Proc]),
Err = xmpp:err_internal_server_error(),
ejabberd_router:route_error(Pkt, Err)
end;
{ok, Pid} ->
mod_muc_room:route(Pid, Pkt)
end
end.
-spec shutdown_rooms(binary()) -> [pid()].
shutdown_rooms(ServerHost) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
Hosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc),
shutdown_rooms(ServerHost, Hosts, RMod).
-spec shutdown_rooms(binary(), [binary()], module()) -> [pid()].
shutdown_rooms(ServerHost, Hosts, RMod) ->
Rooms = [RMod:get_online_rooms(ServerHost, Host, undefined)
|| Host <- Hosts],
lists:flatmap(
@ -149,18 +283,18 @@ shutdown_rooms(ServerHost) ->
%% C) mod_muc:stop was called, and each room is being terminated
%% In this case, the mod_muc process died before the room processes
%% So the message sending must be catched
-spec room_destroyed(binary(), binary(), pid(), binary()) -> ok.
room_destroyed(Host, Room, Pid, ServerHost) ->
catch gen_mod:get_module_proc(ServerHost, ?MODULE) !
{room_destroyed, {Room, Host}, Pid},
ok.
Proc = procname(ServerHost, {Room, Host}),
?GEN_SERVER:cast(Proc, {room_destroyed, {Room, Host}, Pid}).
%% @doc Create a room.
%% If Opts = default, the default room options are used.
%% Else use the passed options as defined in mod_muc_room.
create_room(Host, Name, From, Nick, Opts) ->
ServerHost = ejabberd_router:host_of_route(Host),
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}).
Proc = procname(ServerHost, {Name, Host}),
?GEN_SERVER:call(Proc, {create, Name, Host, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) ->
store_room(ServerHost, Host, Name, Opts, undefined).
@ -232,228 +366,158 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) ->
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
init([Host, Opts, Worker]) ->
process_flag(trap_exit, true),
#state{access = Access, hosts = MyHosts,
history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State = init_state(Host, Opts),
Mod = gen_mod:db_mod(Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
RMod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
lists:foreach(
fun(MyHost) ->
register_iq_handlers(MyHost),
ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host, Access, HistorySize,
RoomShaper, QueueType)
end, MyHosts),
{ok, State}.
MyHosts = gen_mod:get_opt_hosts(Opts),
register_routes(Host, MyHosts, Worker),
register_iq_handlers(MyHosts, Worker),
{ok, #state{server_host = Host, hosts = MyHosts, worker = Worker}}.
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call({create, Room, Host, From, Nick, Opts}, _From,
#state{server_host = ServerHost,
access = Access, default_room_opts = DefOpts,
history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State) ->
#state{server_host = ServerHost} = State) ->
?DEBUG("MUC: create new room '~s'~n", [Room]),
NewOpts = case Opts of
default -> DefOpts;
_ -> Opts
default -> mod_muc_opt:default_room_options(ServerHost);
_ -> Opts
end,
{ok, Pid} = mod_muc_room:start(
Host, ServerHost, Access,
Room, HistorySize,
RoomShaper, From,
Nick, NewOpts, QueueType),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(ServerHost, Room, Host, Pid),
ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
{reply, ok, State}.
case start_room(RMod, Host, ServerHost, Room, NewOpts, From, Nick) of
{ok, _} ->
ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
{reply, ok, State};
Err ->
{reply, Err, State}
end.
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) ->
NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
NewRMod = gen_mod:ram_db_mod(NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
OldRMod = gen_mod:ram_db_mod(OldOpts, ?MODULE),
#state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts),
if NewMod /= OldMod ->
NewMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
true ->
ok
end,
if NewRMod /= OldRMod ->
NewRMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
true ->
ok
end,
lists:foreach(
fun(NewHost) ->
ejabberd_router:register_route(NewHost, ServerHost),
register_iq_handlers(NewHost)
end, NewHosts -- OldHosts),
lists:foreach(
fun(OldHost) ->
ejabberd_router:unregister_route(OldHost),
unregister_iq_handlers(OldHost)
end, OldHosts -- NewHosts),
lists:foreach(
fun(Host) ->
lists:foreach(
fun({_, _, Pid}) when node(Pid) == node() ->
Pid ! config_reloaded;
(_) ->
ok
end, get_online_rooms(ServerHost, Host))
end, misc:intersection(NewHosts, OldHosts)),
{noreply, NewState};
handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info({route, Packet},
#state{server_host = ServerHost,
access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize, queue_type = QueueType,
max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper} = State) ->
From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet),
Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems,
QueueType) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
handle_cast({route_to_room, Packet}, #state{server_host = ServerHost} = State) ->
try route_to_room(Packet, ServerHost)
catch ?EX_RULE(E, R, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to route packet:~n~s~nReason = ~p",
[xmpp:pp(Packet), {E, {R, StackTrace}}])
end,
{noreply, State};
handle_info({room_destroyed, {Room, Host}, Pid}, State) ->
handle_cast({room_destroyed, {Room, Host}, Pid}, State) ->
ServerHost = State#state.server_host,
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:unregister_online_room(ServerHost, Room, Host, Pid),
{noreply, State};
handle_cast({reload, AddHosts, DelHosts, NewHosts},
#state{server_host = ServerHost, worker = Worker} = State) ->
register_routes(ServerHost, AddHosts, Worker),
register_iq_handlers(AddHosts, Worker),
unregister_routes(DelHosts, Worker),
unregister_iq_handlers(DelHosts, Worker),
{noreply, State#state{hosts = NewHosts}};
handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info({route, Packet}, State) ->
%% We can only receive the packet here from other nodes
%% where mod_muc is not loaded. Such configuration
%% is *highly* discouraged
try route(Packet, State#state.server_host)
catch ?EX_RULE(E, R, St) ->
StackTrace = ?EX_STACK(St),
?ERROR_MSG("Failed to route packet:~n~s~nReason = ~p",
[xmpp:pp(Packet), {E, {R, StackTrace}}])
end,
{noreply, State};
handle_info({room_destroyed, {Room, Host}, Pid}, State) ->
%% For backward compat
handle_cast({room_destroyed, {Room, Host}, Pid}, State);
handle_info(Info, State) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{hosts = MyHosts}) ->
lists:foreach(
fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
unregister_iq_handlers(MyHost)
end, MyHosts).
terminate(_Reason, #state{hosts = Hosts, worker = Worker}) ->
unregister_routes(Hosts, Worker),
unregister_iq_handlers(Hosts, Worker).
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
init_state(Host, Opts) ->
MyHosts = gen_mod:get_opt_hosts(Opts),
Access = mod_muc_opt:access(Opts),
AccessCreate = mod_muc_opt:access_create(Opts),
AccessAdmin = mod_muc_opt:access_admin(Opts),
AccessPersistent = mod_muc_opt:access_persistent(Opts),
AccessMam = mod_muc_opt:access_mam(Opts),
HistorySize = mod_muc_opt:history_size(Opts),
MaxRoomsDiscoItems = mod_muc_opt:max_rooms_discoitems(Opts),
DefRoomOpts = mod_muc_opt:default_room_options(Opts),
QueueType = mod_muc_opt:queue_type(Opts),
RoomShaper = mod_muc_opt:room_shaper(Opts),
#state{hosts = MyHosts,
server_host = Host,
access = {Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam},
default_room_opts = DefRoomOpts,
queue_type = QueueType,
history_size = HistorySize,
max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper}.
-spec register_iq_handlers([binary()], pos_integer()) -> ok.
register_iq_handlers(Hosts, 1) ->
%% Only register handlers on first worker
lists:foreach(
fun(Host) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER,
?MODULE, process_register),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
?MODULE, process_vcard),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUCSUB,
?MODULE, process_mucsub),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE,
?MODULE, process_muc_unique),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items)
end, Hosts);
register_iq_handlers(_, _) ->
ok.
register_iq_handlers(Host) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER,
?MODULE, process_register),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
?MODULE, process_vcard),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUCSUB,
?MODULE, process_mucsub),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE,
?MODULE, process_muc_unique),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items).
-spec unregister_iq_handlers([binary()], pos_integer()) -> ok.
unregister_iq_handlers(Hosts, 1) ->
%% Only unregister handlers on first worker
lists:foreach(
fun(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUCSUB),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS)
end, Hosts);
unregister_iq_handlers(_, _) ->
ok.
unregister_iq_handlers(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUCSUB),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS).
-spec register_routes(binary(), [binary()], pos_integer()) -> ok.
register_routes(ServerHost, Hosts, 1) ->
%% Only register routes on first worker
lists:foreach(
fun(Host) ->
ejabberd_router:register_route(
Host, ServerHost, {apply, ?MODULE, route})
end, Hosts);
register_routes(_, _, _) ->
ok.
do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, _MaxRoomsDiscoItems, QueueType) ->
{AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent, _AccessMam} = Access,
case acl:match_rule(ServerHost, AccessRoute, From) of
allow ->
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, QueueType);
deny ->
Lang = xmpp:get_lang(Packet),
ErrText = ?T("Access denied by service policy"),
Err = xmpp:err_forbidden(ErrText, Lang),
ejabberd_router:route_error(Packet, Err)
end.
-spec unregister_routes([binary()], pos_integer()) -> ok.
unregister_routes(Hosts, 1) ->
%% Only unregister routes on first worker
lists:foreach(
fun(Host) ->
ejabberd_router:unregister_route(Host)
end, Hosts);
unregister_routes(_, _) ->
ok.
do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
_From, #jid{luser = <<"">>, lresource = <<"">>} = _To,
#iq{} = IQ, _DefRoomOpts, _QueueType) ->
ejabberd_router:process_iq(IQ);
do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper,
From, #jid{luser = <<"">>, lresource = <<"">>} = _To,
#message{lang = Lang, body = Body, type = Type} = Packet, _, _) ->
{_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent, _AccessMam} = Access,
if Type == error ->
ok;
true ->
case acl:match_rule(ServerHost, AccessAdmin, From) of
allow ->
Msg = xmpp:get_text(Body),
broadcast_service_message(ServerHost, Host, Msg);
deny ->
ErrText = ?T("Only service administrators are allowed "
"to send service messages"),
Err = xmpp:err_forbidden(ErrText, Lang),
ejabberd_router:route_error(Packet, Err)
end
end;
do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
_From, #jid{luser = <<"">>} = _To, Packet, _DefRoomOpts, _) ->
Err = xmpp:err_service_unavailable(),
ejabberd_router:route_error(Packet, Err);
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, QueueType) ->
{Room, _, Nick} = jid:tolower(To),
-spec route_to_room(stanza(), binary()) -> ok.
route_to_room(Packet, ServerHost) ->
From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet),
{Room, Host, Nick} = jid:tolower(To),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
case is_create_request(Packet) of
true ->
case check_create_room(
ServerHost, Host, Room, From, Access) of
case check_create_room(ServerHost, Host, Room, From) of
true ->
{ok, Pid} = start_new_room(
Host, ServerHost, Access,
Room, HistorySize,
RoomShaper, From, Nick, DefRoomOpts,
QueueType),
RMod:register_online_room(ServerHost, Room, Host, Pid),
mod_muc_room:route(Pid, Packet),
ok;
case start_new_room(Host, ServerHost, Room, From, Nick) of
{ok, Pid} ->
mod_muc_room:route(Pid, Packet);
_Err ->
Err = xmpp:err_internal_server_error(),
ejabberd_router:route_error(Packet, Err)
end;
false ->
Lang = xmpp:get_lang(Packet),
ErrText = ?T("Room creation is denied by service policy"),
@ -467,9 +531,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
ejabberd_router:route_error(Packet, Err)
end;
{ok, Pid} ->
?DEBUG("MUC: send to process ~p~n", [Pid]),
mod_muc_room:route(Pid, Packet),
ok
mod_muc_room:route(Pid, Packet)
end.
-spec process_vcard(iq()) -> iq().
@ -612,11 +674,9 @@ is_create_request(#iq{type = T} = IQ) when T == get; T == set ->
is_create_request(_) ->
false.
-spec check_create_room(binary(), binary(), binary(), jid(), tuple())
-> boolean().
check_create_room(ServerHost, Host, Room, From, Access) ->
{_AccessRoute, AccessCreate, AccessAdmin,
_AccessPersistent, _AccessMam} = Access,
-spec check_create_room(binary(), binary(), binary(), jid()) -> boolean().
check_create_room(ServerHost, Host, Room, From) ->
AccessCreate = mod_muc_opt:access_create(ServerHost),
case acl:match_rule(ServerHost, AccessCreate, From) of
allow ->
case mod_muc_opt:max_room_id(ServerHost) of
@ -624,8 +684,8 @@ check_create_room(ServerHost, Host, Room, From, Access) ->
Regexp = mod_muc_opt:regexp_room_id(ServerHost),
case re:run(Room, Regexp, [unicode, {capture, none}]) of
match ->
case acl:match_rule(
ServerHost, AccessAdmin, From) of
AccessAdmin = mod_muc_opt:access_admin(ServerHost),
case acl:match_rule(ServerHost, AccessAdmin, From) of
allow ->
true;
_ ->
@ -643,37 +703,56 @@ check_create_room(ServerHost, Host, Room, From, Access) ->
false
end.
-spec get_access(binary() | gen_mod:opts()) -> access().
get_access(ServerHost) ->
Access = mod_muc_opt:access(ServerHost),
AccessCreate = mod_muc_opt:access_create(ServerHost),
AccessAdmin = mod_muc_opt:access_admin(ServerHost),
AccessPersistent = mod_muc_opt:access_persistent(ServerHost),
AccessMam = mod_muc_opt:access_mam(ServerHost),
{Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam}.
-spec get_rooms(binary(), binary()) -> [#muc_room{}].
get_rooms(ServerHost, Host) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_rooms(LServer, Host).
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
Mod:get_rooms(ServerHost, Host).
load_permanent_rooms(Host, ServerHost, Access,
HistorySize, RoomShaper, QueueType) ->
-spec load_permanent_rooms([binary()], binary(), gen_mod:opts()) -> ok.
load_permanent_rooms(Hosts, ServerHost, Opts) ->
case mod_muc_opt:preload_rooms(Opts) of
true ->
Access = get_access(Opts),
HistorySize = mod_muc_opt:history_size(Opts),
QueueType = mod_muc_opt:queue_type(Opts),
RoomShaper = mod_muc_opt:room_shaper(Opts),
RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
lists:foreach(
fun(Host) ->
?DEBUG("Loading rooms at ~s", [Host]),
lists:foreach(
fun(R) ->
{Room, _} = R#muc_room.name_host,
case proplists:get_bool(persistent, R#muc_room.opts) of
true ->
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
start_room(RMod, Host, ServerHost, Access,
Room, HistorySize, RoomShaper,
R#muc_room.opts, QueueType);
{ok, _} ->
ok
end;
_ ->
forget_room(ServerHost, Host, Room)
end
end, get_rooms(ServerHost, Host))
end, Hosts);
false ->
ok
end.
start_new_room(Host, ServerHost, Room, From, Nick) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
lists:foreach(
fun(R) ->
{Room, Host} = R#muc_room.name_host,
case proplists:get_bool(persistent, R#muc_room.opts) of
true ->
case RMod:find_online_room(ServerHost, Room, Host) of
error ->
{ok, Pid} = mod_muc_room:start(Host,
ServerHost, Access, Room,
HistorySize, RoomShaper,
R#muc_room.opts, QueueType),
RMod:register_online_room(ServerHost, Room, Host, Pid);
{ok, _} ->
ok
end;
_ ->
forget_room(ServerHost, Host, Room)
end
end, get_rooms(ServerHost, Host)).
start_new_room(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, From,
Nick, DefRoomOpts, QueueType) ->
Opts = case restore_room(ServerHost, Host, Room) of
error ->
error;
@ -687,14 +766,52 @@ start_new_room(Host, ServerHost, Access, Room,
end,
case Opts of
error ->
?DEBUG("MUC: open new room '~s'~n", [Room]),
mod_muc_room:start(Host, ServerHost, Access, Room,
HistorySize, RoomShaper,
From, Nick, DefRoomOpts, QueueType);
?DEBUG("Open new room: ~s", [Room]),
DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
start_room(RMod, Host, ServerHost, Room, DefRoomOpts, From, Nick);
_ ->
?DEBUG("MUC: restore room '~s'~n", [Room]),
mod_muc_room:start(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, Opts, QueueType)
?DEBUG("Restore room: ~s", [Room]),
start_room(RMod, Host, ServerHost, Room, Opts)
end.
start_room(Mod, Host, ServerHost, Room, DefOpts) ->
Access = get_access(ServerHost),
HistorySize = mod_muc_opt:history_size(ServerHost),
QueueType = mod_muc_opt:queue_type(ServerHost),
RoomShaper = mod_muc_opt:room_shaper(ServerHost),
start_room(Mod, Host, ServerHost, Access, Room, HistorySize,
RoomShaper, DefOpts, QueueType).
start_room(Mod, Host, ServerHost, Room, DefOpts, Creator, Nick) ->
Access = get_access(ServerHost),
HistorySize = mod_muc_opt:history_size(ServerHost),
QueueType = mod_muc_opt:queue_type(ServerHost),
RoomShaper = mod_muc_opt:room_shaper(ServerHost),
start_room(Mod, Host, ServerHost, Access, Room,
HistorySize, RoomShaper,
Creator, Nick, DefOpts, QueueType).
start_room(Mod, Host, ServerHost, Access, Room,
HistorySize, RoomShaper, DefOpts, QueueType) ->
case mod_muc_room:start(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, DefOpts, QueueType) of
{ok, Pid} ->
Mod:register_online_room(ServerHost, Room, Host, Pid),
{ok, Pid};
Err ->
Err
end.
start_room(Mod, Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefOpts, QueueType) ->
case mod_muc_room:start(Host, ServerHost, Access, Room,
HistorySize, RoomShaper,
Creator, Nick, DefOpts, QueueType) of
{ok, Pid} ->
Mod:register_online_room(ServerHost, Room, Host, Pid),
{ok, Pid};
Err ->
Err
end.
-spec iq_disco_items(binary(), binary(), jid(), binary(), integer(), binary(),
@ -964,7 +1081,7 @@ mod_opt_type(max_room_id) ->
mod_opt_type(max_rooms_discoitems) ->
econf:non_neg_int();
mod_opt_type(regexp_room_id) ->
econf:binary();
econf:re();
mod_opt_type(max_room_name) ->
econf:pos_int(infinity);
mod_opt_type(max_user_conferences) ->
@ -979,6 +1096,8 @@ mod_opt_type(min_message_interval) ->
econf:number(0);
mod_opt_type(min_presence_interval) ->
econf:number(0);
mod_opt_type(preload_rooms) ->
econf:bool();
mod_opt_type(room_shaper) ->
econf:atom();
mod_opt_type(user_message_shaper) ->
@ -1053,6 +1172,7 @@ mod_options(Host) ->
{room_shaper, none},
{user_message_shaper, none},
{user_presence_shaper, none},
{preload_rooms, true},
{default_room_options,
[{allow_change_subj,true},
{allow_private_messages,true},

View File

@ -25,6 +25,7 @@
-export([min_message_interval/1]).
-export([min_presence_interval/1]).
-export([name/1]).
-export([preload_rooms/1]).
-export([queue_type/1]).
-export([ram_db_type/1]).
-export([regexp_room_id/1]).
@ -164,6 +165,12 @@ name(Opts) when is_map(Opts) ->
name(Host) ->
gen_mod:get_module_opt(Host, mod_muc, name).
-spec preload_rooms(gen_mod:opts() | global | binary()) -> boolean().
preload_rooms(Opts) when is_map(Opts) ->
gen_mod:get_opt(preload_rooms, Opts);
preload_rooms(Host) ->
gen_mod:get_module_opt(Host, mod_muc, preload_rooms).
-spec queue_type(gen_mod:opts() | global | binary()) -> 'file' | 'ram'.
queue_type(Opts) when is_map(Opts) ->
gen_mod:get_opt(queue_type, Opts);
@ -176,7 +183,7 @@ ram_db_type(Opts) when is_map(Opts) ->
ram_db_type(Host) ->
gen_mod:get_module_opt(Host, mod_muc, ram_db_type).
-spec regexp_room_id(gen_mod:opts() | global | binary()) -> binary().
-spec regexp_room_id(gen_mod:opts() | global | binary()) -> <<>> | re:mp().
regexp_room_id(Opts) when is_map(Opts) ->
gen_mod:get_opt(regexp_room_id, Opts);
regexp_room_id(Host) ->

View File

@ -93,23 +93,35 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
atom(), jid(), binary(), [{atom(), term()}], ram | file) ->
{ok, pid()} | {error, any()}.
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) ->
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS).
-spec start(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
atom(), [{atom(), term()}], ram | file) ->
{ok, pid()} | {error, any()}.
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts, QueueType],
?FSMOPTS).
-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
atom(), jid(), binary(), [{atom(), term()}], ram | file) ->
{ok, pid()} | {error, any()}.
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) ->
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS).
-spec start_link(binary(), binary(), mod_muc:access(), binary(), non_neg_integer(),
atom(), [{atom(), term()}], ram | file) ->
{ok, pid()} | {error, any()}.
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts, QueueType],
@ -756,6 +768,7 @@ terminate(Reason, _StateName,
%%%----------------------------------------------------------------------
-spec route(pid(), stanza()) -> ok.
route(Pid, Packet) ->
?DEBUG("Routing to MUC room ~p:~n~s", [Pid, xmpp:pp(Packet)]),
#jid{lresource = Nick} = xmpp:get_to(Packet),
p1_fsm:send_event(Pid, {route, Nick, Packet}).