1: -module(ejabberd_listener_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -include_lib("eunit/include/eunit.hrl").
    6: 
    7: -import(ejabberd_helper, [copy/2,
    8:                           data/2]).
    9: 
   10: -define(DEFAULT_PORTS, [5222, 5280, 5269]).
   11: 
   12: all() ->
   13:     [tcp_socket_is_started_with_default_backlog,
   14:      tcp_socket_is_started_with_options,
   15:      tcp_socket_supports_proxy_protocol,
   16:      tcp_socket_has_connection_details,
   17:      tcp_socket_supports_proxy_protocol,
   18:      udp_socket_is_started_with_defaults,
   19:      tcp_start_stop_reload
   20:      ].
   21: 
   22: init_per_testcase(_Case, Config) ->
   23:     meck:new([gen_udp, gen_tcp], [unstick, passthrough]),
   24:     meck:expect(gen_udp, open,
   25:                 fun(Port, Opts) -> meck:passthrough([Port, Opts]) end),
   26:     meck:expect(gen_tcp, listen,
   27:                 fun(Port, Opts) -> meck:passthrough([Port, Opts]) end),
   28:     Config.
   29: 
   30: end_per_testcase(_Case, Config) ->
   31:     meck:unload(),
   32:     Config.
   33: 
   34: init_per_suite(C) ->
   35:    C.
   36: 
   37: end_per_suite(_C) ->
   38:     mnesia:stop(),
   39:     mnesia:delete_schema([node()]).
   40: 
   41: tcp_socket_is_started_with_default_backlog(_C) ->
   42:    {ok, _Pid} = listener_started([]),
   43: 
   44:    [{_Pid, {gen_tcp, listen, [_, Opts]}, _Result}] =  meck:history(gen_tcp),
   45: 
   46:     100 = proplists:get_value(backlog, Opts).
   47: 
   48: 
   49: tcp_socket_is_started_with_options(_C) ->
   50: 
   51:     OverrideBacklog = {backlog, 50},
   52:     {ok, _Pid} = listener_started([OverrideBacklog]),
   53: 
   54:     [{_Pid, {gen_tcp, listen, [_, Opts]}, _Result}] =  meck:history(gen_tcp),
   55: 
   56:     50 = proplists:get_value(backlog, Opts).
   57: 
   58: tcp_socket_has_connection_details(_C) ->
   59:     {ok, _Pid} = listener_started([]),
   60: 
   61:     {Port, _, _} = tcp_port_ip(),
   62: 
   63:     meck:new(ejabberd_socket),
   64:     TestPid = self(),
   65:     meck:expect(ejabberd_socket, start,
   66:                 fun(_Module, _SockMode, Socket, Opts) ->
   67:                         TestPid ! {socket_started, Socket, Opts},
   68:                         ok
   69:                 end),
   70: 
   71:     {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
   72:     {ok, SrcPort} = inet:port(Socket),
   73: 
   74:     receive
   75:         {socket_started, _Socket, Opts} ->
   76:             ConnectionDetails = proplists:get_value(connection_details, Opts),
   77:             ?assertEqual(#{proxy => false,
   78:                            src_address => {127, 0, 0, 1},
   79:                            src_port => SrcPort,
   80:                            dest_address => {127, 0, 0, 1},
   81:                            dest_port => Port}, ConnectionDetails)
   82:     after
   83:         5000 ->
   84:             ct:fail(timeout_waiting_for_tcp)
   85:     end.
   86: 
   87: tcp_socket_supports_proxy_protocol(_C) ->
   88:     ProxyProtocol = {proxy_protocol, true},
   89:     {ok, _Pid} = listener_started([ProxyProtocol]),
   90: 
   91:     CommonProxyInfo = #{src_address => {1, 2, 3, 4},
   92:                         src_port => 444,
   93:                         dest_address => {192, 168, 0, 1},
   94:                         dest_port => 443,
   95:                         version => 2},
   96:     RanchProxyInfo = CommonProxyInfo#{command => proxy,
   97:                                       transport_family => ipv4,
   98:                                       transport_protocol => stream},
   99:     {Port, _, _} = tcp_port_ip(),
  100: 
  101:     meck:new(ejabberd_socket),
  102:     TestPid = self(),
  103:     meck:expect(ejabberd_socket, start,
  104:                 fun(_Module, _SockMode, Socket, Opts) ->
  105:                         TestPid ! {socket_started, Socket, Opts},
  106:                         ok
  107:                 end),
  108: 
  109:     {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
  110:     ok = gen_tcp:send(Socket, [ranch_proxy_header:header(RanchProxyInfo)]),
  111: 
  112:     receive
  113:         {socket_started, _Socket, Opts} ->
  114:             ConnectionDetails = proplists:get_value(connection_details, Opts),
  115:             ?assertEqual(CommonProxyInfo#{proxy => true}, ConnectionDetails)
  116:     after
  117:         5000 ->
  118:             ct:fail(timeout_waiting_for_tcp_with_proxy_protocol)
  119:     end.
  120: 
  121: udp_socket_is_started_with_defaults(_C) ->
  122:     {ok, _Pid} = receiver_started([]),
  123: 
  124:     [{_Pid, {gen_udp, open, [_, Opts]}, _Result}] =  meck:history(gen_udp),
  125: 
  126:     {0,0,0,0} = proplists:get_value(ip, Opts).
  127: 
  128: listener_started(RawOpts) ->
  129:     {tcp, Opts, SockOpts, Port, IPS} =
  130:         ejabberd_listener:opts_to_listener_args(tcp_port_ip(), [{acceptors_num, 5} | RawOpts]),
  131:     mongoose_tcp_listener:start_link(tcp_port_ip(), ?MODULE, Opts, SockOpts, Port, IPS).
  132: 
  133: receiver_started(RawOpts) ->
  134:     ets:new(listen_sockets, [named_table, public]),
  135:     {udp, Opts, SockOpts, Port, IPS} =
  136:         ejabberd_listener:opts_to_listener_args(udp_port_ip(), RawOpts),
  137:     mongoose_udp_listener:start_link(udp_port_ip(), ?MODULE, Opts, SockOpts, Port, IPS).
  138: 
  139: udp_port_ip() ->
  140:     {1805, {0,0,0,0}, udp}.
  141: 
  142: tcp_port_ip() ->
  143:     {1805, {0,0,0,0}, tcp}.
  144: 
  145: tcp_start_stop_reload(C) ->
  146:     %% start server
  147:     copy(data(C, "mongooseim.basic.toml"), data(C, "mongooseim.toml")),
  148:     ejabberd_helper:start_ejabberd_with_config(C, "mongooseim.toml"),
  149:     ?assert(lists:keymember(mongooseim, 1, application:which_applications())),
  150:     %% make sure all ports are open
  151:     lists:map(fun assert_open/1, ?DEFAULT_PORTS),
  152:     %% stop listeners, now they should be closed
  153:     ejabberd_listener:stop_listeners(),
  154:     lists:map(fun assert_closed/1, ?DEFAULT_PORTS),
  155:     %% and start them all again
  156:     ejabberd_listener:start_listeners(),
  157:     lists:map(fun assert_open/1, ?DEFAULT_PORTS),
  158: 
  159:     %% we want to make sure that connection to an unchanged port survives reload
  160:     UnchPort = 5222,
  161:     {ok, Sock} = gen_tcp:connect("localhost", UnchPort,[{active, false}, {packet, 2}]),
  162:     assert_connected(Sock, UnchPort),
  163: 
  164:     %% and that to the changed port does too (this is current implementation)
  165:     ChgPort = 5269,
  166:     {ok, Sock2} = gen_tcp:connect("localhost", ChgPort,[{active, false}, {packet, 2}]),
  167:     assert_connected(Sock2, ChgPort),
  168: 
  169:     ok = ejabberd_helper:stop_ejabberd(),
  170:     ok.
  171: 
  172: assert_open(PortNo) ->
  173:     case gen_tcp:connect("localhost", PortNo, [{active, false}, {packet, 2}]) of
  174:         {ok, Socket} ->
  175:             gen_tcp:close(Socket),
  176:             ok;
  177:         E ->
  178:             ct:fail("Failed: port ~p is closed, should be open; error was: ~p", [PortNo, E])
  179:     end .
  180: 
  181: assert_closed(PortNo) ->
  182:     F = fun() ->
  183:               gen_tcp:connect("localhost", PortNo, [{active, false}, {packet, 2}])
  184:         end,
  185:     async_helper:wait_until(F, {error, econnrefused}).
  186: 
  187: 
  188: assert_connected(Sock, Port) ->
  189:     case gen_tcp:recv(Sock, 0, 500) of
  190:         {error, timeout} ->
  191:             ok;
  192:         Else ->
  193:             ct:fail("Failed: connection to ~p is broken, error was: ~p", [Port, Else])
  194:     end.
  195: 
  196: %%assert_disconnected(Sock, Port) ->
  197: %%    case gen_tcp:recv(Sock, 0, 500) of
  198: %%        {error, timeout} ->
  199: %%            ct:fail("Failed: connection to ~p is still open", [Port]),
  200: %%            ok;
  201: %%        _ ->
  202: %%            ok
  203: %%    end.