From: Pablo Polvorin Date: Fri, 2 Dec 2011 18:36:51 +0000 (-0300) Subject: Prevent overload of incomming s2s connections X-Git-Tag: v3.0.0-alpha-5~12 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=df2e050780022a29832c2a08f721b996344d0035;p=ejabberd Prevent overload of incomming s2s connections Three changes were introduced: 1) ejabberd_s2s_in now uses p1_fsm instead of gen_fsm. And uses the {max_queue, N} option to kill the process if its input queue grows too much. 2) If a ejabberd_s2s_in process is overload and killed, the server that originated that connection is not allowed to connect back to us for X seconds (set to 60seconds on the source) 3) The list of blocked (both statically and dynamically by the above method) host is now also checked for hosts authenticating by starttls+sasl. Previusly it was only used during dialback. --- diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index a3ccb7d74..84fe92401 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -42,7 +42,11 @@ allow_host/2, incoming_s2s_number/0, outgoing_s2s_number/0, - migrate/1 + migrate/1, + clean_temporarily_blocked_table/0, + list_temporarily_blocked_hosts/0, + external_host_overloaded/1, + is_temporarly_blocked/1 ]). %% gen_server callbacks @@ -65,9 +69,14 @@ -define(PREFIXED_NS, [{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]). +-define(S2S_OVERLOAD_BLOCK_PERIOD, 60). +%% once a server is temporarly blocked, it stay blocked for 60 seconds + -record(s2s, {fromto, pid, key}). -record(state, {}). +-record(temporarily_blocked, {host, timestamp}). + %%==================================================================== %% API %%==================================================================== @@ -98,6 +107,31 @@ route(From, To, Packet) -> ok end. +clean_temporarily_blocked_table() -> + mnesia:clear_table(temporarily_blocked). +list_temporarily_blocked_hosts() -> + ets:tab2list(temporarily_blocked). + +external_host_overloaded(Host) -> + ?INFO_MSG("Disabling connections from ~s for ~s seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]), + mnesia:transaction( fun() -> + mnesia:write(#temporarily_blocked{host = Host, timestamp = now()}) + end). + +is_temporarly_blocked(Host) -> + case mnesia:dirty_read(temporarily_blocked, Host) of + [] -> false; + [#temporarily_blocked{timestamp = T}=Entry] -> + case timer:now_diff(now(), T) of + N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 -> + mnesia:dirty_delete_object(Entry), + false; + _ -> + true + end + end. + + remove_connection(FromTo, Pid, Key) -> case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo, pid = Pid, @@ -248,6 +282,7 @@ init([]) -> mnesia:add_table_copy(s2s, node(), ram_copies), ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100), ejabberd_commands:register_commands(commands()), + mnesia:create_table(temporarily_blocked, [{ram_copies, [node()]}, {attributes, record_info(fields, temporarily_blocked)}]), {ok, #state{}}. %%-------------------------------------------------------------------- @@ -576,6 +611,8 @@ update_tables() -> %% Check if host is in blacklist or white list allow_host(MyServer, S2SHost) -> + allow_host2(MyServer, S2SHost) andalso (not is_temporarly_blocked(S2SHost)). +allow_host2(MyServer, S2SHost) -> Hosts = ?MYHOSTS, case lists:dropwhile( fun(ParentDomain) -> diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 367bce502..a0cf1a732 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -27,7 +27,7 @@ -module(ejabberd_s2s_in). -author('alexey@process-one.net'). --behaviour(gen_fsm). +-behaviour(p1_fsm). %% External exports -export([start/2, @@ -80,10 +80,12 @@ -define(FSMOPTS, []). -endif. +-define(FSMLIMITS, [{max_queue, 2000}]). %% if queue grows more than this, we shutdown this connection. + %% Module start with or without supervisor: -ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, gen_fsm:start(ejabberd_s2s_in, [SockData, Opts], - ?FSMOPTS)). +-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], + ?FSMOPTS ++ ?FSMLIMITS)). -else. -define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts])). @@ -102,7 +104,7 @@ start(SockData, Opts) -> ?SUPERVISOR_START. start_link(SockData, Opts) -> - gen_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS). + p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS ++ ?FSMLIMITS). socket_type() -> xml_stream. @@ -323,7 +325,8 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> false end, if - AuthRes -> + AllowRemoteHost = ejabberd_s2s:allow_host("", AuthDomain), + AuthRes andalso AllowRemoteHost -> (StateData#state.sockmod):reset_stream( StateData#state.socket), send_element(StateData, @@ -622,9 +625,25 @@ handle_info(_, StateName, StateData) -> %%---------------------------------------------------------------------- terminate(Reason, _StateName, StateData) -> ?DEBUG("terminated: ~p", [Reason]), + case Reason of + {process_limit, _} -> + [ejabberd_s2s:external_host_overloaded(Host) || Host <- get_external_hosts(StateData)]; + _ -> + ok + end, (StateData#state.sockmod):close(StateData#state.socket), ok. +get_external_hosts(StateData) -> + case StateData#state.authenticated of + true -> + [StateData#state.auth_domain]; + false -> + Connections = StateData#state.connections, + [D || {{D, _}, established} <- dict:to_list(Connections)] + end. + + %%%---------------------------------------------------------------------- %%% Internal functions %%%----------------------------------------------------------------------