diff --git a/big_tests/tests/connect_SUITE.erl b/big_tests/tests/connect_SUITE.erl index 9c5b7fdac27..34e1a95db75 100644 --- a/big_tests/tests/connect_SUITE.erl +++ b/big_tests/tests/connect_SUITE.erl @@ -169,9 +169,9 @@ init_per_group(feature_order, Config) -> configure_c2s_listener(Config, #{zlib => 10000, tls => [starttls_required | common_tls_opts(Config)]}), Config; -init_per_group(just_tls,Config)-> +init_per_group(just_tls, Config)-> [{tls_module, just_tls} | Config]; -init_per_group(fast_tls,Config)-> +init_per_group(fast_tls, Config)-> [{tls_module, fast_tls} | Config]; init_per_group(proxy_protocol, Config) -> configure_c2s_listener(Config, #{proxy_protocol => true}), @@ -765,10 +765,13 @@ configure_c2s_listener(Config, ExtraC2SOpts) -> mongoose_helper:restart_listener(mim(), NewC2SListener). common_tls_opts(Config) -> - TLSModule = ?config(tls_module, Config), - [{tls_module, TLSModule}, - {certfile, ?CERT_FILE}, - {dhfile, ?DH_FILE}]. + tls_module_opts(Config) ++ [{certfile, ?CERT_FILE}, {dhfile, ?DH_FILE}]. + +tls_module_opts(Config) -> + case ?config(tls_module, Config) of + undefined -> []; + Module -> [{tls_module, Module}] + end. set_secure_connection_protocol(UserSpec, Version) -> [{ssl_opts, [{versions, [Version]}]} | UserSpec]. diff --git a/big_tests/tests/domain_rest_helper.erl b/big_tests/tests/domain_rest_helper.erl index 3e3ff3a4d5b..93b2eebf6b1 100644 --- a/big_tests/tests/domain_rest_helper.erl +++ b/big_tests/tests/domain_rest_helper.erl @@ -19,6 +19,7 @@ stop_listener/1]). -import(distributed_helper, [mim/0, mim2/0, require_rpc_nodes/1, rpc/4]). +-import(config_parser_helper, [default_config/1, config/2]). -define(TEST_PORT, 8866). @@ -98,23 +99,19 @@ patch_custom(Config, Role, Path, Body) -> %% REST handler setup start_listener(Params) -> - rpc(mim(), ejabberd_listener, start_listener, [listener_opts(Params)]). + rpc(mim(), mongoose_listener, start_listener, [listener_opts(Params)]). stop_listener(Params) -> - rpc(mim(), ejabberd_listener, stop_listener, [listener_opts(Params)]). + rpc(mim(), mongoose_listener, stop_listener, [listener_opts(Params)]). listener_opts(Params) -> - #{port => ?TEST_PORT, - ip_tuple => {127, 0, 0, 1}, - ip_address => "127.0.0.1", - ip_version => 4, - proto => tcp, - module => ejabberd_cowboy, - modules => [domain_handler(Params)], - transport_options => transport_options()}. - -transport_options() -> - [{max_connections, 1024}, {num_acceptors, 10}]. + config([listen, http], + #{port => ?TEST_PORT, + ip_tuple => {127, 0, 0, 1}, + ip_address => "127.0.0.1", + module => ejabberd_cowboy, + handlers => [domain_handler(Params)], + transport => config([listen, http, transport], #{num_acceptors => 10})}). domain_handler(Params) -> {"localhost", "/api", mongoose_domain_handler, handler_opts(Params)}. diff --git a/big_tests/tests/mongoose_helper.erl b/big_tests/tests/mongoose_helper.erl index 65a27d9d7d3..fb3051799f3 100644 --- a/big_tests/tests/mongoose_helper.erl +++ b/big_tests/tests/mongoose_helper.erl @@ -493,8 +493,8 @@ get_listeners(#{} = Spec, Pattern) -> %% 'port', 'ip_tuple' and 'proto' options need to stay unchanged for a successful restart restart_listener(Spec, Listener) -> - rpc(Spec, ejabberd_listener, stop_listener, [Listener]), - rpc(Spec, ejabberd_listener, start_listener, [Listener]). + rpc(Spec, mongoose_listener, stop_listener, [Listener]), + rpc(Spec, mongoose_listener, start_listener, [Listener]). should_minio_be_running(Config) -> case proplists:get_value(preset, Config, undefined) of diff --git a/big_tests/tests/rest_helper.erl b/big_tests/tests/rest_helper.erl index ce15d72bf09..7a99b16c987 100644 --- a/big_tests/tests/rest_helper.erl +++ b/big_tests/tests/rest_helper.erl @@ -255,34 +255,34 @@ get_port(Role, Node, _Params) -> get_ssl_status(Role, Node) -> Listeners = rpc(Node, mongoose_config, get_opt, [listen]), [Opts] = lists:filter(fun (Opts) -> is_roles_config(Opts, Role) end, Listeners), - maps:is_key(ssl, Opts). + maps:is_key(tls, Opts). % @doc Changes the control credentials for admin by restarting the listener % with new options. -spec change_admin_creds({User :: binary(), Password :: binary()}) -> 'ok' | 'error'. change_admin_creds(Creds) -> stop_admin_listener(), - {ok, _} = start_admin_listener(Creds). + ok = start_admin_listener(Creds). -spec stop_admin_listener() -> 'ok' | {'error', 'not_found' | 'restarting' | 'running' | 'simple_one_for_one'}. stop_admin_listener() -> Listeners = rpc(mim(), mongoose_config, get_opt, [listen]), [Opts] = lists:filter(fun (Opts) -> is_roles_config(Opts, admin) end, Listeners), - rpc(mim(), ejabberd_listener, stop_listener, [Opts]). + rpc(mim(), mongoose_listener, stop_listener, [Opts]). -spec start_admin_listener(Creds :: {binary(), binary()}) -> {'error', pid()} | {'ok', _}. start_admin_listener(Creds) -> Listeners = rpc(mim(), mongoose_config, get_opt, [listen]), [Opts] = lists:filter(fun (Opts) -> is_roles_config(Opts, admin) end, Listeners), NewOpts = insert_creds(Opts, Creds), - rpc(mim(), ejabberd_listener, start_listener, [NewOpts]). + rpc(mim(), mongoose_listener, start_listener, [NewOpts]). -insert_creds(Opts = #{modules := Modules}, Creds) -> +insert_creds(Opts = #{handlers := Modules}, Creds) -> {Host, Path, mongoose_api_admin, PathOpts} = lists:keyfind(mongoose_api_admin, 3, Modules), NewPathOpts = inject_creds_to_opts(PathOpts, Creds), NewModules = lists:keyreplace(mongoose_api_admin, 3, Modules, {Host, Path, mongoose_api_admin, NewPathOpts}), - Opts#{modules := NewModules}. + Opts#{handlers := NewModules}. inject_creds_to_opts(PathOpts, any) -> lists:keydelete(auth, 1, PathOpts); @@ -298,9 +298,9 @@ inject_creds_to_opts(PathOpts, Creds) -> % This is determined based on modules used. If there is any mongoose_api_admin module used, % it is admin config. If not and there is at least one mongoose_api_client* module used, % it's clients. -is_roles_config(#{module := ejabberd_cowboy, modules := Modules}, admin) -> +is_roles_config(#{module := ejabberd_cowboy, handlers := Modules}, admin) -> lists:any(fun({_, _Path, Mod, _Args}) -> Mod == mongoose_api_admin; (_) -> false end, Modules); -is_roles_config(#{module := ejabberd_cowboy, modules := Modules}, client) -> +is_roles_config(#{module := ejabberd_cowboy, handlers := Modules}, client) -> ModulesTokens = lists:map(fun({_, _Path, Mod, _}) -> string:tokens(atom_to_list(Mod), "_"); (_) -> [] end, Modules), diff --git a/doc/configuration/listen.md b/doc/configuration/listen.md index 8b2335cfe41..4942e3795c2 100644 --- a/doc/configuration/listen.md +++ b/doc/configuration/listen.md @@ -37,11 +37,11 @@ The port number to which the listening socket is bound. The IP address to which the listening socket is bound. ### `listen.*.proto` -* **Syntax:** string, `"udp"` or `"tcp"` +* **Syntax:** string, only `"tcp"` is accepted * **Default:** `"tcp"` -* **Example:** `proto = "udp"` +* **Example:** `proto = "tcp"` -The protocol, which is TCP by default. There is no reason to change this for XMPP or HTTP listeners. +The protocol, which is TCP by default. Currently this is the only valid option. ### `listen.*.ip_version` * **Syntax:** integer, `4` or `6` @@ -78,11 +78,11 @@ Hibernation greatly reduces memory consumption of client processes, but *may* re The default, recommended value of 0 means that the client processes will hibernate at every opportunity. ### `listen.*.max_stanza_size` -* **Syntax:** positive integer -* **Default:** not set, unlimited size +* **Syntax:** positive integer or the string `"infinity"` +* **Default:** `"infinity"` * **Example:** `max_stanza_size = 10_000` -Maximum allowed incoming stanza size in bytes. +Maximum allowed incoming stanza size in bytes. !!! Warning This limit is checked **after** the input data parsing, so it does not apply to the input data size itself. diff --git a/src/config/mongoose_config_spec.erl b/src/config/mongoose_config_spec.erl index 0e7599714b1..95a03c33b76 100644 --- a/src/config/mongoose_config_spec.erl +++ b/src/config/mongoose_config_spec.erl @@ -252,84 +252,107 @@ domain_cert() -> %% path: listen listen() -> - Keys = [<<"http">>, <<"c2s">>, <<"s2s">>, <<"service">>], + Keys = [c2s, s2s, service, http], #section{ - items = maps:from_list([{Key, #list{items = listener(Key), - wrap = none}} || Key <- Keys]), + items = maps:from_list([{atom_to_binary(Key), #list{items = listener(Key), wrap = none}} + || Key <- Keys]), process = fun mongoose_listener_config:verify_unique_listeners/1, wrap = global_config }. %% path: listen.*[] listener(Type) -> - ExtraItems = listener_items(Type), - #section{ - items = ExtraItems#{<<"port">> => #option{type = integer, - validate = port}, - <<"ip_address">> => #option{type = string, - validate = ip_address}, - <<"proto">> => #option{type = atom, - validate = {enum, [tcp, udp]}}, - <<"ip_version">> => #option{type = integer, - validate = {enum, [4, 6]}} - }, - format_items = map, - required = [<<"port">>], - defaults = #{<<"proto">> => tcp}, - process = fun ?MODULE:process_listener/2 - }. - -listener_items(<<"http">>) -> - #{<<"tls">> => http_listener_tls(), - <<"transport">> => http_transport(), - <<"protocol">> => http_protocol(), - <<"handlers">> => http_handlers() - }; -listener_items(Type) -> - ExtraItems = xmpp_listener_items(Type), - ExtraItems#{<<"hibernate_after">> => #option{type = integer, - validate = non_negative}, - <<"max_stanza_size">> => #option{type = integer, - validate = positive}, - <<"backlog">> => #option{type = integer, - validate = non_negative}, - <<"proxy_protocol">> => #option{type = boolean}, - <<"num_acceptors">> => #option{type = integer, - validate = positive, - wrap = {kv, acceptors_num}} - }. - -xmpp_listener_items(<<"c2s">>) -> - #{<<"access">> => #option{type = atom, - validate = non_empty}, - <<"shaper">> => #option{type = atom, - validate = non_empty}, - <<"xml_socket">> => #option{type = boolean}, - <<"zlib">> => #option{type = integer, - validate = positive}, - <<"max_fsm_queue">> => #option{type = integer, - validate = positive}, - <<"allowed_auth_methods">> => #list{items = #option{type = atom, - validate = {module, ejabberd_auth}}}, - <<"tls">> => c2s_tls()}; -xmpp_listener_items(<<"s2s">>) -> - #{<<"shaper">> => #option{type = atom, - validate = non_empty}, - <<"tls">> => s2s_tls()}; -xmpp_listener_items(<<"service">>) -> - #{<<"access">> => #option{type = atom, - validate = non_empty}, - <<"shaper_rule">> => #option{type = atom, - validate = non_empty}, - <<"check_from">> => #option{type = boolean, - wrap = {kv, service_check_from}}, - <<"hidden_components">> => #option{type = boolean}, - <<"conflict_behaviour">> => #option{type = atom, - validate = {enum, [kick_old, disconnect]}}, - <<"password">> => #option{type = string, - validate = non_empty}, - <<"max_fsm_queue">> => #option{type = integer, - validate = positive}}. + merge_sections(listener_common(), listener_extra(Type)). + +listener_common() -> + #section{items = #{<<"port">> => #option{type = integer, + validate = port}, + <<"ip_address">> => #option{type = string, + validate = ip_address}, + <<"proto">> => #option{type = atom, + validate = {enum, [tcp]}}, + <<"ip_version">> => #option{type = integer, + validate = {enum, [4, 6]}} + }, + format_items = map, + required = [<<"port">>], + defaults = #{<<"proto">> => tcp}, + process = fun ?MODULE:process_listener/2 + }. + +listener_extra(http) -> + #section{items = #{<<"tls">> => http_listener_tls(), + <<"transport">> => http_transport(), + <<"protocol">> => http_protocol(), + <<"handlers">> => http_handlers()}}; +listener_extra(Type) -> + merge_sections(xmpp_listener_common(), xmpp_listener_extra(Type)). + +xmpp_listener_common() -> + #section{items = #{<<"backlog">> => #option{type = integer, + validate = non_negative}, + <<"proxy_protocol">> => #option{type = boolean}, + <<"hibernate_after">> => #option{type = integer, + validate = non_negative}, + <<"max_stanza_size">> => #option{type = int_or_infinity, + validate = positive}, + <<"num_acceptors">> => #option{type = integer, + validate = positive} + }, + defaults = #{<<"backlog">> => 100, + <<"proxy_protocol">> => false, + <<"hibernate_after">> => 0, + <<"max_stanza_size">> => infinity, + <<"num_acceptors">> => 100}, + format_items = map + }. + +xmpp_listener_extra(c2s) -> + #section{items = #{<<"access">> => #option{type = atom, + validate = non_empty}, + <<"shaper">> => #option{type = atom, + validate = non_empty}, + <<"zlib">> => #option{type = integer, + validate = positive}, + <<"max_fsm_queue">> => #option{type = integer, + validate = positive}, + <<"allowed_auth_methods">> => #list{items = #option{type = atom, + validate = {module, ejabberd_auth}}, + validate = unique}, + <<"tls">> => c2s_tls()}, + defaults = #{<<"access">> => all, + <<"shaper">> => none}, + format_items = map + }; +xmpp_listener_extra(s2s) -> + #section{items = #{<<"shaper">> => #option{type = atom, + validate = non_empty}, + <<"tls">> => s2s_tls()}, + defaults = #{<<"shaper">> => none}, + format_items = map + }; +xmpp_listener_extra(service) -> + #section{items = #{<<"access">> => #option{type = atom, + validate = non_empty}, + <<"shaper_rule">> => #option{type = atom, + validate = non_empty}, + <<"check_from">> => #option{type = boolean}, + <<"hidden_components">> => #option{type = boolean}, + <<"conflict_behaviour">> => #option{type = atom, + validate = {enum, [kick_old, disconnect]}}, + <<"password">> => #option{type = string, + validate = non_empty}, + <<"max_fsm_queue">> => #option{type = integer, + validate = positive} + }, + required = [<<"password">>], + defaults = #{<<"access">> => all, + <<"shaper_rule">> => none, + <<"check_from">> => true, + <<"hidden_components">> => false, + <<"conflict_behaviour">> => disconnect}, + format_items = map + }. %% path: listen.c2s[].tls c2s_tls() -> @@ -389,7 +412,6 @@ http_listener_tls() -> items = Items#{<<"verify_mode">> => #option{type = atom, validate = {enum, [peer, selfsigned_peer, none]}} }, - wrap = {kv, ssl}, process = fun ?MODULE:process_tls_sni/1 }. @@ -401,14 +423,19 @@ http_transport() -> <<"max_connections">> => #option{type = int_or_infinity, validate = non_negative} }, - wrap = {kv, transport_options} + format_items = map, + defaults = #{<<"num_acceptors">> => 100, + <<"max_connections">> => 1024}, + include = always }. %% path: listen.http[].protocol http_protocol() -> #section{ items = #{<<"compress">> => #option{type = boolean}}, - wrap = {kv, protocol_options} + format_items = map, + defaults = #{<<"compress">> => false}, + include = always }. %% path: listen.http[].handlers @@ -424,7 +451,7 @@ http_handlers() -> items = maps:from_list([{Key, #list{items = http_handler(Key), wrap = none}} || Key <- Keys]), validate_keys = module, - wrap = {kv, modules} + include = always }. %% path: listen.http[].handlers.*[] @@ -450,8 +477,8 @@ http_handler_items(<<"mod_websockets">>) -> validate = positive}, <<"max_stanza_size">> => #option{type = int_or_infinity, validate = positive}, - <<"service">> => #section{items = xmpp_listener_items(<<"service">>), - wrap = {kv, ejabberd_service}}}; + <<"service">> => xmpp_listener_extra(service) + }; http_handler_items(<<"lasse_handler">>) -> #{<<"module">> => #option{type = atom, validate = module}}; @@ -1313,3 +1340,12 @@ process_s2s_address(M) -> process_domain_cert(#{domain := Domain, certfile := Certfile}) -> {Domain, Certfile}. + +%% Helpers + +merge_sections(BasicSection, ExtraSection) -> + #section{items = Items1, required = Required1, defaults = Defaults1} = BasicSection, + #section{items = Items2, required = Required2, defaults = Defaults2} = ExtraSection, + BasicSection#section{items = maps:merge(Items1, Items2), + required = Required1 ++ Required2, + defaults = maps:merge(Defaults1, Defaults2)}. diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 44dca969625..c2befdcfd22 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -68,7 +68,7 @@ start(normal, _Args) -> mongoose_modules:start(), service_mongoose_system_metrics:verify_if_configured(), mongoose_metrics:init(), - ejabberd_listener:start_listeners(), + mongoose_listener:start(), ejabberd_admin:start(), update_status_file(started), ?LOG_NOTICE(#{what => mongooseim_node_started, version => ?MONGOOSE_VERSION, node => node()}), @@ -81,7 +81,7 @@ start(_, _) -> %% before shutting down the processes of the application. prep_stop(State) -> mongoose_deprecations:stop(), - ejabberd_listener:stop_listeners(), + mongoose_listener:stop(), mongoose_modules:stop(), mongoose_service:stop(), broadcast_c2s_shutdown(), diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 1798cf325d6..28bc1e6e971 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -32,7 +32,6 @@ terminate_session/2, start_link/2, send_text/2, - socket_type/0, get_presence/1, get_aux_field/2, set_aux_field/3, @@ -46,6 +45,10 @@ run_remote_hook/3, run_remote_hook_after/4]). +%% mongoose_listener API +-export([socket_type/0, + start_listener/1]). + %% gen_fsm callbacks -export([init/1, wait_for_stream/2, @@ -74,28 +77,39 @@ -include_lib("exml/include/exml.hrl"). -xep([{xep, 18}, {version, "0.2"}]). -behaviour(p1_fsm_old). +-behaviour(mongoose_listener). -export_type([broadcast/0, packet/0, state/0]). -type packet() :: {jid:jid(), jid:jid(), exml:element()}. --type sock_data() :: term(). +-type socket_data() :: {ejabberd:sockmod(), term()}. -type start_result() :: {error, _} | {ok, undefined | pid()} | {ok, undefined | pid(), _}. +-type options() :: #{access := atom(), + shaper := atom(), + hibernate_after := non_neg_integer(), + atom() => any()}. + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- --spec start(sock_data(), ejabberd_listener:opts()) -> start_result(). +-spec start_listener(options()) -> ok. +start_listener(Opts) -> + mongoose_tcp_listener:start_listener(Opts). + +-spec start(socket_data(), options()) -> start_result(). start(SockData, Opts) -> ?SUPERVISOR_START(SockData, Opts). --spec start_link(sock_data(), ejabberd_listener:opts()) -> start_result(). +-spec start_link(socket_data(), options()) -> start_result(). start_link(SockData, Opts) -> p1_fsm_old:start_link(ejabberd_c2s, {SockData, Opts}, ?FSMOPTS ++ fsm_limit_opts(Opts)). +-spec socket_type() -> mongoose_listener:socket_type(). socket_type() -> xml_stream. @@ -177,51 +191,23 @@ run_remote_hook_after(Delay, Pid, HandlerName, Args) -> %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- --spec init({sock_data(), ejabberd_listener:opts()}) -> +-spec init({socket_data(), options()}) -> {stop, normal} | {ok, wait_for_stream, state(), non_neg_integer()}. init({{SockMod, Socket}, Opts}) -> - Access = case lists:keyfind(access, 1, Opts) of - {_, A} -> A; - _ -> all - end, - Shaper = case lists:keyfind(shaper, 1, Opts) of - {_, S} -> S; - _ -> none - end, - XMLSocket = - case lists:keyfind(xml_socket, 1, Opts) of - {_, XS} -> XS; - _ -> false - end, - Zlib = case lists:keyfind(zlib, 1, Opts) of - {_, ZlibLimit} -> {true, ZlibLimit}; - _ -> {false, 0} - end, - Verify = case lists:member(verify_peer, Opts) of + #{access := Access, shaper := Shaper, hibernate_after := HibernateAfter} = Opts, + XMLSocket = maps:get(xml_socket, Opts, false), + Zlib = zlib(Opts), + TLSConfig = maps:get(tls, Opts, []), + Verify = case lists:member(verify_peer, TLSConfig) of true -> verify_peer; false -> verify_none end, - HibernateAfter = - case lists:keyfind(hibernate_after, 1, Opts) of - {_, HA} -> HA; - _ -> 0 - end, - StartTLS = lists:member(starttls, Opts) orelse Verify =:= verify_peer, - StartTLSRequired = lists:member(starttls_required, Opts), - TLSEnabled = lists:member(tls, Opts), + StartTLS = lists:member(starttls, TLSConfig) orelse Verify =:= verify_peer, + StartTLSRequired = lists:member(starttls_required, TLSConfig), + TLSEnabled = lists:member(tls, TLSConfig), TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled, - TLSOpts1 = - lists:filter(fun({certfile, _}) -> true; - ({cafile, _}) -> true; - ({ciphers, _}) -> true; - ({protocol_options, _}) -> true; - ({dhfile, _}) -> true; - ({tls_module, _}) -> true; - ({ssl_options, _}) -> true; - (_) -> false - end, Opts), - TLSOpts = verify_opts(Verify) ++ TLSOpts1, - [ssl_crl_cache:insert({file, CRL}) || CRL <- proplists:get_value(crlfiles, Opts, [])], + TLSOpts = verify_opts(Verify) ++ TLSConfig, + [ssl_crl_cache:insert({file, CRL}) || CRL <- proplists:get_value(crlfiles, TLSConfig, [])], IP = peerip(SockMod, Socket), %% Check if IP is blacklisted: case is_ip_blacklisted(IP) of @@ -259,6 +245,9 @@ init({{SockMod, Socket}, Opts}) -> ?C2S_OPEN_TIMEOUT} end. +zlib(#{zlib := Limit}) -> {true, Limit}; +zlib(#{}) -> {false, 0}. + %% @doc Return list of all available resources of contacts, get_subscribed(FsmRef) -> p1_fsm_old:sync_send_all_state_event(FsmRef, get_subscribed, 1000). @@ -2498,18 +2487,15 @@ check_from(El, #jid{ luser = C2SU, lserver = C2SS, lresource = C2SR }) -> end end. - -fsm_limit_opts(Opts) -> - case lists:keyfind(max_fsm_queue, 1, Opts) of - {_, N} when is_integer(N) -> +-spec fsm_limit_opts(options()) -> [{max_queue, integer()}]. +fsm_limit_opts(#{max_fsm_queue := N}) -> + [{max_queue, N}]; +fsm_limit_opts(#{}) -> + case mongoose_config:lookup_opt(max_fsm_queue) of + {ok, N} -> [{max_queue, N}]; - _ -> - case mongoose_config:lookup_opt(max_fsm_queue) of - {ok, N} -> - [{max_queue, N}]; - {error, not_found} -> - [] - end + {error, not_found} -> + [] end. -spec bounce_messages(list(), #state{}) -> 'ok'. diff --git a/src/ejabberd_cowboy.erl b/src/ejabberd_cowboy.erl index 46e9dc0d554..9ae5339309a 100644 --- a/src/ejabberd_cowboy.erl +++ b/src/ejabberd_cowboy.erl @@ -16,12 +16,13 @@ %%% @end %%%=================================================================== -module(ejabberd_cowboy). --behavior(gen_server). --behavior(cowboy_middleware). +-behaviour(gen_server). +-behaviour(cowboy_middleware). +-behaviour(mongoose_listener). -%% ejabberd_listener API +%% mongoose_listener API -export([socket_type/0, - start_listener/2]). + start_listener/1]). %% cowboy_middleware API -export([execute/2]). @@ -37,18 +38,28 @@ %% helper for internal use -export([ref/1, reload_dispatch/1]). --export([start_cowboy/2, stop_cowboy/1]). +-export([start_cowboy/4, start_cowboy/2, stop_cowboy/1]). -ignore_xref([behaviour_info/1, process/1, ref/1, socket_type/0, start_cowboy/2, - start_link/1, start_listener/2, start_listener/1, stop_cowboy/1]). + start_cowboy/4, start_link/1, start_listener/2, start_listener/1, stop_cowboy/1]). -include("mongoose.hrl"). --type options() :: [any()]. +-type options() :: [any()]. -type path() :: binary(). -type paths() :: [path()]. -type route() :: {path() | paths(), module(), options()}. -type implemented_result() :: [route()]. +-type listener_options() :: #{port := inet:port_number(), + ip_tuple := inet:ip_address(), + ip_address := string(), + ip_version := 4 | 6, + proto := tcp, + handlers := list(), + transport := ranch:opts(), + protocol := cowboy:opts(), + atom() => any()}. + -export_type([options/0]). -export_type([path/0]). -export_type([route/0]). @@ -56,33 +67,33 @@ -callback cowboy_router_paths(path(), options()) -> implemented_result(). --record(cowboy_state, {ref, opts = []}). +-record(cowboy_state, {ref :: atom(), opts :: listener_options()}). %%-------------------------------------------------------------------- -%% ejabberd_listener API +%% mongoose_listener API %%-------------------------------------------------------------------- +-spec socket_type() -> mongoose_listener:socket_type(). socket_type() -> independent. -start_listener({Port, IP, tcp}=Listener, Opts) -> - Ref = ref(Listener), - ChildSpec = {Listener, {?MODULE, start_link, - [#cowboy_state{ref = Ref, opts = Opts}]}, - transient, infinity, worker, [?MODULE]}, - {ok, Pid} = supervisor:start_child(ejabberd_listeners, ChildSpec), - TransportOpts = proplists:get_value(transport_options, Opts, []), - TransportOptsMap = maps:from_list(TransportOpts), - TransportOptsMap2 = TransportOptsMap#{socket_opts => [{port, Port}, {ip, IP}]}, - TransportOptsMap3 = maybe_insert_max_connections(TransportOptsMap2, Opts), - Opts2 = lists:keystore(transport_options, 1, Opts, {transport_options, TransportOptsMap3}), - {ok, _} = start_cowboy(Ref, Opts2), - {ok, Pid}. +-spec start_listener(listener_options()) -> ok. +start_listener(Opts = #{proto := tcp}) -> + ListenerId = mongoose_listener_config:listener_id(Opts), + Ref = ref(ListenerId), + ChildSpec = #{id => ListenerId, + start => {?MODULE, start_link, [#cowboy_state{ref = Ref, opts = Opts}]}, + restart => transient, + shutdown => infinity, + modules => [?MODULE]}, + mongoose_listener_sup:start_child(ChildSpec), + {ok, _} = start_cowboy(Ref, Opts), + ok. reload_dispatch(Ref) -> gen_server:call(Ref, reload_dispatch). -%% @doc gen_server for handling shutdown when started via ejabberd_listener +%% @doc gen_server for handling shutdown when started via mongoose_listener -spec start_link(_) -> 'ignore' | {'error', _} | {'ok', pid()}. start_link(State) -> gen_server:start_link(?MODULE, State, []). @@ -131,33 +142,33 @@ execute(Req, Env) -> %% Internal Functions %%-------------------------------------------------------------------- +-spec start_cowboy(atom(), listener_options()) -> {ok, pid()} | {error, any()}. start_cowboy(Ref, Opts) -> - {Retries, SleepTime} = proplists:get_value(retries, Opts, {20, 50}), - do_start_cowboy(Ref, Opts, Retries, SleepTime). - + start_cowboy(Ref, Opts, 20, 50). -do_start_cowboy(Ref, Opts, 0, _) -> +-spec start_cowboy(atom(), listener_options(), + Retries :: non_neg_integer(), SleepTime :: non_neg_integer()) -> + {ok, pid()} | {error, any()}. +start_cowboy(Ref, Opts, 0, _) -> do_start_cowboy(Ref, Opts); -do_start_cowboy(Ref, Opts, Retries, SleepTime) -> +start_cowboy(Ref, Opts, Retries, SleepTime) -> case do_start_cowboy(Ref, Opts) of {error, eaddrinuse} -> timer:sleep(SleepTime), - do_start_cowboy(Ref, Opts, Retries - 1, SleepTime); + start_cowboy(Ref, Opts, Retries - 1, SleepTime); Other -> Other end. +-spec do_start_cowboy(atom(), listener_options()) -> {ok, pid()} | {error, any()}. do_start_cowboy(Ref, Opts) -> - SSLOpts = proplists:get_value(ssl, Opts), - NumAcceptors = proplists:get_value(num_acceptors, Opts, 100), - TransportOpts0 = proplists:get_value(transport_options, Opts, #{}), - TransportOpts = TransportOpts0#{num_acceptors => NumAcceptors}, - Modules = proplists:get_value(modules, Opts), + #{ip_tuple := IPTuple, port := Port, handlers := Modules, + transport := TransportOpts0, protocol := ProtocolOpts0} = Opts, Dispatch = cowboy_router:compile(get_routes(Modules)), - ProtocolOpts = [{env, [{dispatch, Dispatch}]} | - proplists:get_value(protocol_options, Opts, [])], + ProtocolOpts = ProtocolOpts0#{env => #{dispatch => Dispatch}}, + TransportOpts = TransportOpts0#{socket_opts => [{port, Port}, {ip, IPTuple}]}, ok = trails_store(Modules), - case catch start_http_or_https(SSLOpts, Ref, TransportOpts, ProtocolOpts) of + case catch start_http_or_https(Opts, Ref, TransportOpts, ProtocolOpts) of {error, {{shutdown, {failed_to_start_child, ranch_acceptors_sup, {{badmatch, {error, eaddrinuse}}, _ }}}, _}} -> @@ -166,25 +177,21 @@ do_start_cowboy(Ref, Opts) -> Result end. -start_http_or_https(undefined, Ref, TransportOpts, ProtocolOpts) -> - cowboy_start_http(Ref, TransportOpts, ProtocolOpts); -start_http_or_https(SSLOpts, Ref, TransportOpts, ProtocolOpts) -> +start_http_or_https(#{tls := SSLOpts}, Ref, TransportOpts, ProtocolOpts) -> SSLOptsWithVerifyFun = maybe_set_verify_fun(SSLOpts), - FilteredSSLOptions = filter_options(ignored_ssl_options(), SSLOptsWithVerifyFun), - SocketOptsWithSSL = maps:get(socket_opts, TransportOpts) ++ FilteredSSLOptions, - cowboy_start_https(Ref, TransportOpts#{socket_opts => SocketOptsWithSSL}, ProtocolOpts). + SocketOptsWithSSL = maps:get(socket_opts, TransportOpts) ++ SSLOptsWithVerifyFun, + cowboy_start_https(Ref, TransportOpts#{socket_opts := SocketOptsWithSSL}, ProtocolOpts); +start_http_or_https(#{}, Ref, TransportOpts, ProtocolOpts) -> + cowboy_start_http(Ref, TransportOpts, ProtocolOpts). cowboy_start_http(Ref, TransportOpts, ProtocolOpts) -> - ProtoOpts = add_common_middleware(make_env_map(maps:from_list(ProtocolOpts))), + ProtoOpts = add_common_middleware(ProtocolOpts), cowboy:start_clear(Ref, TransportOpts, ProtoOpts). cowboy_start_https(Ref, TransportOpts, ProtocolOpts) -> - ProtoOpts = add_common_middleware(make_env_map(maps:from_list(ProtocolOpts))), + ProtoOpts = add_common_middleware(ProtocolOpts), cowboy:start_tls(Ref, TransportOpts, ProtoOpts). -make_env_map(Map = #{ env := Env }) -> - Map#{ env => maps:from_list(Env) }. - % We need to insert our middleware just before `cowboy_handler`, % so the injected response header is taken into account. add_common_middleware(Map = #{ middlewares := Middlewares }) -> @@ -193,8 +200,8 @@ add_common_middleware(Map = #{ middlewares := Middlewares }) -> add_common_middleware(Map) -> Map#{ middlewares => [cowboy_router, ?MODULE, cowboy_handler] }. -reload_dispatch(Ref, Opts) -> - Dispatch = cowboy_router:compile(get_routes(proplists:get_value(modules, Opts))), +reload_dispatch(Ref, #{handlers := Modules}) -> + Dispatch = cowboy_router:compile(get_routes(Modules)), cowboy:set_env(Ref, dispatch, Dispatch). stop_cowboy(Ref) -> @@ -249,41 +256,13 @@ ensure_loaded_module(Module) -> reason => Other}) end. -ignored_ssl_options() -> - %% these options should be ignored if they creep into ssl section - [port, ip, max_connections, verify_mode]. - -filter_options(IgnoreOpts, [Opt | Opts]) when is_atom(Opt) -> - case lists:member(Opt, IgnoreOpts) of - true -> filter_options(IgnoreOpts, Opts); - false -> [Opt | filter_options(IgnoreOpts, Opts)] - end; -filter_options(IgnoreOpts, [Opt | Opts]) when tuple_size(Opt) >= 1 -> - case lists:member(element(1, Opt), IgnoreOpts) of - true -> filter_options(IgnoreOpts, Opts); - false -> [Opt | filter_options(IgnoreOpts, Opts)] - end; -filter_options(_, []) -> - []. - maybe_set_verify_fun(SSLOptions) -> - case proplists:get_value(verify_mode, SSLOptions) of - undefined -> + case lists:keytake(verify_mode, 1, SSLOptions) of + false -> SSLOptions; - Mode -> + {value, {_, Mode}, SSLOptions1} -> Fun = just_tls:verify_fun(Mode), - lists:keystore(verify_fun, 1, SSLOptions, {verify_fun, Fun}) - end. - -% This functions is for backward compatibility, as previous default config -% used max_connections tuple for all ejabberd_cowboy listeners -maybe_insert_max_connections(TransportOpts, Opts) -> - Key = max_connections, - case proplists:get_value(Key, Opts) of - undefined -> - TransportOpts; - Value -> - TransportOpts#{Key => Value} + [{verify_fun, Fun} | SSLOptions1] end. %% ------------------------------------------------------------------- diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl deleted file mode 100644 index ba58dbe0f2b..00000000000 --- a/src/ejabberd_listener.erl +++ /dev/null @@ -1,171 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_listener.erl -%%% Author : Alexey Shchepin -%%% Purpose : Manage socket listener -%%% Created : 16 Nov 2002 by Alexey Shchepin -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2011 ProcessOne -%%% -%%% This program is free software; you can redistribute it and/or -%%% modify it under the terms of the GNU General Public License as -%%% published by the Free Software Foundation; either version 2 of the -%%% License, or (at your option) any later version. -%%% -%%% This program is distributed in the hope that it will be useful, -%%% but WITHOUT ANY WARRANTY; without even the implied warranty of -%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -%%% General Public License for more details. -%%% -%%% You should have received a copy of the GNU General Public License -%%% along with this program; if not, write to the Free Software -%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_listener). --author('alexey@process-one.net'). - --export([start_link/0, - init/1, - start_listeners/0, - start_listener/1, - stop_listeners/0, - stop_listener/1 - ]). - -%% Internal --export([format_error/1, socket_error/6]). - --ignore_xref([start_link/0, init/1, start_listener/1, stop_listener/1]). --export_type([opts/0, mod/0]). - --type opts() :: list(). --type mod() :: module(). - --include("mongoose.hrl"). - --spec start_link() -> 'ignore' | {'error', _} | {'ok', pid()}. -start_link() -> - supervisor:start_link({local, ejabberd_listeners}, ?MODULE, []). - - -init(_) -> - %bind_tcp_ports(), - {ok, {{one_for_one, 10, 1}, []}}. - - --spec start_listeners() -> {'ok', {{_, _, _}, [any()]}}. -start_listeners() -> - Ls = mongoose_config:get_opt(listen), - Ls2 = lists:map( - fun(Listener) -> - case start_listener(Listener) of - {ok, _Pid} = R -> R; - {error, Error} -> - throw(Error) - end - end, Ls), - {ok, {{one_for_one, 10, 1}, Ls2}}. - --spec start_listener(mongoose_listener_config:listener()) -> {'error', pid()} | {'ok', _}. -start_listener(Opts = #{module := Module}) -> - try - %% It is only required to start the supervisor in some cases. - %% But it doesn't hurt to attempt to start it for any listener. - %% So, it's normal (and harmless) that in most cases this call returns: - %% {error, {already_started, pid()}} - start_module_sup(Module), - start_listener_sup(Module, Opts) - of - {ok, _Pid} = R -> R; - {error, {already_started, Pid}} -> - {ok, Pid}; - {error, Reason} = R -> - ?LOG_CRITICAL(#{what => listener_failed_to_start, reason => Reason, - text => <<"Failed to start a listener">>, - module => Module, opts => Opts}), - R - catch Class:Reason:Stacktrace -> - ?LOG_CRITICAL(#{what => listener_failed_to_start, - text => <<"Failed to start a listener">>, - module => Module, opts => Opts, - class => Class, reason => Reason, stacktrace => Stacktrace}), - {error, Reason} - end. - --spec start_module_sup(Module :: module()) - -> {'error', _} | {'ok', 'undefined' | pid()} | {'ok', 'undefined' | pid(), _}. -start_module_sup(Module) -> - Proc = gen_mod:get_module_proc("sup", Module), - ChildSpec = - {Proc, - {ejabberd_tmp_sup, start_link, [Proc, Module]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, - %% TODO Rewrite using ejabberd_sup:start_child - %% This function is called more than once - supervisor:start_child(ejabberd_sup, ChildSpec). - --spec start_listener_sup(module(), mongoose_listener_config:listener()) - -> {'error', _} | {'ok', 'undefined' | pid()} | {'ok', 'undefined' | pid(), _}. -start_listener_sup(Module, Listener = #{ip_address := IPAddr, port := Port, proto := Proto}) -> - ListenerId = mongoose_listener_config:listener_id(Listener), - Opts = mongoose_listener_config:prepare_opts(Listener), - case Module:socket_type() of - independent -> - Module:start_listener(ListenerId, Opts); - _ -> - SockOpts = mongoose_listener_config:filter_socket_opts(Opts), - {SupModule, Kill, Type} = - case Proto of - udp -> {mongoose_udp_listener, brutal_kill, worker}; - _ -> {mongoose_tcp_listener, 1000, supervisor} - end, - ChildSpec = {ListenerId, - {SupModule, start_link, - [ListenerId, Module, Opts, SockOpts, Port, IPAddr]}, - transient, Kill, Type, [SupModule]}, - supervisor:start_child(ejabberd_listeners, ChildSpec) - end. - --spec stop_listeners() -> 'ok'. -stop_listeners() -> - Listeners = mongoose_config:get_opt(listen), - lists:foreach(fun stop_listener/1, Listeners). - --spec stop_listener(mongoose_listener_config:listener()) - -> 'ok' | {'error', 'not_found' | 'restarting' | 'running' | 'simple_one_for_one'}. -stop_listener(Listener) -> - ListenerId = mongoose_listener_config:listener_id(Listener), - supervisor:terminate_child(ejabberd_listeners, ListenerId), - supervisor:delete_child(ejabberd_listeners, ListenerId). - -%%% -%%% Check options -%%% - -socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) -> - ReasonT = case Reason of - eaddrnotavail -> - "IP address not available: " ++ IPS; - eaddrinuse -> - "IP address and port number already used: " - ++IPS++" "++integer_to_list(Port); - _ -> - format_error(Reason) - end, - ?LOG_ERROR(#{what => failed_to_open_socket, reason => ReasonT, - port => Port, module => Module, socket_option => SockOpts}), - throw({Reason, PortIP}). - --spec format_error(atom()) -> string(). -format_error(Reason) -> - case inet:format_error(Reason) of - "unknown POSIX error" -> - atom_to_list(Reason); - ReasonStr -> - ReasonStr - end. diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 5fdd3f457f8..7a083e9f357 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -58,6 +58,10 @@ hibernate_after = 0 :: non_neg_integer()}). -type state() :: #state{}. +-type options() :: #{max_stanza_size := pos_integer() | infinity, + hibernate_after := non_neg_integer(), + atom() => any()}. + %%==================================================================== %% API %%==================================================================== @@ -65,19 +69,15 @@ %% Function: start_link() -> {ok, Pid} | ignore | {error, Error} %% Description: Starts the server %%-------------------------------------------------------------------- --spec start_link(_, _, _, _) -> 'ignore' | {'error', _} | {'ok', pid()}. -start_link(Socket, SockMod, Shaper, ConnOpts) -> - gen_server:start_link( - ?MODULE, [Socket, SockMod, Shaper, ConnOpts], []). - -%%-------------------------------------------------------------------- -%% Function: start() -> {ok, Pid} | ignore | {error, Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- -start(Socket, SockMod, Shaper, ConnOpts) -> - {ok, Pid} = supervisor:start_child( - ejabberd_receiver_sup, - [Socket, SockMod, Shaper, ConnOpts]), +-spec start_link(port(), ejabberd:sockmod(), atom(), options()) -> + ignore | {error, _} | {ok, pid()}. +start_link(Socket, SockMod, Shaper, Opts) -> + gen_server:start_link(?MODULE, [Socket, SockMod, Shaper, Opts], []). + +-spec start(port(), ejabberd:sockmod(), atom(), options()) -> pid(). +start(Socket, SockMod, Shaper, Opts) -> + {ok, Pid} = supervisor:start_child(ejabberd_receiver_sup, + [Socket, SockMod, Shaper, Opts]), Pid. -spec change_shaper(atom() | pid() | {atom(), _} | {'via', _, _}, _) -> 'ok'. @@ -112,24 +112,13 @@ close(Pid) -> %% Description: Initiates the server %%-------------------------------------------------------------------- -spec init([any(), ...]) -> {'ok', state()}. -init([Socket, SockMod, Shaper, ConnOpts]) -> +init([Socket, SockMod, Shaper, Opts]) -> ShaperState = shaper:new(Shaper), + #{max_stanza_size := MaxStanzaSize, hibernate_after := HibernateAfter} = Opts, Timeout = case SockMod of - ssl -> - 20; - _ -> - infinity + ssl -> 20; + _ -> infinity end, - MaxStanzaSize = - case lists:keyfind(max_stanza_size, 1, ConnOpts) of - {_, Size} -> Size; - _ -> infinity - end, - HibernateAfter = - case lists:keyfind(hibernate_after, 1, ConnOpts) of - {_, HA} -> HA; - _ -> 0 - end, {ok, #state{socket = Socket, sock_mod = SockMod, shaper_state = ShaperState, diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 2c058bd523f..955ee2bdc53 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -26,12 +26,16 @@ -module(ejabberd_s2s_in). -author('alexey@process-one.net'). -behaviour(gen_fsm_compat). +-behaviour(mongoose_listener). + +%% mongoose_listener API +-export([socket_type/0, + start_listener/1]). %% External exports -export([start/2, start_link/2, - match_domain/2, - socket_type/0]). + match_domain/2]). %% gen_fsm callbacks -export([init/1, @@ -99,21 +103,26 @@ "id='", (StateData#state.streamid)/binary, "'", Version/binary, ">">>) ). +-type socket_data() :: {ejabberd:sockmod(), term()}. +-type options() :: #{shaper := atom(), atom() => any()}. + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- --spec start(_, _) -> {'error', _} - | {'ok', 'undefined' | pid()} - | {'ok', 'undefined' | pid(), _}. +-spec start(socket_data(), options()) -> + {error, _} | {ok, undefined | pid()} | {ok, undefined | pid(), _}. start(SockData, Opts) -> ?SUPERVISOR_START. - --spec start_link(_, _) -> 'ignore' | {'error', _} | {'ok', pid()}. +-spec start_link(socket_data(), options()) -> ignore | {error, _} | {ok, pid()}. start_link(SockData, Opts) -> gen_fsm_compat:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS). +-spec start_listener(options()) -> ok. +start_listener(Opts) -> + mongoose_tcp_listener:start_listener(Opts). +-spec socket_type() -> mongoose_listener:socket_type(). socket_type() -> xml_stream. @@ -128,21 +137,12 @@ socket_type() -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- --spec init(_) -> {'ok', 'wait_for_stream', state()}. -init([{SockMod, Socket}, Opts]) -> +-spec init([socket_data() | options(), ...]) -> {ok, wait_for_stream, state()}. +init([{SockMod, Socket}, Opts = #{shaper := Shaper}]) -> ?LOG_DEBUG(#{what => s2n_in_started, text => <<"New incoming S2S connection">>, sockmod => SockMod, socket => Socket}), - Shaper = case lists:keysearch(shaper, 1, Opts) of - {value, {_, S}} -> S; - _ -> none - end, - TLSOpts = lists:filter(fun({protocol_options, _}) -> true; - ({dhfile, _}) -> true; - ({cafile, _}) -> true; - ({ciphers, _}) -> true; - (_) -> false - end, Opts), + TLSOpts = maps:get(tls, Opts, []), Timer = erlang:start_timer(ejabberd_s2s:timeout(), self(), []), {ok, wait_for_stream, #state{socket = Socket, diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index c4c209b0f7b..72956d29e62 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -29,13 +29,17 @@ -behaviour(p1_fsm). -behaviour(mongoose_packet_handler). +-behaviour(mongoose_listener). + +%% mongoose_listener API +-export([socket_type/0, + start_listener/1]). %% External exports -export([start/2, start_link/2, send_text/2, - send_element/2, - socket_type/0]). + send_element/2]). %% gen_fsm callbacks -export([init/1, @@ -52,8 +56,8 @@ %% packet handler callback -export([process_packet/5]). --ignore_xref([print_state/1, send_element/2, send_text/2, socket_type/0, start_link/2, - stream_established/2, wait_for_handshake/2, wait_for_stream/2]). +-ignore_xref([print_state/1, send_element/2, send_text/2, start_listener/1, socket_type/0, + start_link/2, stream_established/2, wait_for_handshake/2, wait_for_stream/2]). -include("mongoose.hrl"). -include("jlib.hrl"). @@ -114,29 +118,43 @@ "">> ). +-type socket_data() :: {ejabberd:sockmod(), term()}. +-type options() :: #{access := atom(), + shaper_rule := atom(), + password := binary(), + check_from := boolean(), + hidden_components := boolean(), + conflict_behaviour := conflict_behaviour(), + atom() => any()}. + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- --spec start(_, _) -> {'error', _} | {'ok', 'undefined' | pid()} | {'ok', 'undefined' | pid(), _}. +-spec start(socket_data(), options()) -> + {error, _} | {ok, undefined | pid()} | {ok, undefined | pid(), _}. start(SockData, Opts) -> supervisor:start_child(ejabberd_service_sup, [SockData, Opts]). --spec start_link(_, list()) -> 'ignore' | {'error', _} | {'ok', pid()}. +-spec start_link(socket_data(), options()) -> ignore | {error, _} | {ok, pid()}. start_link(SockData, Opts) -> p1_fsm:start_link(ejabberd_service, [SockData, Opts], fsm_limit_opts(Opts) ++ ?FSMOPTS). - +-spec socket_type() -> mongoose_listener:socket_type(). socket_type() -> xml_stream. +-spec start_listener(options()) -> ok. +start_listener(Opts) -> + mongoose_tcp_listener:start_listener(Opts). + %%%---------------------------------------------------------------------- %%% mongoose_packet_handler callback %%%---------------------------------------------------------------------- -spec process_packet(Acc :: mongoose_acc:t(), From :: jid:jid(), To :: jid:jid(), - El :: exml:element(), #{pid := pid()}) -> mongoose_acc:t(). + El :: exml:element(), #{pid := pid()}) -> mongoose_acc:t(). process_packet(Acc, From, To, _El, #{pid := Pid}) -> Pid ! {route, From, To, Acc}, Acc. @@ -152,28 +170,16 @@ process_packet(Acc, From, To, _El, #{pid := Pid}) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- --spec init([list() | {atom() | tuple(), _}, ...]) -> {'ok', 'wait_for_stream', state()}. +-spec init([socket_data() | options(), ...]) -> {'ok', 'wait_for_stream', state()}. init([{SockMod, Socket}, Opts]) -> ?LOG_INFO(#{what => comp_started, text => <<"External service connected">>, socket => Socket}), - Access = case lists:keysearch(access, 1, Opts) of - {value, {_, A}} -> A; - _ -> all - end, - {password, Password} = lists:keyfind(password, 1, Opts), - Shaper = case lists:keysearch(shaper_rule, 1, Opts) of - {value, {_, S}} -> S; - _ -> none - end, - CheckFrom = case lists:keysearch(service_check_from, 1, Opts) of - {value, {_, CF}} -> CF; - _ -> true - end, + #{access := Access, shaper_rule := Shaper, password := Password, + check_from := CheckFrom, hidden_components := HiddenComponents, + conflict_behaviour := ConflictBehaviour} = Opts, SockMod:change_shaper(Socket, Shaper), SocketMonitor = SockMod:monitor(Socket), - HiddenComponents = proplists:get_value(hidden_components, Opts, false), - ConflictBehaviour = proplists:get_value(conflict_behaviour, Opts, disconnect), {ok, wait_for_stream, #state{socket = Socket, sockmod = SockMod, socket_monitor = SocketMonitor, @@ -441,18 +447,15 @@ new_id() -> component_host(#state{ host = undefined }) -> "undefined"; component_host(#state{ host = Host }) -> Host. --spec fsm_limit_opts(maybe_improper_list()) -> [{'max_queue', integer()}]. -fsm_limit_opts(Opts) -> - case lists:keysearch(max_fsm_queue, 1, Opts) of - {value, {_, N}} when is_integer(N) -> +-spec fsm_limit_opts(options()) -> [{max_queue, integer()}]. +fsm_limit_opts(#{max_fsm_queue := N}) -> + [{max_queue, N}]; +fsm_limit_opts(#{}) -> + case mongoose_config:lookup_opt(max_fsm_queue) of + {ok, N} -> [{max_queue, N}]; - _ -> - case mongoose_config:lookup_opt(max_fsm_queue) of - {ok, N} -> - [{max_queue, N}]; - {error, not_found} -> - [] - end + {error, not_found} -> + [] end. try_register_routes(StateData) -> diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index 0b1da51f6bf..43a6632d4a6 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -29,7 +29,7 @@ -behaviour(mongoose_transport). %% API --export([start/4, +-export([start/5, connect/3, connect/4, starttls/2, @@ -66,21 +66,23 @@ %% Function: %% Description: %%-------------------------------------------------------------------- --spec start(ejabberd_listener:mod(), ejabberd:sockmod(), - Socket :: port(), Opts :: ejabberd_listener:opts()) -> ok. -start(Module, SockMod, Socket, Opts) -> - case Module:socket_type() of +-spec start(module(), ejabberd:sockmod(), gen_tcp:socket(), + mongoose_tcp_listener:options(), + mongoose_tcp_listener:connection_details()) -> ok. +start(Module, SockMod, Socket, Opts, ConnectionDetails) -> + case mongoose_listener:socket_type(Module) of xml_stream -> - start_xml_stream(Module, SockMod, Socket, Opts); + start_xml_stream(Module, SockMod, Socket, Opts, ConnectionDetails); independent -> ok; raw -> - start_raw_stream(Module, SockMod, Socket, Opts) + start_raw_stream(Module, SockMod, Socket, Opts, ConnectionDetails) end. --spec start_raw_stream(ejabberd_listener:mod(), ejabberd:sockmod(), - Socket :: port(), Opts :: ejabberd_listener:opts()) -> ok. -start_raw_stream(Module, SockMod, Socket, Opts) -> +-spec start_raw_stream(module(), ejabberd:sockmod(), + Socket :: port(), mongoose_tcp_listener:options(), + mongoose_tcp_listener:connection_details()) -> ok. +start_raw_stream(Module, SockMod, Socket, Opts, _ConnectionDetails) -> case Module:start({SockMod, Socket}, Opts) of {ok, Pid} -> case SockMod:controlling_process(Socket, Pid) of @@ -94,8 +96,9 @@ start_raw_stream(Module, SockMod, Socket, Opts) -> end. -spec start_xml_stream(atom() | tuple(), ejabberd:sockmod(), - Socket :: port(), Opts :: [{atom(), _}]) -> ok. -start_xml_stream(Module, SockMod, Socket, Opts) -> + Socket :: port(), mongoose_tcp_listener:options(), + mongoose_tcp_listener:connection_details()) -> ok. +start_xml_stream(Module, SockMod, Socket, Opts, ConnectionDetails) -> {ReceiverMod, Receiver, RecRef} = case catch SockMod:custom_receiver(Socket) of {receiver, RecMod, RecPid} -> @@ -104,11 +107,6 @@ start_xml_stream(Module, SockMod, Socket, Opts) -> RecPid = ejabberd_receiver:start(Socket, SockMod, none, Opts), {ejabberd_receiver, RecPid, RecPid} end, - ConnectionDetails = - case lists:keyfind(connection_details, 1, Opts) of - {_, CD} -> CD; - _ -> throw(connection_details_not_available) - end, SocketData = #socket_state{sockmod = SockMod, socket = Socket, receiver = RecRef, @@ -155,7 +153,10 @@ connect(Addr, Port, Opts) -> connect(Addr, Port, Opts, Timeout) -> case gen_tcp:connect(Addr, Port, Opts, Timeout) of {ok, Socket} -> - Receiver = ejabberd_receiver:start(Socket, gen_tcp, none, Opts), + %% Receiver options are configurable only for listeners + %% It might make sense to make them configurable for outgoing s2s connections as well + ReceiverOpts = #{max_stanza_size => infinity, hibernate_after => 0}, + Receiver = ejabberd_receiver:start(Socket, gen_tcp, none, ReceiverOpts), {SrcAddr, SrcPort} = case inet:sockname(Socket) of {ok, {A, P}} -> {A, P}; {error, _} -> {unknown, unknown} diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 49ccf11d2d9..7dd9e230dae 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -73,12 +73,12 @@ init([]) -> worker, [ejabberd_local]}, Listener = - {ejabberd_listener, - {ejabberd_listener, start_link, []}, + {mongoose_listener_sup, + {mongoose_listener_sup, start_link, []}, permanent, infinity, supervisor, - [ejabberd_listener]}, + [mongoose_listener_sup]}, ReceiverSupervisor = {ejabberd_receiver_sup, {ejabberd_tmp_sup, start_link, diff --git a/src/mod_bosh_socket.erl b/src/mod_bosh_socket.erl index 548025fc12c..a30756eff24 100644 --- a/src/mod_bosh_socket.erl +++ b/src/mod_bosh_socket.erl @@ -192,7 +192,7 @@ get_cached_responses(Pid) -> %%-------------------------------------------------------------------- init([HostType, Sid, Peer, PeerCert]) -> BoshSocket = #bosh_socket{sid = Sid, pid = self(), peer = Peer, peercert = PeerCert}, - C2SOpts = [{xml_socket, true}], + C2SOpts = #{access => all, shaper => none, xml_socket => true, hibernate_after => 0}, {ok, C2SPid} = ejabberd_c2s:start({mod_bosh_socket, BoshSocket}, C2SOpts), Opts = gen_mod:get_loaded_module_opts(HostType, mod_bosh), State = new_state(Sid, C2SPid, Opts), diff --git a/src/mod_websockets.erl b/src/mod_websockets.erl index 73cc4d5af6e..7e70a579c7f 100644 --- a/src/mod_websockets.erl +++ b/src/mod_websockets.erl @@ -195,15 +195,15 @@ send_to_fsm(FSM, StreamElement) -> maybe_start_fsm([#xmlstreamstart{ name = <<"stream", _/binary>>, attrs = Attrs} | _], #ws_state{fsm_pid = undefined, opts = Opts}=State) -> - case lists:keyfind(<<"xmlns">>, 1, Attrs) of - {<<"xmlns">>, ?NS_COMPONENT} -> - ServiceOpts = proplists:get_value(ejabberd_service, Opts, []), + case {lists:keyfind(<<"xmlns">>, 1, Attrs), proplists:get_value(service, Opts)} of + {{<<"xmlns">>, ?NS_COMPONENT}, #{} = ServiceOpts} -> do_start_fsm(ejabberd_service, ServiceOpts, State); _ -> {stop, State} end; maybe_start_fsm([#xmlel{ name = <<"open">> }], - #ws_state{fsm_pid = undefined, opts = Opts}=State) -> + #ws_state{fsm_pid = undefined} = State) -> + Opts = #{access => all, shaper => none, xml_socket => true, hibernate_after => 0}, do_start_fsm(ejabberd_c2s, Opts, State); maybe_start_fsm(_Els, State) -> {ok, State}. @@ -212,8 +212,7 @@ do_start_fsm(FSMModule, Opts, State = #ws_state{peer = Peer, peercert = PeerCert SocketData = #websocket{pid = self(), peername = Peer, peercert = PeerCert}, - Opts1 = [{xml_socket, true} | Opts], - case call_fsm_start(FSMModule, SocketData, Opts1) of + case call_fsm_start(FSMModule, SocketData, Opts) of {ok, Pid} -> ?LOG_DEBUG(#{what => ws_c2s_started, text => <<"WebSockets starts c2s process">>, diff --git a/src/mongoose_api_common.erl b/src/mongoose_api_common.erl index b8e077874b0..1c2d53d55f0 100644 --- a/src/mongoose_api_common.erl +++ b/src/mongoose_api_common.erl @@ -87,7 +87,7 @@ reload_dispatches(drop) -> drop; reload_dispatches(_Command) -> - Listeners = supervisor:which_children(ejabberd_listeners), + Listeners = supervisor:which_children(mongoose_listener_sup), CowboyListeners = [Child || {_Id, Child, _Type, [ejabberd_cowboy]} <- Listeners], [ejabberd_cowboy:reload_dispatch(Child) || Child <- CowboyListeners], drop. diff --git a/src/mongoose_credentials.erl b/src/mongoose_credentials.erl index bde448c88a4..2ec107ce22d 100644 --- a/src/mongoose_credentials.erl +++ b/src/mongoose_credentials.erl @@ -29,14 +29,11 @@ -type opts() :: #{}. --spec make_opts(ejabberd_listener:opts()) -> opts(). -make_opts(Opts) -> - case lists:keyfind(allowed_auth_methods, 1, Opts) of - {allowed_auth_methods, Methods} -> - #{allowed_modules => ejabberd_auth:methods_to_modules(Methods)}; - _ -> - #{} - end. +-spec make_opts(mongoose_listener:options()) -> opts(). +make_opts(#{allowed_auth_methods := Methods}) -> + #{allowed_modules => ejabberd_auth:methods_to_modules(Methods)}; +make_opts(#{}) -> + #{}. -spec new(jid:lserver(), binary(), opts()) -> mongoose_credentials:t(). new(LServer, HostType, Opts) when is_binary(LServer), is_binary(HostType) -> diff --git a/src/mongoose_listener.erl b/src/mongoose_listener.erl new file mode 100644 index 00000000000..7d3117e083e --- /dev/null +++ b/src/mongoose_listener.erl @@ -0,0 +1,58 @@ +%% @doc Manage starting and stopping of configured listeners + +-module(mongoose_listener). + +-include("mongoose.hrl"). + +%% Only for tests +-export([start_listener/1, stop_listener/1]). +-ignore_xref([start_listener/1, stop_listener/1]). + +%% API +-export([start/0, stop/0, socket_type/1]). + +-callback start_listener(options()) -> ok. +-callback socket_type() -> socket_type(). + +-type options() :: #{port := inet:port_number(), + ip_tuple := inet:ip_address(), + ip_address := string(), + ip_version := 4 | 6, + proto := proto(), + any() => any()}. +-type id() :: {inet:port_number(), inet:ip_address(), proto()}. +-type proto() :: tcp. +-type socket_type() :: independent | xml_stream | raw. + +-export_type([options/0, id/0, proto/0, socket_type/0]). + +%% API + +start() -> + lists:foreach(fun start_listener/1, mongoose_config:get_opt(listen)). + +stop() -> + lists:foreach(fun stop_listener/1, mongoose_config:get_opt(listen)). + +-spec socket_type(module()) -> any(). +socket_type(Module) -> + Module:socket_type(). + +%% Internal functions + +start_listener(Opts = #{module := Module}) -> + try + Module:start_listener(Opts) % This function should call mongoose_listener_sup:start_child/1 + catch + Class:Reason:Stacktrace -> + ?LOG_CRITICAL(#{what => listener_failed_to_start, + text => <<"Failed to start a listener">>, + module => Module, opts => Opts, + class => Class, reason => Reason, stacktrace => Stacktrace}), + erlang:raise(Class, Reason, Stacktrace) + end. + +stop_listener(Opts) -> + ListenerId = mongoose_listener_config:listener_id(Opts), + supervisor:terminate_child(mongoose_listener_sup, ListenerId), + supervisor:delete_child(mongoose_listener_sup, ListenerId). diff --git a/src/mongoose_listener_config.erl b/src/mongoose_listener_config.erl index df16b1a7f20..5171cff7efb 100644 --- a/src/mongoose_listener_config.erl +++ b/src/mongoose_listener_config.erl @@ -4,24 +4,12 @@ -export([ensure_ip_options/1, verify_unique_listeners/1, - prepare_opts/1, - filter_socket_opts/1, + address_family/1, listener_id/1]). --type listener() :: #{port := inet:port_number(), - ip_tuple := inet:ip_address(), - ip_address := string(), - ip_version := 4 | 6, - proto := proto(), - any() => any()}. --type listener_id() :: {inet:port_number(), inet:ip_address(), proto()}. --type proto() :: tcp | udp. - --export_type([listener/0, listener_id/0, proto/0]). - %% @doc Fill in IP-related options that can be calculated automatically. %% Apart from these options, the input should be a complete listener configuration. --spec ensure_ip_options(map()) -> listener(). +-spec ensure_ip_options(map()) -> mongoose_listener:options(). ensure_ip_options(Opts = #{ip_address := IPAddr, ip_version := 4}) -> {ok, IPTuple} = inet:parse_ipv4_address(IPAddr), Opts#{ip_tuple => IPTuple}; @@ -41,7 +29,7 @@ ip_version(T) when tuple_size(T) =:= 4 -> 4; ip_version(T) when tuple_size(T) =:= 8 -> 6. %% @doc Verify that all listeners have unique socket addresses --spec verify_unique_listeners([listener()]) -> [listener()]. +-spec verify_unique_listeners([mongoose_listener:options()]) -> [mongoose_listener:options()]. verify_unique_listeners(Listeners) -> Counts = lists:foldl(fun(L, Cts) -> maps:update_with(listener_id(L), fun(Ct) -> Ct + 1 end, 1, Cts) @@ -52,29 +40,11 @@ verify_unique_listeners(Listeners) -> text => <<"Some listeners have duplicate listening socket addresses">>}) end. -%% @doc Convert listener configuration to options that can be passed to listener modules --spec prepare_opts(listener()) -> ejabberd_listener:opts(). -prepare_opts(Listener) -> - lists:flatmap(fun prepare_opt/1, maps:to_list(Listener)). - -prepare_opt({tls, Opts}) -> Opts; -prepare_opt({ip_version, 4}) -> [inet]; -prepare_opt({ip_version, 6}) -> [inet6]; -prepare_opt({ip_tuple, Val}) -> [{ip, Val}]; -prepare_opt(Opt) -> [Opt]. - -%% @doc Filter listener options, leaving only socket-related ones --spec filter_socket_opts(proplists:proplist()) -> proplists:proplist(). -filter_socket_opts(Opts) -> - lists:filter(fun filter_socket_opt/1, Opts). - -filter_socket_opt(inet) -> true; -filter_socket_opt(inet6) -> true; -filter_socket_opt({ip, _}) -> true; -filter_socket_opt({backlog, _}) -> true; -filter_socket_opt(_) -> false. +-spec address_family(4 | 6) -> inet:address_family(). +address_family(4) -> inet; +address_family(6) -> inet6. %% @doc Create a unique ID based on the listening socket address --spec listener_id(listener()) -> listener_id(). +-spec listener_id(mongoose_listener:options()) -> mongoose_listener:id(). listener_id(#{port := Port, ip_tuple := IPTuple, proto := Proto}) -> {Port, IPTuple, Proto}. diff --git a/src/mongoose_listener_sup.erl b/src/mongoose_listener_sup.erl new file mode 100644 index 00000000000..9191cdea91b --- /dev/null +++ b/src/mongoose_listener_sup.erl @@ -0,0 +1,28 @@ +%% @doc Supervisor for the socket listeners + +-module(mongoose_listener_sup). + +-behaviour(supervisor). + +-export([start_link/0, start_child/1, init/1]). + +-ignore_xref([start_link/0, init/1]). + +%% API + +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +-spec start_child(supervisor:child_spec()) -> ok. +start_child(ChildSpec) -> + {ok, _Pid} = supervisor:start_child(?MODULE, ChildSpec), + ok. + +%% Supervisor callbacks + +-spec init([]) -> {ok, {supervisor:sup_flags(), []}}. +init([]) -> + {ok, {#{strategy => one_for_one, + intensity => 10, + period => 1}, []}}. diff --git a/src/mongoose_tcp_listener.erl b/src/mongoose_tcp_listener.erl index 5bf8894ef03..67c2075eb35 100644 --- a/src/mongoose_tcp_listener.erl +++ b/src/mongoose_tcp_listener.erl @@ -24,12 +24,23 @@ %% We do not block on send anymore. -define(TCP_SEND_TIMEOUT, 15000). --export([start_link/6, init/1]). +-export([start_listener/1, start_link/1, init/1]). %% Internal --export([start_accept_loop/3, accept_loop/4]). +-export([start_accept_loop/3, accept_loop/3]). --ignore_xref([start_link/6, start_accept_loop/3]). +-ignore_xref([start_link/1, start_accept_loop/3]). + +-type options() :: #{module := module(), + port := inet:port_number(), + ip_tuple := inet:ip_address(), + ip_address := string(), + ip_version := 4 | 6, + proto := tcp, + num_acceptors := pos_integer(), + backlog := non_neg_integer(), + proxy_protocol := boolean(), + atom() => any()}. -type connection_details() :: #{ proxy := boolean(), @@ -39,33 +50,36 @@ dest_address := inet:ip_address() | binary(), dest_port := inet:port_number() }. --export_type([connection_details/0]). +-export_type([options/0, connection_details/0]). %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- --spec start_link(Id :: mongoose_listener_config:listener_id(), - Module :: atom(), - Opts :: [any(), ...], - SockOpts :: [gen_tcp:listen_option()], - Port :: inet:port_number(), - IPS :: [any()]) -> any(). -start_link(Id, Module, Opts, SockOpts, Port, IPS) -> - supervisor:start_link(?MODULE, {Id, Module, Opts, SockOpts, Port, IPS}). - --spec init({Id :: mongoose_listener_config:listener_id(), - Module :: atom(), - Opts :: [any(), ...], - SockOpts :: [gen_tcp:listen_option()], - Port :: inet:port_number(), - IPS :: [any()]}) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. -init({Id, Module, Opts, SockOpts, Port, IPS}) -> +-spec start_listener(options()) -> ok. +start_listener(Opts = #{proto := tcp}) -> + ListenerId = mongoose_listener_config:listener_id(Opts), + mongoose_listener_sup:start_child(listener_child_spec(ListenerId, Opts)). + +listener_child_spec(ListenerId, Opts) -> + #{id => ListenerId, + start => {?MODULE, start_link, [Opts]}, + restart => permanent, + shutdown => 1000, + type => supervisor, + modules => [?MODULE]}. + +-spec start_link(options()) -> any(). +start_link(Opts) -> + supervisor:start_link(?MODULE, Opts). + +-spec init(options()) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init(Opts = #{module := Module, num_acceptors := NumAcceptors}) -> try - AcceptorsNum = proplists:get_value(acceptors_num, Opts, 100), - ListenSocket = listen_tcp(Id, Module, SockOpts, Port, IPS), + ListenSocket = listen_tcp(Opts), + Id = mongoose_listener_config:listener_id(Opts), Children = [make_childspec({Id, I}, ListenSocket, Module, Opts) - || I <- lists:seq(1, AcceptorsNum)], + || I <- lists:seq(1, NumAcceptors)], {ok, {#{strategy => one_for_one, intensity => 100, period => 1}, Children}} catch Error -> exit(Error) @@ -76,31 +90,29 @@ init({Id, Module, Opts, SockOpts, Port, IPS}) -> %%-------------------------------------------------------------------- -spec start_accept_loop(Socket :: port(), - Module :: ejabberd_listener:mod(), - Opts :: ejabberd_listener:opts()) -> {ok, pid()}. + Module :: module(), + Opts :: options()) -> {ok, pid()}. start_accept_loop(ListenSock, Module, Opts) -> - ProxyProtocol = proplists:get_value(proxy_protocol, Opts, false), - Pid = proc_lib:spawn_link(?MODULE, accept_loop, [ListenSock, Module, Opts, ProxyProtocol]), + Pid = proc_lib:spawn_link(?MODULE, accept_loop, [ListenSock, Module, Opts]), {ok, Pid}. -spec accept_loop(Socket :: port(), - Module :: ejabberd_listener:mod(), - Opts :: ejabberd_listener:opts(), - ProxyProtocol :: boolean()) -> no_return(). -accept_loop(ListenSocket, Module, Opts, ProxyProtocol) -> + Module :: module(), + Opts :: options()) -> no_return(). +accept_loop(ListenSocket, Module, Opts = #{proxy_protocol := ProxyProtocol}) -> case do_accept(ListenSocket, ProxyProtocol) of {ok, Socket, ConnectionDetails} -> ?LOG_INFO(#{what => tcp_accepted, socket => Socket, handler_module => Module, conn_details => ConnectionDetails}), ejabberd_socket:start( - Module, gen_tcp, Socket, [{connection_details, ConnectionDetails} | Opts]), - ?MODULE:accept_loop(ListenSocket, Module, Opts, ProxyProtocol); + Module, gen_tcp, Socket, Opts, ConnectionDetails), + ?MODULE:accept_loop(ListenSocket, Module, Opts); {error, Reason} -> ?LOG_INFO(#{what => tcp_accept_failed, listen_socket => ListenSocket, reason => Reason, handler_module => Module}), - ?MODULE:accept_loop(ListenSocket, Module, Opts, ProxyProtocol) + ?MODULE:accept_loop(ListenSocket, Module, Opts) end. -spec do_accept(gen_tcp:socket(), boolean()) -> @@ -133,7 +145,7 @@ read_proxy_header(Socket) -> }}. -spec make_childspec(Id :: term(), ListenSock :: port(), - Module :: module(), Opts :: [any()]) -> + Module :: module(), Opts :: options()) -> supervisor:child_spec(). make_childspec(Id, ListenSock, Module, Opts) -> #{id => Id, @@ -143,30 +155,33 @@ make_childspec(Id, ListenSock, Module, Opts) -> type => worker, modules => [?MODULE]}. --spec listen_tcp(Id :: mongoose_listener_config:listener_id(), - Module :: atom(), - SockOpts :: [gen_tcp:listen_option()], - Port :: inet:port_number(), - IPS :: [any()]) -> port(). -listen_tcp(Id, Module, SockOpts, Port, IPS) -> - DefaultSockOpts = [binary, - {backlog, 100}, - {packet, 0}, - {active, false}, - {reuseaddr, true}, - {nodelay, true}, - {send_timeout, ?TCP_SEND_TIMEOUT}, - {keepalive, true}, - {send_timeout_close, true}], - FinalSockOpts = override_sock_opts(SockOpts, DefaultSockOpts), - Res = listen_or_retry(Port, FinalSockOpts, 10), +-spec listen_tcp(options()) -> port(). +listen_tcp(Opts = #{port := Port}) -> + SockOpts = prepare_socket_opts(Opts), + Res = listen_or_retry(Port, SockOpts, 10), case Res of {ok, ListenSocket} -> ListenSocket; {error, Reason} -> - ejabberd_listener:socket_error(Reason, Id, Module, SockOpts, Port, IPS) + error(#{what => mongoose_tcp_listener_init_failed, + reason => Reason, + text => inet:format_error(Reason), + options => Opts}) end. +prepare_socket_opts(#{ip_version := IPVersion, ip_tuple := IPTuple, backlog := Backlog}) -> + [binary, + {packet, 0}, + {active, false}, + {reuseaddr, true}, + {nodelay, true}, + {send_timeout, ?TCP_SEND_TIMEOUT}, + {keepalive, true}, + {send_timeout_close, true}, + mongoose_listener_config:address_family(IPVersion), + {ip, IPTuple}, + {backlog, Backlog}]. + %% Process exit and socket release are not transactional %% So, there can be a short period of time when we can't bind listen_or_retry(Port, SockOpts, Retries) -> @@ -179,20 +194,3 @@ listen_or_retry(Port, SockOpts, Retries) -> {error, Reason} -> {error, Reason} end. - -override_sock_opts([], Opts) -> - Opts; -override_sock_opts([Override | OverrideOpts], Opts) -> - NewOpts = do_override(Override, Opts), - override_sock_opts(OverrideOpts, NewOpts). - -do_override({ip, _} = IP, Opts) -> - lists:keystore(ip, 1, Opts, IP); -do_override({backlog, _} = Backlog, Opts) -> - lists:keystore(backlog, 1, Opts, Backlog); -do_override(inet6, Opts) -> - [inet6 | lists:delete(inet6, Opts)]; -do_override(inet, Opts) -> - [inet | lists:delete(inet, Opts)]; -do_override(_, Opts) -> - Opts. diff --git a/src/mongoose_udp_listener.erl b/src/mongoose_udp_listener.erl deleted file mode 100644 index db02ffeaa67..00000000000 --- a/src/mongoose_udp_listener.erl +++ /dev/null @@ -1,92 +0,0 @@ -%%============================================================================== -%% Copyright 2018 Erlang Solutions Ltd. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%============================================================================== - --module(mongoose_udp_listener). --author('konrad.zemek@erlang-solutions.com'). - --include("mongoose.hrl"). - --type udp_listen_option() :: gen_udp:option() - | {ip, _} - | {fd, pos_integer()} - | {ifaddr, _} - | inet:address_family() - | {port, inet:port_number()}. - --export([start_link/6, init/6]). - -%% Internal --export([recv_loop/3]). - --ignore_xref([start_link/6, init/6]). - -%%-------------------------------------------------------------------- -%% API -%%-------------------------------------------------------------------- - --spec start_link(Id :: mongoose_listener_config:listener_id(), - Module :: atom(), - Opts :: [any(), ...], - SockOpts :: [udp_listen_option()], - Port :: inet:port_number(), - IPS :: [any()]) -> any(). -start_link(Id, Module, Opts, SockOpts, Port, IPS) -> - proc_lib:start_link(?MODULE, init, [Id, Module, Opts, SockOpts, Port, IPS]). - --spec init(Id :: mongoose_listener_config:listener_id(), - Module :: atom(), - Opts :: [any(), ...], - SockOpts :: [udp_listen_option()], - Port :: inet:port_number(), - IPS :: [any()]) -> no_return(). -init(Id, Module, Opts, SockOpts, Port, IPS) -> - case gen_udp:open(Port, [binary, {active, false}, {reuseaddr, true} - | SockOpts]) of - {ok, Socket} -> - %% Inform my parent that this port was opened succesfully - proc_lib:init_ack({ok, self()}), - recv_loop(Socket, Module, Opts); - {error, Reason} -> - ejabberd_listener:socket_error(Reason, Id, Module, SockOpts, Port, IPS) - end. - -%%-------------------------------------------------------------------- -%% Helpers -%%-------------------------------------------------------------------- - --spec recv_loop(Socket :: port(), - Module :: atom(), - Opts :: [any(), ...]) -> no_return(). -recv_loop(Socket, Module, Opts) -> - case gen_udp:recv(Socket, 0) of - {ok, {Addr, Port, Packet}} -> - try Module:udp_recv(Socket, Addr, Port, Packet, Opts) - catch Class:Reason:Stacktrace -> - ?LOG_ERROR(#{what => udp_listener_recv_failed, - text => <<"Failed to process UDP packet">>, - socket => Socket, handler_module => Module, - ip => Addr, port => Port, - class => Class, reason => Reason, - stacktrace => Stacktrace, udp_packet => Packet}) - end, - ?MODULE:recv_loop(Socket, Module, Opts); - {error, Reason} -> - ?LOG_ERROR(#{what => udp_listener_recv_failed, - text => <<"Unexpected UDP error">>, - socket => Socket, handler_module => Module, - reason => ejabberd_listener:format_error(Reason)}), - exit({error, Reason}) - end. diff --git a/src/system_metrics/mongoose_system_metrics_collector.erl b/src/system_metrics/mongoose_system_metrics_collector.erl index 0516234f3e4..06ddc60408a 100644 --- a/src/system_metrics/mongoose_system_metrics_collector.erl +++ b/src/system_metrics/mongoose_system_metrics_collector.erl @@ -143,7 +143,7 @@ get_transport_mechanisms() -> get_http_handler_modules() -> Listeners = get_listeners(ejabberd_cowboy), - Modules = lists:flatten([Modules || #{modules := Modules} <- Listeners]), + Modules = lists:flatten([Modules || #{handlers := Modules} <- Listeners]), % Modules Option can have variable number of elements. To be more % error-proof, extracting 3rd element instead of pattern matching. lists:usort(lists:map(fun(Module) -> element(3, Module) end, Modules)). diff --git a/test/commands_backend_SUITE.erl b/test/commands_backend_SUITE.erl index 5b7c32c357e..98c438cb302 100644 --- a/test/commands_backend_SUITE.erl +++ b/test/commands_backend_SUITE.erl @@ -3,17 +3,12 @@ -module(commands_backend_SUITE). -compile([export_all, nowarn_export_all]). --include_lib("exml/include/exml.hrl"). -include_lib("eunit/include/eunit.hrl"). --include("ejabberd_commands.hrl"). -define(PORT, 5288). -define(HOST, "localhost"). -define(IP, {127,0,0,1}). -%% Error messages --define(ARGS_LEN_ERROR, <<"Bad parameters length.">>). --define(ARGS_SPEC_ERROR, <<"Bad name of the parameter.">>). -type method() :: string(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%% suite configuration @@ -31,7 +26,7 @@ all() -> {group, get_advanced_backend}, {group, post_advanced_backend}, {group, delete_advanced_backend}, - {group, simple_client} + {group, simple_client} ]. groups() -> @@ -104,15 +99,18 @@ setup(Module) -> meck:expect(gen_hook, run_fold, fun(_, _, _, _) -> {ok, ok} end), spawn(fun mc_holder/0), meck:expect(supervisor, start_child, - fun(ejabberd_listeners, {_, {_, start_link, [_]}, transient, - infinity, worker, [_]}) -> {ok, self()}; - (A,B) -> meck:passthrough([A,B]) + fun(mongoose_listener_sup, _) -> {ok, self()}; + (A, B) -> meck:passthrough([A, B]) end), + mongoose_listener_sup:start_link(), %% HTTP API config - Opts = [{num_acceptors, 10}, - {max_connections, 1024}, - {modules, [{"localhost", "/api", Module, []}]}], - ejabberd_cowboy:start_listener({?PORT, ?IP, tcp}, Opts). + Opts = #{transport => #{num_acceptors => 10, max_connections => 1024}, + protocol => #{}, + port => ?PORT, + ip_tuple => ?IP, + proto => tcp, + handlers => [{"localhost", "/api", Module, []}]}, + ejabberd_cowboy:start_listener(Opts). teardown() -> cowboy:stop_listener(ejabberd_cowboy:ref({?PORT, ?IP, tcp})), diff --git a/test/common/config_parser_helper.erl b/test/common/config_parser_helper.erl index 3fc7748efec..f6e7bc26a0c 100644 --- a/test/common/config_parser_helper.erl +++ b/test/common/config_parser_helper.erl @@ -63,17 +63,16 @@ options("miscellaneous") -> {hosts, [<<"localhost">>, <<"anonymous.localhost">>]}, {language, <<"en">>}, {listen, - [#{ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, module => ejabberd_cowboy, - modules => - [{"_", "/ws-xmpp", mod_websockets, - [{ejabberd_service, [{access, all}, - {max_fsm_queue, 1000}, - {password, "secret"}, - {shaper_rule, fast}]}]}], - port => 5280, proto => tcp, - transport_options => [{max_connections, 1024}, {num_acceptors, 10}]}]}, + [config([listen, http], + #{port => 5280, + handlers => [{"_", "/ws-xmpp", mod_websockets, + [{service, maps:merge(extra_service_listener_config(), + #{password => "secret", + shaper_rule => fast, + max_fsm_queue => 1000})}] + }], + transport => #{num_acceptors => 10, max_connections => 1024} + })]}, {loglevel, warning}, {mongooseimctl_access_commands, [{local, ["join_cluster"], [{node, "mongooseim@prime"}]}]}, @@ -129,104 +128,113 @@ options("mongooseim-pgsql") -> [<<"localhost">>, <<"anonymous.localhost">>, <<"localhost.bis">>]}, {language, <<"en">>}, {listen, - [#{access => c2s, ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, max_stanza_size => 65536, module => ejabberd_c2s, - port => 5222, proto => tcp, shaper => c2s_shaper, - tls => [{certfile, "priv/dc1.pem"}, {dhfile, "priv/dh.pem"}, starttls], - zlib => 10000}, - #{access => c2s, ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, max_stanza_size => 65536, module => ejabberd_c2s, - port => 5223, proto => tcp, shaper => c2s_shaper, zlib => 4096}, - #{ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, module => ejabberd_cowboy, - modules => - [{"_", "/http-bind", mod_bosh, []}, - {"_", "/ws-xmpp", mod_websockets, - [{ejabberd_service, [{access, all}, - {password, "secret"}, - {shaper_rule, fast}]}]}], - port => 5280, proto => tcp, - transport_options => [{max_connections, 1024}, {num_acceptors, 10}]}, - #{ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, module => ejabberd_cowboy, - modules => - [{"_", "/http-bind", mod_bosh, []}, - {"_", "/ws-xmpp", mod_websockets, - [{max_stanza_size, 100}, - {ping_rate, 120000}, - {timeout, infinity}]}, - {"localhost", "/api", mongoose_api_admin, - [{auth, {<<"ala">>, <<"makotaipsa">>}}]}, - {"localhost", "/api/contacts/{:jid}", mongoose_api_client, []}], - port => 5285, proto => tcp, - ssl => - [{certfile, "priv/cert.pem"}, - {keyfile, "priv/dc1.pem"}, - {password, []}], - transport_options => [{max_connections, 1024}, {num_acceptors, 10}]}, - #{ip_address => "127.0.0.1", - ip_tuple => {127, 0, 0, 1}, - ip_version => 4, module => ejabberd_cowboy, - modules => [{"localhost", "/api", mongoose_api_admin, []}], - port => 8088, proto => tcp, - transport_options => [{max_connections, 1024}, {num_acceptors, 10}]}, - #{ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, module => ejabberd_cowboy, - modules => - [{"_", "/api-docs/[...]", cowboy_static, - {priv_dir, cowboy_swagger, "swagger", - [{mimetypes, cow_mimetypes, all}]}}, - {"_", "/api-docs/swagger.json", cowboy_swagger_json_handler, #{}}, - {"_", "/api-docs", cowboy_swagger_redirect_handler, #{}}, - {"_", "/api/sse", lasse_handler, [mongoose_client_api_sse]}, - {"_", "/api/contacts/[:jid]", mongoose_client_api_contacts, []}, - {"_", "/api/messages/[:with]", mongoose_client_api_messages, []}, - {"_", "/api/rooms/[:id]", mongoose_client_api_rooms, []}, - {"_", "/api/rooms/[:id]/config", - mongoose_client_api_rooms_config, []}, - {"_", "/api/rooms/[:id]/messages", - mongoose_client_api_rooms_messages, []}, - {"_", "/api/rooms/:id/users/[:user]", - mongoose_client_api_rooms_users, []}], - port => 8089, proto => tcp, - protocol_options => [{compress, true}], - ssl => - [{certfile, "priv/cert.pem"}, - {keyfile, "priv/dc1.pem"}, - {password, []}], - transport_options => [{max_connections, 1024}, {num_acceptors, 10}]}, - #{ip_address => "127.0.0.1", - ip_tuple => {127, 0, 0, 1}, - ip_version => 4, module => ejabberd_cowboy, - modules => - [{"localhost", "/api", mongoose_api, - [{handlers, [mongoose_api_metrics, mongoose_api_users]}]}], - port => 5288, proto => tcp, - transport_options => [{max_connections, 1024}, {num_acceptors, 10}]}, - #{ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, max_stanza_size => 131072, - module => ejabberd_s2s_in, port => 5269, proto => tcp, - shaper => s2s_shaper, - tls => [{dhfile, "priv/dh.pem"}]}, - #{access => all, ip_address => "127.0.0.1", - ip_tuple => {127, 0, 0, 1}, - ip_version => 4, module => ejabberd_service, password => "secret", - port => 8888, proto => tcp, shaper_rule => fast}, - #{access => all, conflict_behaviour => kick_old, - ip_address => "127.0.0.1", - ip_tuple => {127, 0, 0, 1}, - ip_version => 4, module => ejabberd_service, password => "secret", - port => 8666, proto => tcp, shaper_rule => fast}, - #{access => all, hidden_components => true, ip_address => "127.0.0.1", - ip_tuple => {127, 0, 0, 1}, - ip_version => 4, module => ejabberd_service, password => "secret", - port => 8189, proto => tcp, shaper_rule => fast}]}, + [config([listen, c2s], + #{port => 5222, + access => c2s, + shaper => c2s_shaper, + max_stanza_size => 65536, + zlib => 10000, + tls => [{certfile, "priv/dc1.pem"}, {dhfile, "priv/dh.pem"}, starttls] + }), + config([listen, c2s], + #{port => 5223, + access => c2s, + shaper => c2s_shaper, + max_stanza_size => 65536, + zlib => 4096 + }), + config([listen, http], + #{port => 5280, + handlers => [{"_", "/http-bind", mod_bosh, []}, + {"_", "/ws-xmpp", mod_websockets, + [{service, maps:merge(extra_service_listener_config(), + #{password => "secret", shaper_rule => fast})}] + }], + transport => #{num_acceptors => 10, max_connections => 1024} + }), + config([listen, http], + #{port => 5285, + handlers => [{"_", "/http-bind", mod_bosh, []}, + {"_", "/ws-xmpp", mod_websockets, + [{max_stanza_size, 100}, + {ping_rate, 120000}, + {timeout, infinity}]}, + {"localhost", "/api", mongoose_api_admin, + [{auth, {<<"ala">>, <<"makotaipsa">>}}]}, + {"localhost", "/api/contacts/{:jid}", mongoose_api_client, []}], + transport => #{num_acceptors => 10, max_connections => 1024}, + tls => [{certfile, "priv/cert.pem"}, {keyfile, "priv/dc1.pem"}, {password, []}] + }), + config([listen, http], + #{ip_address => "127.0.0.1", + ip_tuple => {127, 0, 0, 1}, + port => 8088, + transport => #{num_acceptors => 10, max_connections => 1024}, + handlers => [{"localhost", "/api", mongoose_api_admin, []}] + }), + config([listen, http], + #{port => 8089, + handlers => [{"_", "/api-docs/[...]", cowboy_static, + {priv_dir, cowboy_swagger, "swagger", + [{mimetypes, cow_mimetypes, all}]}}, + {"_", "/api-docs/swagger.json", cowboy_swagger_json_handler, #{}}, + {"_", "/api-docs", cowboy_swagger_redirect_handler, #{}}, + {"_", "/api/sse", lasse_handler, [mongoose_client_api_sse]}, + {"_", "/api/contacts/[:jid]", mongoose_client_api_contacts, []}, + {"_", "/api/messages/[:with]", mongoose_client_api_messages, []}, + {"_", "/api/rooms/[:id]", mongoose_client_api_rooms, []}, + {"_", "/api/rooms/[:id]/config", + mongoose_client_api_rooms_config, []}, + {"_", "/api/rooms/[:id]/messages", + mongoose_client_api_rooms_messages, []}, + {"_", "/api/rooms/:id/users/[:user]", + mongoose_client_api_rooms_users, []}], + protocol => #{compress => true}, + transport => #{num_acceptors => 10, max_connections => 1024}, + tls => [{certfile, "priv/cert.pem"}, {keyfile, "priv/dc1.pem"}, {password, []}] + }), + config([listen, http], + #{ip_address => "127.0.0.1", + ip_tuple => {127, 0, 0, 1}, + port => 5288, + transport => #{num_acceptors => 10, max_connections => 1024}, + handlers => + [{"localhost", "/api", mongoose_api, + [{handlers, [mongoose_api_metrics, mongoose_api_users]}]}] + }), + config([listen, s2s], + #{port => 5269, + shaper => s2s_shaper, + max_stanza_size => 131072, + tls => [{dhfile, "priv/dh.pem"}] + }), + config([listen, service], + #{ip_address => "127.0.0.1", + ip_tuple => {127, 0, 0, 1}, + port => 8888, + access => all, + shaper_rule => fast, + password => "secret" + }), + config([listen, service], + #{ip_address => "127.0.0.1", + ip_tuple => {127, 0, 0, 1}, + port => 8666, + access => all, + shaper_rule => fast, + password => "secret", + conflict_behaviour => kick_old + }), + config([listen, service], + #{ip_address => "127.0.0.1", + ip_tuple => {127, 0, 0, 1}, + port => 8189, + access => all, + shaper_rule => fast, + password => "secret", + hidden_components => true + }) + ]}, {loglevel, warning}, {max_fsm_queue, 1000}, {mongooseimctl_access_commands, []}, @@ -1045,6 +1053,46 @@ default_room_opts() -> subject => <<>>, subject_author => <<>>}. +common_xmpp_listener_config() -> + (common_listener_config())#{backlog => 100, + proxy_protocol => false, + hibernate_after => 0, + max_stanza_size => infinity, + num_acceptors => 100}. + +common_listener_config() -> + #{ip_address => "0", + ip_tuple => {0, 0, 0, 0}, + ip_version => 4, + proto => tcp}. + +extra_service_listener_config() -> + #{access => all, + shaper_rule => none, + check_from => true, + hidden_components => false, + conflict_behaviour => disconnect}. + +default_config([listen, http]) -> + (common_listener_config())#{module => ejabberd_cowboy, + transport => default_config([listen, http, transport]), + protocol => default_config([listen, http, protocol]), + handlers => []}; +default_config([listen, http, transport]) -> + #{num_acceptors => 100, + max_connections => 1024}; +default_config([listen, http, protocol]) -> + #{compress => false}; +default_config([listen, c2s]) -> + (common_xmpp_listener_config())#{module => ejabberd_c2s, + access => all, + shaper => none}; +default_config([listen, s2s]) -> + (common_xmpp_listener_config())#{module => ejabberd_s2s_in, + shaper => none}; +default_config([listen, service]) -> + Extra = maps:merge(common_xmpp_listener_config(), extra_service_listener_config()), + Extra#{module => ejabberd_service}; default_config([modules, M]) -> default_mod_config(M); default_config([modules, mod_event_pusher, http]) -> diff --git a/test/config_parser_SUITE.erl b/test/config_parser_SUITE.erl index 3340c2e3fc8..1eb959c5bac 100644 --- a/test/config_parser_SUITE.erl +++ b/test/config_parser_SUITE.erl @@ -35,6 +35,7 @@ -import(mongoose_config_parser_toml, [extract_errors/1]). -import(config_parser_helper, [merge_with_default_pool_config/1, default_s2s/0, + extra_service_listener_config/0, mod_event_pusher_http_handler/0, mod_config/2, default_mod_config/1, config/2, default_config/1]). @@ -81,38 +82,16 @@ groups() -> replaced_wait_timeout, hide_service_name, domain_certfile]}, - {listen, [parallel], [listen_portip, - listen_proto, - listen_duplicate, - listen_ip_version, - listen_backlog, - listen_proxy_protocol, - listen_num_acceptors, - listen_access, - listen_shaper, - listen_xml_socket, - listen_zlib, - listen_hibernate_after, - listen_max_fsm_queue, - listen_max_stanza_size, - listen_tls_mode, - listen_tls_module, - listen_tls_verify, - listen_tls_verify_mode, - listen_tls_crl_files, - listen_tls_certfile, - listen_tls_cacertfile, - listen_tls_dhfile, - listen_tls_ciphers, - listen_tls_versions, - listen_tls_protocol_options, - listen_check_from, - listen_hidden_components, - listen_conflict_behaviour, - listen_password, - listen_http_num_acceptors, - listen_http_max_connections, - listen_http_compress, + {listen, [parallel], [listen_duplicate, + listen_c2s, + listen_c2s_fast_tls, + listen_c2s_just_tls, + listen_s2s, + listen_s2s_tls, + listen_service, + listen_http, + listen_http_tls, + listen_http_transport, listen_http_handlers, listen_http_handlers_websockets, listen_http_handlers_lasse, @@ -487,376 +466,274 @@ domain_certfile(_Config) -> %% tests: listen -listen_portip(_Config) -> - ?cfg(listen, [], #{}), - ?cfg(listener_config(ejabberd_c2s, #{}), listen_raw(<<"c2s">>, #{})), - ?cfg(listener_config(ejabberd_c2s, #{ip_address => "192.168.1.16", - ip_tuple => {192, 168, 1, 16}}), - listen_raw(<<"c2s">>, #{<<"ip_address">> => <<"192.168.1.16">>})), - ?cfg(listener_config(ejabberd_c2s, #{ip_address => "2001:db8:3:4:5:6:7:8", - ip_tuple => {8193, 3512, 3, 4, 5, 6, 7, 8}, - ip_version => 6}), - listen_raw(<<"c2s">>, #{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})), - ?err(listen_raw(<<"c2s">>, #{<<"ip_address">> => <<"192.168.1.999">>})), - ?err(#{<<"listen">> => #{<<"c2s">> => [#{<<"ip_address">> => <<"192.168.1.16">>}]}}), - ?err(#{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => <<"5222">>}]}}), - ?err(#{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 522222}]}}). - -listen_proto(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{}), - listen_raw(<<"c2s">>, #{<<"proto">> => <<"tcp">>})), - ?cfg(listener_config(ejabberd_c2s, #{proto => udp}), - listen_raw(<<"c2s">>, #{<<"proto">> => <<"udp">>})), - ?err(listen_raw(<<"c2s">>, #{<<"proto">> => <<"pigeon">>})). - listen_duplicate(_Config) -> - ?cfg(listen, [listener(ejabberd_c2s, #{}), - listener(ejabberd_c2s, #{port => 5223})], - #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, - <<"ip_address">> => <<"0">>}, + ?cfg(listen, [listener(c2s, #{port => 5222}), + listener(c2s, #{port => 5223})], + #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, <<"ip_address">> => <<"0">>}, #{<<"port">> => 5223}]}}), ?err([#{reason := duplicate_listeners, duplicates := [{5222, {0, 0, 0, 0}, tcp}]}], - #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, - <<"ip_address">> => <<"0">>}, - #{<<"port">> => 5222}]}}). - -listen_ip_version(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{}), - listen_raw(<<"c2s">>, #{<<"ip_version">> => 4})), - ?cfg(listener_config(ejabberd_c2s, #{ip_address => "::", - ip_tuple => {0, 0, 0, 0, 0, 0, 0, 0}, - ip_version => 6}), - listen_raw(<<"c2s">>, #{<<"ip_version">> => 6})), - ?err(listen_raw(<<"c2s">>, #{<<"ip_version">> => 7})). - -listen_backlog(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{backlog => 10}), - listen_raw(<<"c2s">>, #{<<"backlog">> => 10})), - ?err(listen_raw(<<"c2s">>, #{<<"backlog">> => -10})). - -listen_proxy_protocol(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{proxy_protocol => true}), - listen_raw(<<"c2s">>, #{<<"proxy_protocol">> => true})), - ?cfg(listener_config(ejabberd_s2s_in, #{proxy_protocol => true}), - listen_raw(<<"s2s">>, #{<<"proxy_protocol">> => true})), - ?cfg(listener_config(ejabberd_service, #{proxy_protocol => true}), - listen_raw(<<"service">>, #{<<"proxy_protocol">> => true})), - ?err(listen_raw(<<"c2s">>, #{<<"proxy_protocol">> => <<"awesome">>})). - -listen_num_acceptors(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{acceptors_num => 100}), - listen_raw(<<"c2s">>, #{<<"num_acceptors">> => 100})), - ?cfg(listener_config(ejabberd_s2s_in, #{acceptors_num => 100}), - listen_raw(<<"s2s">>, #{<<"num_acceptors">> => 100})), - ?cfg(listener_config(ejabberd_service, #{acceptors_num => 100}), - listen_raw(<<"service">>, #{<<"num_acceptors">> => 100})), - ?err(listen_raw(<<"c2s">>, #{<<"num_acceptors">> => 0})). - -listen_access(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{access => rule1}), - listen_raw(<<"c2s">>, #{<<"access">> => <<"rule1">>})), - ?cfg(listener_config(ejabberd_service, #{access => rule1}), - listen_raw(<<"service">>, #{<<"access">> => <<"rule1">>})), - ?err(listen_raw(<<"c2s">>, #{<<"access">> => <<>>})). - -listen_shaper(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{shaper => c2s_shaper}), - listen_raw(<<"c2s">>, #{<<"shaper">> => <<"c2s_shaper">>})), - ?cfg(listener_config(ejabberd_s2s_in, #{shaper => s2s_shaper}), - listen_raw(<<"s2s">>, #{<<"shaper">> => <<"s2s_shaper">>})), - ?cfg(listener_config(ejabberd_service, #{shaper_rule => fast}), - listen_raw(<<"service">>, #{<<"shaper_rule">> => <<"fast">>})), - ?err(listen_raw(<<"s2s">>, #{<<"shaper">> => <<>>})). - -listen_xml_socket(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{xml_socket => true}), - listen_raw(<<"c2s">>, #{<<"xml_socket">> => true})), - ?err(listen_raw(<<"c2s">>, #{<<"xml_socket">> => 10})). - -listen_zlib(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{zlib => 1024}), - listen_raw(<<"c2s">>, #{<<"zlib">> => 1024})), - ?err(listen_raw(<<"c2s">>, #{<<"zlib">> => 0})). - -listen_hibernate_after(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{hibernate_after => 10}), - listen_raw(<<"c2s">>, #{<<"hibernate_after">> => 10})), - ?cfg(listener_config(ejabberd_s2s_in, #{hibernate_after => 10}), - listen_raw(<<"s2s">>, #{<<"hibernate_after">> => 10})), - ?cfg(listener_config(ejabberd_service, #{hibernate_after => 10}), - listen_raw(<<"service">>, #{<<"hibernate_after">> => 10})), - ?err(listen_raw(<<"c2s">>, #{<<"hibernate_after">> => -10})). - -listen_max_stanza_size(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{max_stanza_size => 10000}), - listen_raw(<<"c2s">>, #{<<"max_stanza_size">> => 10000})), - ?cfg(listener_config(ejabberd_s2s_in, #{max_stanza_size => 10000}), - listen_raw(<<"s2s">>, #{<<"max_stanza_size">> => 10000})), - ?cfg(listener_config(ejabberd_service, #{max_stanza_size => 10000}), - listen_raw(<<"service">>, #{<<"max_stanza_size">> => 10000})), - ?err(listen_raw(<<"c2s">>, #{<<"max_stanza_size">> => <<"infinity">>})). - -listen_max_fsm_queue(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{max_fsm_queue => 1000}), - listen_raw(<<"c2s">>, #{<<"max_fsm_queue">> => 1000})), - ?cfg(listener_config(ejabberd_service, #{max_fsm_queue => 1000}), - listen_raw(<<"service">>, #{<<"max_fsm_queue">> => 1000})), - ?err(listen_raw(<<"s2s">>, #{<<"max_fsm_queue">> => 1000})), % only for c2s and service - ?err(listen_raw(<<"c2s">>, #{<<"max_fsm_queue">> => 0})). - -listen_tls_mode(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [starttls]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"mode">> => <<"starttls">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"mode">> => <<"stoptls">>}})). - -listen_tls_module(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => []}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"fast_tls">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"slow_tls">>}})). - -listen_tls_verify(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [verify_peer]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"verify_peer">> => true}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [verify_none]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"verify_peer">> => false}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, verify_peer]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"verify_peer">> => true}})), - ?cfg(listener_config(ejabberd_cowboy, #{ssl => [{verify, verify_peer}]}), - listen_raw(<<"http">>, #{<<"tls">> => #{<<"verify_peer">> => true}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, verify_none]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"verify_peer">> => false}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"verify_peer">> => <<"maybe">>}})). - -listen_tls_verify_mode(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, [{verify_fun, {peer, true}}]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"verify_mode">> => <<"peer">>}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, [{verify_fun, - {selfsigned_peer, false}}]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"verify_mode">> => <<"selfsigned_peer">>, - <<"disconnect_on_failure">> => false}})), - ?cfg(listener_config(ejabberd_cowboy, #{ssl => [{verify_mode, peer}]}), - listen_raw(<<"http">>, #{<<"tls">> => #{<<"verify_mode">> => <<"peer">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"verify_mode">> => <<"peer">>, - <<"disconnect_on_failure">> => <<"false">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"verify_mode">> => <<"whatever">>}})), - ?err(listen_raw(<<"http">>, #{<<"tls">> => #{<<"verify_mode">> => <<"whatever">>}})). - -listen_tls_crl_files(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {crlfiles, ["file1", "file2"]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"crl_files">> => [<<"file1">>, - <<"file2">>]}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"crl_files">> => [<<>>]}})), - %% only for just_tls - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"crl_files">> => [<<"file1">>, - <<"file2">>]}})). - -listen_tls_certfile(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{certfile, "priv/cert.pem"}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"certfile">> => <<"priv/cert.pem">>}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, [{certfile, "priv/cert.pem"}]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"certfile">> => <<"priv/cert.pem">>}})), - ?cfg(listener_config(ejabberd_cowboy, #{ssl => [{certfile, "priv/cert.pem"}]}), - listen_raw(<<"http">>, #{<<"tls">> => #{<<"certfile">> => <<"priv/cert.pem">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"certfile">> => <<"no_such_file.pem">>}})). - -listen_tls_cacertfile(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{cafile, "priv/ca.pem"}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"cacertfile">> => <<"priv/ca.pem">>}})), - ?cfg(listener_config(ejabberd_s2s_in, #{tls => [{cafile, "priv/ca.pem"}]}), - listen_raw(<<"s2s">>, #{<<"tls">> => #{<<"cacertfile">> => <<"priv/ca.pem">>}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, [{cacertfile, "priv/ca.pem"}]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"cacertfile">> => <<"priv/ca.pem">>}})), - ?cfg(listener_config(ejabberd_cowboy, #{ssl => [{cacertfile, "priv/ca.pem"}]}), - listen_raw(<<"http">>, #{<<"tls">> => #{<<"cacertfile">> => <<"priv/ca.pem">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"cacertfile">> => <<"no_such_file.pem">>}})). - -listen_tls_dhfile(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{dhfile, "priv/dh.pem"}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"dhfile">> => <<"priv/dh.pem">>}})), - ?cfg(listener_config(ejabberd_s2s_in, #{tls => [{dhfile, "priv/dh.pem"}]}), - listen_raw(<<"s2s">>, #{<<"tls">> => #{<<"dhfile">> => <<"priv/dh.pem">>}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, [{dhfile, "priv/dh.pem"}]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"dhfile">> => <<"priv/dh.pem">>}})), - ?cfg(listener_config(ejabberd_cowboy, #{ssl => [{dhfile, "priv/dh.pem"}]}), - listen_raw(<<"http">>, #{<<"tls">> => #{<<"dhfile">> => <<"priv/dh.pem">>}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"dhfile">> => <<"no_such_file.pem">>}})). - -listen_tls_ciphers(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{ciphers, "TLS_AES_256_GCM_SHA384"}]}), - listen_raw(<<"c2s">>, - #{<<"tls">> => #{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})), - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, - [{ciphers, "TLS_AES_256_GCM_SHA384"}]}]}), - listen_raw(<<"c2s">>, - #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})), - ?cfg(listener_config(ejabberd_s2s_in, #{tls => [{ciphers, "TLS_AES_256_GCM_SHA384"}]}), - listen_raw(<<"s2s">>, - #{<<"tls">> => #{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})), - ?cfg(listener_config(ejabberd_cowboy, #{ssl => [{ciphers, "TLS_AES_256_GCM_SHA384"}]}), - listen_raw(<<"http">>, - #{<<"tls">> => #{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>}})), - ?err(listen_raw(<<"c2s">>, - #{<<"tls">> => #{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]}})). - -listen_tls_versions(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{tls_module, just_tls}, - {ssl_options, - [{versions, ['tlsv1.2', 'tlsv1.3']}]}]}), - listen_raw(<<"c2s">>, - #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"versions">> => [<<"tlsv1.2">>, <<"tlsv1.3">>]}})), - ?err(listen_raw(<<"c2s">>, - #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"versions">> => <<"tlsv1.2">>}})). - -listen_tls_protocol_options(_Config) -> - ?cfg(listener_config(ejabberd_c2s, #{tls => [{protocol_options, ["nosslv2"]}]}), - listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<"nosslv2">>]}})), - ?cfg(listener_config(ejabberd_s2s_in, #{tls => [{protocol_options, ["nosslv2"]}]}), - listen_raw(<<"s2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<"nosslv2">>]}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<>>]}})), - ?err(listen_raw(<<"s2s">>, #{<<"tls">> => #{<<"protocol_options">> => [<<>>]}})), - ?err(listen_raw(<<"c2s">>, #{<<"tls">> => #{<<"module">> => <<"just_tls">>, - <<"protocol_options">> => [<<"nosslv2">>]}})). - -listen_check_from(_Config) -> - ?cfg(listener_config(ejabberd_service, #{service_check_from => false}), - listen_raw(<<"service">>, #{<<"check_from">> => false})), - ?err(listen_raw(<<"service">>, #{<<"check_from">> => 1})). - -listen_hidden_components(_Config) -> - ?cfg(listener_config(ejabberd_service, #{hidden_components => true}), - listen_raw(<<"service">>, #{<<"hidden_components">> => true})), - ?err(listen_raw(<<"service">>, #{<<"hidden_components">> => <<"yes">>})). - -listen_conflict_behaviour(_Config) -> - ?cfg(listener_config(ejabberd_service, #{conflict_behaviour => kick_old}), - listen_raw(<<"service">>, #{<<"conflict_behaviour">> => <<"kick_old">>})), - ?err(listen_raw(<<"service">>, #{<<"conflict_behaviour">> => <<"kill_server">>})). - -listen_password(_Config) -> - ?cfg(listener_config(ejabberd_service, #{password => "secret"}), - listen_raw(<<"service">>, #{<<"password">> => <<"secret">>})), - ?err(listen_raw(<<"service">>, #{<<"password">> => <<>>})). - -listen_http_num_acceptors(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{transport_options => [{num_acceptors, 10}]}), - listen_raw(<<"http">>, #{<<"transport">> => #{<<"num_acceptors">> => 10}})), - ?err(listen_raw(<<"http">>, #{<<"transport">> => #{<<"num_acceptors">> => 0}})). - -listen_http_max_connections(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{transport_options => [{max_connections, 100}]}), - listen_raw(<<"http">>, #{<<"transport">> => #{<<"max_connections">> => 100}})), - ?cfg(listener_config(ejabberd_cowboy, #{transport_options => [{max_connections, infinity}]}), - listen_raw(<<"http">>, #{<<"transport">> => - #{<<"max_connections">> => <<"infinity">>}})), - ?err(listen_raw(<<"http">>, #{<<"transport">> => #{<<"max_connections">> => -1}})). - -listen_http_compress(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{protocol_options => [{compress, true}]}), - listen_raw(<<"http">>, #{<<"protocol">> => #{<<"compress">> => true}})), - ?err(listen_raw(<<"http">>, #{<<"protocol">> => #{<<"compress">> => 0}})). + #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, <<"ip_address">> => <<"0">>}, + #{<<"port">> => 5222}]}}), + ?err([#{reason := duplicate_listeners, + duplicates := [{5222, {0, 0, 0, 0}, tcp}]}], + #{<<"listen">> => #{<<"c2s">> => [#{<<"port">> => 5222, <<"ip_address">> => <<"0">>}], + <<"s2s">> => [#{<<"port">> => 5222}]}}). + +listen_c2s(_Config) -> + T = fun(Opts) -> listen_raw(c2s, maps:merge(#{<<"port">> => 5222}, Opts)) end, + P = [listen, 1], + ?cfg(P, config([listen, c2s], #{port => 5222}), T(#{})), + test_listen(P, T), + test_listen_xmpp(P, T), + ?cfg(P ++ [access], rule1, T(#{<<"access">> => <<"rule1">>})), + ?cfg(P ++ [shaper], c2s_shaper, T(#{<<"shaper">> => <<"c2s_shaper">>})), + ?cfg(P ++ [zlib], 1024, T(#{<<"zlib">> => 1024})), + ?cfg(P ++ [max_fsm_queue], 1000, T(#{<<"max_fsm_queue">> => 1000})), + ?cfg(P ++ [allowed_auth_methods], [rdbms, http], + T(#{<<"allowed_auth_methods">> => [<<"rdbms">>, <<"http">>]})), + ?err(T(#{<<"access">> => <<>>})), + ?err(T(#{<<"shaper">> => <<>>})), + ?err(T(#{<<"zlib">> => 0})), + ?err(T(#{<<"max_fsm_queue">> => 0})), + ?err(T(#{<<"allowed_auth_methods">> => [<<"bad_method">>]})), + ?err(T(#{<<"allowed_auth_methods">> => [<<"rdbms">>, <<"rdbms">>]})). + +listen_c2s_fast_tls(_Config) -> + T = fun(Opts) -> listen_raw(c2s, #{<<"port">> => 5222, + <<"tls">> => Opts}) end, + P = [listen, 1, tls], + ?cfg(P, [], T(#{<<"module">> => <<"fast_tls">>})), + ?cfg(P, [starttls], T(#{<<"mode">> => <<"starttls">>})), + ?cfg(P, [verify_peer], T(#{<<"verify_peer">> => true})), + ?cfg(P, [verify_none], T(#{<<"verify_peer">> => false})), + ?cfg(P, [{certfile, "priv/cert.pem"}], T(#{<<"certfile">> => <<"priv/cert.pem">>})), + ?cfg(P, [{cafile, "priv/ca.pem"}], T(#{<<"cacertfile">> => <<"priv/ca.pem">>})), + ?cfg(P, [{dhfile, "priv/dh.pem"}], T(#{<<"dhfile">> => <<"priv/dh.pem">>})), + ?cfg(P, [{ciphers, "TLS_AES_256_GCM_SHA384"}], + T(#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})), + ?cfg(P, [{protocol_options, ["nosslv2"]}], T(#{<<"protocol_options">> => [<<"nosslv2">>]})), + ?err(T(#{<<"mode">> => <<"stopttls">>})), + ?err(T(#{<<"module">> => <<"slow_tls">>})), + ?err(T(#{<<"verify_peer">> => <<"maybe">>})), + ?err(T(#{<<"crl_files">> => [<<"file1">>, <<"file2">>]})), % only for just_tls + ?err(T(#{<<"certfile">> => <<"no_such_file.pem">>})), + ?err(T(#{<<"cacertfile">> => <<"no_such_file.pem">>})), + ?err(T(#{<<"dhfile">> => <<"no_such_file.pem">>})), + ?err(T(#{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]})), + ?err(T(#{<<"protocol_options">> => [<<>>]})). + +listen_c2s_just_tls(_Config) -> + T = fun(Opts) -> listen_raw(c2s, #{<<"port">> => 5222, + <<"tls">> => Opts#{<<"module">> => <<"just_tls">>}}) end, + P = [listen, 1, tls], + BaseOpts = [{tls_module, just_tls}], + ?cfg(P, BaseOpts, T(#{})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{verify_fun, {selfsigned_peer, false}}]}], + T(#{<<"verify_mode">> => <<"selfsigned_peer">>, <<"disconnect_on_failure">> => false})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{verify_fun, {peer, true}}]}], + T(#{<<"verify_mode">> => <<"peer">>})), + ?cfg(P, BaseOpts ++ [{crlfiles, ["file1", "file2"]}], + T(#{<<"crl_files">> => [<<"file1">>, <<"file2">>]})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{certfile, "priv/cert.pem"}]}], + T(#{<<"certfile">> => <<"priv/cert.pem">>})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{cacertfile, "priv/ca.pem"}]}], + T(#{<<"cacertfile">> => <<"priv/ca.pem">>})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{dhfile, "priv/dh.pem"}]}], + T(#{<<"dhfile">> => <<"priv/dh.pem">>})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{ciphers, "TLS_AES_256_GCM_SHA384"}]}], + T(#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})), + ?cfg(P, BaseOpts ++ [{ssl_options, [{versions, ['tlsv1.2', 'tlsv1.3']}]}], + T(#{<<"versions">> => [<<"tlsv1.2">>, <<"tlsv1.3">>]})), + ?err(T(#{<<"verify_mode">> => <<"whatever">>})), + ?err(T(#{<<"verify_mode">> => <<"peer">>, <<"disconnect_on_failure">> => <<"sometimes">>})), + ?err(T(#{<<"crl_files">> => [<<>>]})), + ?err(T(#{<<"versions">> => <<"tlsv1.2">>})), + ?err(T(#{<<"protocol_options">> => [<<"nosslv2">>]})). % only for fast_tls + +listen_s2s(_Config) -> + T = fun(Opts) -> listen_raw(s2s, maps:merge(#{<<"port">> => 5269}, Opts)) end, + P = [listen, 1], + ?cfg(P, config([listen, s2s], #{port => 5269}), T(#{})), + test_listen(P, T), + test_listen_xmpp(P, T), + ?cfg(P ++ [shaper], s2s_shaper, T(#{<<"shaper">> => <<"s2s_shaper">>})), + ?err(T(#{<<"shaper">> => <<>>})). + +listen_s2s_tls(_Config) -> + T = fun(Opts) -> listen_raw(s2s, #{<<"port">> => 5269, <<"tls">> => Opts}) end, + P = [listen, 1, tls], + ?cfg(P, [{cafile, "priv/ca.pem"}], T(#{<<"cacertfile">> => <<"priv/ca.pem">>})), + ?cfg(P, [{dhfile, "priv/dh.pem"}], T(#{<<"dhfile">> => <<"priv/dh.pem">>})), + ?cfg(P, [{ciphers, "TLS_AES_256_GCM_SHA384"}], + T(#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})), + ?cfg(P, [{protocol_options, ["nosslv2"]}], T(#{<<"protocol_options">> => [<<"nosslv2">>]})), + ?err(T(#{<<"cacertfile">> => <<>>})), + ?err(T(#{<<"dhfile">> => 12})), + ?err(T(#{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]})), + ?err(T(#{<<"protocol_options">> => [<<>>]})). + +listen_service(_Config) -> + T = fun(Opts) -> listen_raw(service, maps:merge(#{<<"port">> => 8888, + <<"password">> => <<"secret">>}, Opts)) + end, + P = [listen, 1], + ?cfg(P, config([listen, service], #{port => 8888, password => "secret"}), T(#{})), + test_listen(P, T), + test_listen_xmpp(P, T), + ?cfg(P ++ [access], rule1, T(#{<<"access">> => <<"rule1">>})), + ?cfg(P ++ [shaper_rule], fast, T(#{<<"shaper_rule">> => <<"fast">>})), + ?cfg(P ++ [check_from], false, T(#{<<"check_from">> => false})), + ?cfg(P ++ [hidden_components], true, T(#{<<"hidden_components">> => true})), + ?cfg(P ++ [conflict_behaviour], kick_old, T(#{<<"conflict_behaviour">> => <<"kick_old">>})), + ?cfg(P ++ [max_fsm_queue], 1000, T(#{<<"max_fsm_queue">> => 1000})), + ?err(T(#{<<"access">> => <<>>})), + ?err(T(#{<<"shaper_rule">> => <<>>})), + ?err(T(#{<<"check_from">> => 1})), + ?err(T(#{<<"hidden_components">> => <<"yes">>})), + ?err(T(#{<<"conflict_behaviour">> => <<"kill_server">>})), + ?err(T(#{<<"password">> => <<>>})), + ?err(T(#{<<"password">> => undefined})), + ?err(T(#{<<"max_fsm_queue">> => 0})). + +listen_http(_Config) -> + T = fun(Opts) -> listen_raw(http, maps:merge(#{<<"port">> => 5280}, Opts)) end, + P = [listen, 1], + ?cfg(P, config([listen, http], #{port => 5280}), T(#{})), + test_listen(P, T). + +listen_http_tls(_Config) -> + T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"tls">> => Opts}) end, + P = [listen, 1, tls], + ?cfg(P, [{verify, verify_peer}], T(#{<<"verify_peer">> => true})), + ?cfg(P, [{verify_mode, peer}], T(#{<<"verify_mode">> => <<"peer">>})), + ?cfg(P, [{certfile, "priv/cert.pem"}], T(#{<<"certfile">> => <<"priv/cert.pem">>})), + ?cfg(P, [{cacertfile, "priv/ca.pem"}], T(#{<<"cacertfile">> => <<"priv/ca.pem">>})), + ?cfg(P, [{dhfile, "priv/dh.pem"}], T(#{<<"dhfile">> => <<"priv/dh.pem">>})), + ?cfg(P, [{ciphers, "TLS_AES_256_GCM_SHA384"}], + T(#{<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384">>})), + ?err(T(#{<<"verify_peer">> => 0})), + ?err(T(#{<<"verify_mode">> => <<"pear">>})), + ?err(T(#{<<"certfile">> => <<>>})), + ?err(T(#{<<"cacertfile">> => <<>>})), + ?err(T(#{<<"dhfile">> => 12})), + ?err(T(#{<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>]})). + +listen_http_transport(_Config) -> + T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"transport">> => Opts}) end, + P = [listen, 1, transport], + ?cfg(P ++ [num_acceptors], 10, T(#{<<"num_acceptors">> => 10})), + ?cfg(P ++ [max_connections], 1024, T(#{<<"max_connections">> => 1024})), + ?err(T(#{<<"num_acceptors">> => 0})), + ?err(T(#{<<"max_connections">> => -1})). + +listen_http_protocol(_Config) -> + T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"protocol">> => Opts}) end, + P = [listen, 1, protocol], + ?cfg(P ++ [compress], true, T(#{<<"compress">> => true})), + ?err(T(#{<<"compress">> => 1})). listen_http_handlers(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"_", "/http-bind", mod_bosh, []}]}), - listen_raw(<<"http">>, #{<<"handlers">> => - #{<<"mod_bosh">> => - [#{<<"host">> => <<"_">>, - <<"path">> => <<"/http-bind">>}]}})), - ?err(listen_raw(<<"http">>, #{<<"handlers">> => - #{<<"mod_bosch">> => - [#{<<"host">> => <<"dishwasher">>, - <<"path">> => <<"/cutlery">>}]}})), - ?err(listen_raw(<<"http">>, #{<<"handlers">> => - #{<<"mod_bosh">> => - [#{<<"host">> => <<"pathless">>}]}})), - ?err(listen_raw(<<"http">>, #{<<"handlers">> => - #{<<"mod_bosh">> => - [#{<<"host">> => <<>>, - <<"path">> => <<"/">>}]}})), - ?err(listen_raw(<<"http">>, #{<<"handlers">> => - #{<<"mod_bosh">> => - [#{<<"path">> => <<"hostless">>}]}})). + T = fun(Opts) -> listen_raw(http, #{<<"port">> => 5280, <<"handlers">> => Opts}) end, + P = [listen, 1, handlers], + ?cfg(P, [{"_", "/http-bind", mod_bosh, []}], + T(#{<<"mod_bosh">> => [#{<<"host">> => <<"_">>, + <<"path">> => <<"/http-bind">>}]})), + ?err(T(#{<<"mod_bosch">> => [#{<<"host">> => <<"dishwasher">>, + <<"path">> => <<"/cutlery">>}]})), + ?err(T(#{<<"mod_bosh">> => [#{<<"host">> => <<"pathless">>}]})), + ?err(T(#{<<"mod_bosh">> => [#{<<"host">> => <<>>, <<"path">> => <<"/">>}]})), + ?err(T(#{<<"mod_bosh">> => [#{<<"path">> => <<"hostless">>}]})). listen_http_handlers_websockets(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"localhost", "/api", mod_websockets, []}]}), - http_handler_raw(<<"mod_websockets">>, #{})), - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"localhost", "/api", mod_websockets, - [{ejabberd_service, [{access, all}]}] - }]}), - http_handler_raw(<<"mod_websockets">>, #{<<"service">> => #{<<"access">> => <<"all">>}})), - ?err(http_handler_raw(<<"mod_websockets">>, #{<<"service">> => <<"unbelievable">>})). + T = fun(Opts) -> http_handler_raw(<<"mod_websockets">>, Opts) end, + P = [listen, 1, handlers], + ?cfg(P, [{"localhost", "/api", mod_websockets, []}], T(#{})), + ?cfg(P, [{"localhost", "/api", mod_websockets, + [{service, maps:merge(extra_service_listener_config(), #{password => "secret"})}] + }], + T(#{<<"service">> => #{<<"password">> => <<"secret">>}})), + ?err(T(#{<<"service">> => #{}})). listen_http_handlers_lasse(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"localhost", "/api", lasse_handler, - [mongoose_client_api_sse] - }]}), - http_handler_raw(<<"lasse_handler">>, #{<<"module">> => <<"mongoose_client_api_sse">>})), - ?err(http_handler_raw(<<"lasse_handler">>, #{<<"module">> => <<"mooongooose_api_ssie">>})), - ?err(http_handler_raw(<<"lasse_handler">>, #{})). + T = fun(Opts) -> http_handler_raw(<<"lasse_handler">>, Opts) end, + P = [listen, 1, handlers], + ?cfg(P, [{"localhost", "/api", lasse_handler, [mongoose_client_api_sse]}], + T(#{<<"module">> => <<"mongoose_client_api_sse">>})), + ?err(T(#{<<"module">> => <<"mooongooose_api_ssie">>})), + ?err(T(#{})). listen_http_handlers_static(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"localhost", "/api", cowboy_static, - {priv_dir, cowboy_swagger, "swagger", - [{mimetypes, cow_mimetypes, all}]} - }]}), - http_handler_raw(<<"cowboy_static">>, #{<<"type">> => <<"priv_dir">>, - <<"app">> => <<"cowboy_swagger">>, - <<"content_path">> => <<"swagger">>})), - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"localhost", "/api", cowboy_static, - {file, "swagger", [{mimetypes, cow_mimetypes, all}]} - }]}), - http_handler_raw(<<"cowboy_static">>, #{<<"type">> => <<"file">>, - <<"content_path">> => <<"swagger">>})), - ?err(http_handler_raw(<<"cowboy_static">>, #{<<"type">> => <<"priv_dir">>, - <<"app">> => <<"cowboy_swagger">>})). + T = fun(Opts) -> http_handler_raw(<<"cowboy_static">>, Opts) end, + P = [listen, 1, handlers], + ?cfg(P, [{"localhost", "/api", cowboy_static, + {priv_dir, cowboy_swagger, "swagger", + [{mimetypes, cow_mimetypes, all}]} + }], + T(#{<<"type">> => <<"priv_dir">>, <<"app">> => <<"cowboy_swagger">>, + <<"content_path">> => <<"swagger">>})), + ?cfg(P, [{"localhost", "/api", cowboy_static, + {file, "swagger", [{mimetypes, cow_mimetypes, all}]} + }], + T(#{<<"type">> => <<"file">>, <<"content_path">> => <<"swagger">>})), + ?err(T(#{<<"type">> => <<"priv_dir">>, <<"app">> => <<"cowboy_swagger">>})). listen_http_handlers_api(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, #{modules => [{"localhost", "/api", mongoose_api, - [{handlers, [mongoose_api_metrics, - mongoose_api_users]}]} - ]}), - http_handler_raw(<<"mongoose_api">>, #{<<"handlers">> => [<<"mongoose_api_metrics">>, - <<"mongoose_api_users">>]})), - ?err(http_handler_raw(<<"mongoose_api">>, #{<<"handlers">> => [<<"not_an_api_module">>]})), - ?err(http_handler_raw(<<"mongoose_api">>, #{})). + T = fun(Opts) -> http_handler_raw(<<"mongoose_api">>, Opts) end, + P = [listen, 1, handlers], + ?cfg(P, [{"localhost", "/api", mongoose_api, + [{handlers, [mongoose_api_metrics, + mongoose_api_users]}]} + ], + T(#{<<"handlers">> => [<<"mongoose_api_metrics">>, <<"mongoose_api_users">>]})), + ?err(T(#{<<"handlers">> => [<<"not_an_api_module">>]})), + ?err(T(#{})). listen_http_handlers_domain(_Config) -> - ?cfg(listener_config(ejabberd_cowboy, - #{modules => [{"localhost", "/api", mongoose_domain_handler, - [{password, <<"cool">>}, {username, <<"admin">>}] - }]}), - http_handler_raw(<<"mongoose_domain_handler">>, - #{<<"username">> => <<"admin">>, <<"password">> => <<"cool">>})), - ?cfg(listener_config(ejabberd_cowboy, - #{modules => [{"localhost", "/api", mongoose_domain_handler, - [] }]}), - http_handler_raw(<<"mongoose_domain_handler">>, #{})), + T = fun(Opts) -> http_handler_raw(<<"mongoose_domain_handler">>, Opts) end, + P = [listen, 1, handlers], + ?cfg(P, [{"localhost", "/api", mongoose_domain_handler, + [{password, <<"cool">>}, {username, <<"admin">>}] + }], + T(#{<<"username">> => <<"admin">>, <<"password">> => <<"cool">>})), + ?cfg(P, [{"localhost", "/api", mongoose_domain_handler, []}], T(#{})), %% Both username and password required. Or none. - ?err(http_handler_raw(<<"mongoose_domain_handler">>, #{<<"username">> => <<"admin">>})), - ?err(http_handler_raw(<<"mongoose_domain_handler">>, #{<<"password">> => <<"cool">>})). + ?err(T(#{<<"username">> => <<"admin">>})), + ?err(T(#{<<"password">> => <<"cool">>})). + +test_listen(P, T) -> + ?cfg(P ++ [ip_address], "192.168.1.16", T(#{<<"ip_address">> => <<"192.168.1.16">>})), + ?cfg(P ++ [ip_tuple], {192, 168, 1, 16}, T(#{<<"ip_address">> => <<"192.168.1.16">>})), + ?cfg(P ++ [ip_version], 4, T(#{<<"ip_address">> => <<"192.168.1.16">>})), + ?cfg(P ++ [ip_address], "2001:db8:3:4:5:6:7:8", + T(#{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})), + ?cfg(P ++ [ip_tuple], {8193, 3512, 3, 4, 5, 6, 7, 8}, + T(#{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})), + ?cfg(P ++ [ip_version], 6, + T(#{<<"ip_address">> => <<"2001:db8:3:4:5:6:7:8">>})), + ?cfg(P ++ [ip_version], 4, T(#{<<"ip_version">> => 4})), + ?cfg(P ++ [ip_version], 6, T(#{<<"ip_version">> => 6})), + ?cfg(P ++ [ip_address], "::", T(#{<<"ip_version">> => 6})), + ?cfg(P ++ [ip_tuple], {0, 0, 0, 0, 0, 0, 0, 0}, T(#{<<"ip_version">> => 6})), + ?cfg(P ++ [proto], tcp, T(#{<<"proto">> => <<"tcp">>})), + ?err(T(#{<<"ip_address">> => <<"192.168.1.999">>})), + ?err(T(#{<<"port">> => <<"5222">>})), + ?err(T(#{<<"port">> => 522222})), + ?err(T(#{<<"port">> => undefined})), + ?err(T(#{<<"ip_version">> => 7})), + ?err(T(#{<<"proto">> => <<"udp">>})). % only TCP is accepted + +test_listen_xmpp(P, T) -> + ?cfg(P ++ [backlog], 10, T(#{<<"backlog">> => 10})), + ?cfg(P ++ [proxy_protocol], true, T(#{<<"proxy_protocol">> => true})), + ?cfg(P ++ [hibernate_after], 10, T(#{<<"hibernate_after">> => 10})), + ?cfg(P ++ [max_stanza_size], 10000, T(#{<<"max_stanza_size">> => 10000})), + ?cfg(P ++ [num_acceptors], 100, T(#{<<"num_acceptors">> => 100})), + ?err(T(#{<<"backlog">> => -10})), + ?err(T(#{<<"proxy_protocol">> => <<"awesome">>})), + ?err(T(#{<<"hibernate_after">> => -10})), + ?err(T(#{<<"max_stanza_size">> => <<"unlimited">>})), + ?err(T(#{<<"num_acceptors">> => 0})). %% tests: auth @@ -2181,8 +2058,8 @@ mod_mam_meta(_Config) -> check_module_defaults(mod_mam_meta), T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam_meta">> => Opts}} end, P = [modules, mod_mam_meta], - test_cache_config(fun(Opts) -> T(#{<<"cache">> => Opts}) end, P ++ [cache]), - test_mod_mam_meta(T, P). + test_cache_config(P ++ [cache], fun(Opts) -> T(#{<<"cache">> => Opts}) end), + test_mod_mam_meta(P, T). mod_mam_meta_riak(_Config) -> T = fun(Opts) -> @@ -2200,7 +2077,7 @@ mod_mam_meta_riak(_Config) -> mod_mam_meta_pm(_Config) -> T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam_meta">> => #{<<"pm">> => Opts}}} end, P = [modules, mod_mam_meta, pm], - test_mod_mam_meta(T, P), + test_mod_mam_meta(P, T), ?cfgh(P, default_config(P), T(#{})), ?cfgh(P ++ [archive_groupchats], true, T(#{<<"archive_groupchats">> => true})), ?cfgh(P ++ [same_mam_id_for_peers], true, T(#{<<"same_mam_id_for_peers">> => true})), @@ -2211,7 +2088,7 @@ mod_mam_meta_pm(_Config) -> mod_mam_meta_muc(_Config) -> T = fun(Opts) -> #{<<"modules">> => #{<<"mod_mam_meta">> => #{<<"muc">> => Opts}}} end, P = [modules, mod_mam_meta, muc], - test_mod_mam_meta(T, P), + test_mod_mam_meta(P, T), ?cfgh(P, default_config(P), T(#{})), ?cfgh(P ++ [host], {prefix, <<"muc.">>}, T(#{<<"host">> => <<"muc.@HOST@">>})), ?cfgh(P ++ [host], {fqdn, <<"muc.test">>}, T(#{<<"host">> => <<"muc.test">>})), @@ -2221,8 +2098,8 @@ mod_mam_meta_muc(_Config) -> ?errh(T(#{<<"archive_groupchats">> => true})), % pm-only ?errh(T(#{<<"same_mam_id_for_peers">> => true})). % pm-only -test_mod_mam_meta(T, P) -> - test_async_writer(T, P), +test_mod_mam_meta(P, T) -> + test_async_writer(P, T), ?cfgh(P ++ [backend], rdbms, T(#{<<"backend">> => <<"rdbms">>})), ?cfgh(P ++ [no_stanzaid_element], true, @@ -2266,7 +2143,7 @@ test_mod_mam_meta(T, P) -> ?errh(T(#{<<"extra_fin_element">> => <<"bad_module">>})), ?errh(T(#{<<"extra_lookup_params">> => <<"bad_module">>})). -test_cache_config(T, P) -> +test_cache_config(P, T) -> ?cfgh(P ++ [module], internal, T(#{<<"module">> => <<"internal">>})), ?cfgh(P ++ [time_to_live], 8600, T(#{<<"time_to_live">> => 8600})), ?cfgh(P ++ [time_to_live], infinity, T(#{<<"time_to_live">> => <<"infinity">>})), @@ -2279,7 +2156,7 @@ test_cache_config(T, P) -> ?errh(T(#{<<"number_of_segments">> => <<"infinity">>})), ?errh(T(#{<<"cache">> => []})). -test_async_writer(ParentT, ParentP) -> +test_async_writer(ParentP, ParentT) -> P = ParentP ++ [async_writer], T = fun(Opts) -> ParentT(#{<<"async_writer">> => Opts}) end, ?cfgh(P ++ [flush_interval], 1500, T(#{<<"flush_interval">> => 1500})), @@ -2476,7 +2353,7 @@ mod_muc_light(_Config) -> check_module_defaults(mod_muc_light), T = fun(Opts) -> #{<<"modules">> => #{<<"mod_muc_light">> => Opts}} end, P = [modules, mod_muc_light], - test_cache_config(fun(Opts) -> T(#{<<"cache_affs">> => Opts}) end, P ++ [cache_affs]), + test_cache_config(P ++ [cache_affs], fun(Opts) -> T(#{<<"cache_affs">> => Opts}) end), ?cfgh(P ++ [backend], mnesia, T(#{<<"backend">> => <<"mnesia">>})), ?cfgh(P ++ [host], {prefix, <<"muclight.">>}, @@ -3170,26 +3047,21 @@ check_module_defaults(Mod) -> %% helpers for 'listen' tests -listener_config(Mod, Opts) -> - [{listen, [listener(Mod, Opts)]}]. - -listener(Mod, Opts) -> - maps:merge(#{port => 5222, - ip_address => "0", - ip_tuple => {0, 0, 0, 0}, - ip_version => 4, - proto => tcp, - module => Mod}, Opts). +listener(Type, Opts) -> + config([listen, Type], Opts). http_handler_raw(Type, Opts) -> - listen_raw(<<"http">>, #{<<"handlers">> => - #{Type => - [Opts#{<<"host">> => <<"localhost">>, - <<"path">> => <<"/api">>}] - }}). + listen_raw(http, #{<<"port">> => 5280, + <<"handlers">> => #{Type => + [Opts#{<<"host">> => <<"localhost">>, + <<"path">> => <<"/api">>}] + }}). listen_raw(Type, Opts) -> - #{<<"listen">> => #{Type => [Opts#{<<"port">> => 5222}]}}. + #{<<"listen">> => #{atom_to_binary(Type) => [remove_undefined(Opts)]}}. + +remove_undefined(M) -> + maps:filter(fun(_, V) -> V =/= undefined end, M). %% helpers for 'auth' tests @@ -3282,9 +3154,14 @@ assert_option(Key, Value, Config) -> get_config_value([TopKey | Rest], Config) -> case lists:keyfind(TopKey, 1, Config) of false -> ct:fail({"option not found", TopKey, Config}); - {_, TopValue} -> lists:foldl(fun maps:get/2, TopValue, Rest) + {_, TopValue} -> lists:foldl(fun get_value/2, TopValue, Rest) end. +get_value(Index, List) when is_integer(Index), Index > 0, is_list(List) -> + lists:nth(Index, List); +get_value(Key, Map) when not is_integer(Key), is_map(Map) -> + maps:get(Key, Map). + %% helpers for file tests test_config_file(Config, File) -> @@ -3341,12 +3218,8 @@ handle_listener(V1, V2) -> handle_listener_option({tls, O1}, {tls, O2}) -> compare_unordered_lists(O1, O2); -handle_listener_option({ssl, O1}, {ssl, O2}) -> - compare_unordered_lists(O1, O2); -handle_listener_option({modules, M1}, {modules, M2}) -> +handle_listener_option({handlers, M1}, {handlers, M2}) -> compare_unordered_lists(M1, M2, fun handle_listener_module/2); -handle_listener_option({transport_options, O1}, {transport_options, O2}) -> - compare_unordered_lists(O1, O2); handle_listener_option(V1, V2) -> ?eq(V1, V2). handle_listener_module({H1, P1, M1}, M2) -> diff --git a/test/cowboy_SUITE.erl b/test/cowboy_SUITE.erl index 2ca54c7541c..4500c72733d 100644 --- a/test/cowboy_SUITE.erl +++ b/test/cowboy_SUITE.erl @@ -25,6 +25,7 @@ stop_ejabberd/0, use_config_file/2, start_ejabberd_with_config/2]). +-import(config_parser_helper, [default_config/1]). %%-------------------------------------------------------------------- %% Suite configuration @@ -214,12 +215,14 @@ mixed_requests(_Config) -> Responses = lists:duplicate(50, {TextPong, true, TextPong, true}). start_cowboy_returns_error_eaddrinuse(_C) -> - Opts = [{transport_options, #{socket_opts => [{port, 8088}, - {ip, {127, 0, 0, 1}}]}}, - {modules, []}, - {retries, {2, 10}}], - {ok, _Pid} = ejabberd_cowboy:start_cowboy(a_ref, Opts), - Result = ejabberd_cowboy:start_cowboy(a_ref_2, Opts), + Opts = #{port => 8088, + ip_tuple => {127, 0, 0, 1}, + ip_address => "127.0.0.1", + handlers => [], + transport => default_config([listen, http, transport]), + protocol => default_config([listen, http, protocol])}, + {ok, _Pid} = ejabberd_cowboy:start_cowboy(a_ref, Opts, 2, 10), + Result = ejabberd_cowboy:start_cowboy(a_ref_2, Opts, 2, 10), {error, eaddrinuse} = Result. %%-------------------------------------------------------------------- diff --git a/test/ejabberd_c2s_SUITE.erl b/test/ejabberd_c2s_SUITE.erl index dae2efd8d15..f5b01b5a338 100644 --- a/test/ejabberd_c2s_SUITE.erl +++ b/test/ejabberd_c2s_SUITE.erl @@ -1,14 +1,12 @@ -module(ejabberd_c2s_SUITE). -include_lib("eunit/include/eunit.hrl"). --include("ejabberd_c2s.hrl"). -include_lib("exml/include/exml_stream.hrl"). -compile([export_all, nowarn_export_all]). --define(_eq(E, I), ?_assertEqual(E, I)). -define(eq(E, I), ?assertEqual(E, I)). -define(am(E, I), ?assertMatch(E, I)). --define(ne(E, I), ?assert(E =/= I)). +-import(config_parser_helper, [config/2]). all() -> [ c2s_start_stop_test, @@ -108,7 +106,7 @@ c2s_is_killed_when_too_many_messages_in_the_queue(_) -> (Event, StateName, ProcState) -> meck:passthrough([Event, StateName, ProcState]) end), - {ok, C2SPid} = given_c2s_started([{max_fsm_queue, MaxQueueSize}]), + {ok, C2SPid} = given_c2s_started(#{max_fsm_queue => MaxQueueSize}), %% We want to monitor the c2s process and not being linked to it Ref = erlang:monitor(process, C2SPid), @@ -236,20 +234,19 @@ setsession_stanza() -> []}]}. given_c2s_started() -> - given_c2s_started([]). + given_c2s_started(#{}). -given_c2s_started(Opts) -> - ejabberd_c2s:start_link({ejabberd_socket, self()}, - Opts ++ c2s_default_opts()). +given_c2s_started(ExtraOpts) -> + ejabberd_c2s:start_link({ejabberd_socket, self()}, c2s_opts(ExtraOpts)). when_c2s_is_stopped(Pid) -> stop_c2s(Pid), sync_c2s(Pid). -c2s_default_opts() -> - [{access, c2s}, - {shaper, c2s_shaper}, - {max_stanza_size, 65536}]. +c2s_opts(ExtraOpts) -> + config([listen, c2s], ExtraOpts#{access => c2s, + shaper => c2s_shaper, + max_stanza_size => 65536}). stop_c2s(C2SPid) when is_pid(C2SPid) -> _R = ejabberd_c2s:stop(C2SPid). diff --git a/test/mod_websockets_SUITE.erl b/test/mod_websockets_SUITE.erl index ea589ecc3a9..db12a181603 100644 --- a/test/mod_websockets_SUITE.erl +++ b/test/mod_websockets_SUITE.erl @@ -1,7 +1,7 @@ -module(mod_websockets_SUITE). -compile([export_all, nowarn_export_all]). -include_lib("eunit/include/eunit.hrl"). --define(HANDSHAKE_TIMEOUT, 3000). + -define(eq(E, I), ?assertEqual(E, I)). -define(PORT, 5280). -define(IP, {127, 0, 0, 1}). @@ -12,6 +12,7 @@ %The 300ms is just an additional overhead -define(IDLE_TIMEOUT, ?NEW_TIMEOUT * 2 + 300). +-import(config_parser_helper, [default_config/1]). all() -> ping_tests() ++ subprotocol_header_tests() ++ timeout_tests(). @@ -57,20 +58,23 @@ setup() -> meck:expect(gen_mod,get_opt, fun(ping_rate, _, none) -> ?FAST_PING_RATE; (A, B, C) -> meck:passthrough([A, B, C]) end), meck:expect(supervisor, start_child, - fun(ejabberd_listeners, {_, {_, start_link, [_]}, transient, - infinity, worker, [_]}) -> {ok, self()}; - (A,B) -> meck:passthrough([A,B]) + fun(mongoose_listener_sup, _) -> {ok, self()}; + (A, B) -> meck:passthrough([A, B]) end), %% Start websocket cowboy listening - Opts = [{num_acceptors, 10}, - {max_connections, 1024}, - {modules, [{"_", "/http-bind", mod_bosh}, - {"_", "/ws-xmpp", mod_websockets, - [{timeout, ?IDLE_TIMEOUT}, - {ping_rate, ?FAST_PING_RATE}]}]}], - ejabberd_cowboy:start_listener({?PORT, ?IP, tcp}, Opts). - + Handlers = [{"_", "/http-bind", mod_bosh}, + {"_", "/ws-xmpp", mod_websockets, + [{timeout, ?IDLE_TIMEOUT}, + {ping_rate, ?FAST_PING_RATE}]}], + ejabberd_cowboy:start_listener(#{port => ?PORT, + ip_tuple => ?IP, + ip_address => "127.0.0.1", + ip_version => 4, + proto => tcp, + handlers => Handlers, + transport => default_config([listen, http, transport]), + protocol => default_config([listen, http, protocol])}). teardown() -> meck:unload(), diff --git a/test/ejabberd_listener_SUITE.erl b/test/mongoose_listener_SUITE.erl similarity index 74% rename from test/ejabberd_listener_SUITE.erl rename to test/mongoose_listener_SUITE.erl index 209402fb2d8..3625a72f1ea 100644 --- a/test/ejabberd_listener_SUITE.erl +++ b/test/mongoose_listener_SUITE.erl @@ -1,4 +1,4 @@ --module(ejabberd_listener_SUITE). +-module(mongoose_listener_SUITE). -compile([export_all, nowarn_export_all]). @@ -15,14 +15,11 @@ all() -> tcp_socket_supports_proxy_protocol, tcp_socket_has_connection_details, tcp_socket_supports_proxy_protocol, - udp_socket_is_started_with_defaults, tcp_start_stop_reload ]. init_per_testcase(_Case, Config) -> - meck:new([gen_udp, gen_tcp], [unstick, passthrough]), - meck:expect(gen_udp, open, - fun(Port, Opts) -> meck:passthrough([Port, Opts]) end), + meck:new([gen_tcp], [unstick, passthrough]), meck:expect(gen_tcp, listen, fun(Port, Opts) -> meck:passthrough([Port, Opts]) end), Config. @@ -39,24 +36,24 @@ end_per_suite(_C) -> mnesia:delete_schema([node()]). tcp_socket_is_started_with_default_backlog(_C) -> - {ok, _Pid} = listener_started(#{}), + ok = listener_started(#{}), [{_Pid, {gen_tcp, listen, [_, Opts]}, _Result}] = meck:history(gen_tcp), 100 = proplists:get_value(backlog, Opts). tcp_socket_is_started_with_options(_C) -> - {ok, _Pid} = listener_started(#{backlog => 50}), + ok = listener_started(#{backlog => 50}), [{_Pid, {gen_tcp, listen, [_, Opts]}, _Result}] = meck:history(gen_tcp), 50 = proplists:get_value(backlog, Opts). tcp_socket_has_connection_details(_C) -> - {ok, _Pid} = listener_started(#{}), + ok = listener_started(#{}), {Port, _, _} = tcp_port_ip(), meck:new(ejabberd_socket), TestPid = self(), meck:expect(ejabberd_socket, start, - fun(_Module, _SockMode, Socket, Opts) -> - TestPid ! {socket_started, Socket, Opts}, + fun(_Module, _SockMode, Socket, Opts, ConnectionDetails) -> + TestPid ! {socket_started, Socket, Opts, ConnectionDetails}, ok end), @@ -64,8 +61,7 @@ tcp_socket_has_connection_details(_C) -> {ok, SrcPort} = inet:port(Socket), receive - {socket_started, _Socket, Opts} -> - ConnectionDetails = proplists:get_value(connection_details, Opts), + {socket_started, _Socket, _Opts, ConnectionDetails} -> ?assertEqual(#{proxy => false, src_address => {127, 0, 0, 1}, src_port => SrcPort, @@ -77,7 +73,7 @@ tcp_socket_has_connection_details(_C) -> end. tcp_socket_supports_proxy_protocol(_C) -> - {ok, _Pid} = listener_started(#{proxy_protocol => true}), + ok = listener_started(#{proxy_protocol => true}), CommonProxyInfo = #{src_address => {1, 2, 3, 4}, src_port => 444, @@ -92,8 +88,8 @@ tcp_socket_supports_proxy_protocol(_C) -> meck:new(ejabberd_socket), TestPid = self(), meck:expect(ejabberd_socket, start, - fun(_Module, _SockMode, Socket, Opts) -> - TestPid ! {socket_started, Socket, Opts}, + fun(_Module, _SockMode, Socket, Opts, ConnectionDetails) -> + TestPid ! {socket_started, Socket, Opts, ConnectionDetails}, ok end), @@ -101,45 +97,31 @@ tcp_socket_supports_proxy_protocol(_C) -> ok = gen_tcp:send(Socket, [ranch_proxy_header:header(RanchProxyInfo)]), receive - {socket_started, _Socket, Opts} -> - ConnectionDetails = proplists:get_value(connection_details, Opts), + {socket_started, _Socket, _Opts, ConnectionDetails} -> ?assertEqual(CommonProxyInfo#{proxy => true}, ConnectionDetails) after 5000 -> ct:fail(timeout_waiting_for_tcp_with_proxy_protocol) end. -udp_socket_is_started_with_defaults(_C) -> - {ok, _Pid} = receiver_started(#{}), - - [{_Pid, {gen_udp, open, [_, Opts]}, _Result}] = meck:history(gen_udp), - - {0,0,0,0} = proplists:get_value(ip, Opts). - listener_started(Opts) -> mim_ct_sup:start_link(ejabberd_sup), - ejabberd_listener:start_link(), - ejabberd_listener:start_listener(maps:merge(listener_opts(tcp), Opts)). - -receiver_started(Opts) -> - mim_ct_sup:start_link(ejabberd_sup), - ejabberd_listener:start_link(), - ets:new(listen_sockets, [named_table, public]), - ejabberd_listener:start_listener(maps:merge(listener_opts(udp), Opts)). - -udp_port_ip() -> - {1805, {0,0,0,0}, udp}. + mongoose_listener_sup:start_link(), + mongoose_listener:start_listener(maps:merge(listener_opts(), Opts)). tcp_port_ip() -> - {1805, {0,0,0,0}, tcp}. + {1805, {0, 0, 0, 0}, tcp}. -listener_opts(Proto) -> +listener_opts() -> #{module => ?MODULE, - ip_address => "0", + port => 1805, ip_tuple => {0, 0, 0, 0}, + ip_address => "0", ip_version => 4, - port => 1805, - proto => Proto}. + proto => tcp, + num_acceptors => 1, + backlog => 100, + proxy_protocol => false}. tcp_start_stop_reload(C) -> %% start server @@ -149,10 +131,11 @@ tcp_start_stop_reload(C) -> %% make sure all ports are open lists:map(fun assert_open/1, ?DEFAULT_PORTS), %% stop listeners, now they should be closed - ejabberd_listener:stop_listeners(), + Listeners = mongoose_config:get_opt(listen), + lists:foreach(fun mongoose_listener:stop_listener/1, Listeners), lists:map(fun assert_closed/1, ?DEFAULT_PORTS), %% and start them all again - ejabberd_listener:start_listeners(), + lists:foreach(fun mongoose_listener:start_listener/1, Listeners), lists:map(fun assert_open/1, ?DEFAULT_PORTS), %% we want to make sure that connection to an unchanged port survives reload @@ -194,3 +177,6 @@ assert_connected(Sock, Port) -> socket_type() -> xml_stream. + +start_listener(Opts) -> + mongoose_tcp_listener:start_listener(Opts). diff --git a/test/ejabberd_listener_SUITE_data/mongooseim.basic.toml b/test/mongoose_listener_SUITE_data/mongooseim.basic.toml similarity index 100% rename from test/ejabberd_listener_SUITE_data/mongooseim.basic.toml rename to test/mongoose_listener_SUITE_data/mongooseim.basic.toml