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:     [{_Pid, {gen_tcp, listen, [_, Opts]}, _Result}] =  meck:history(gen_tcp),
   44:     100 = proplists:get_value(backlog, Opts).
   45: 
   46: tcp_socket_is_started_with_options(_C) ->
   47:     {ok, _Pid} = listener_started(#{backlog => 50}),
   48:     [{_Pid, {gen_tcp, listen, [_, Opts]}, _Result}] =  meck:history(gen_tcp),
   49:     50 = proplists:get_value(backlog, Opts).
   50: 
   51: tcp_socket_has_connection_details(_C) ->
   52:     {ok, _Pid} = listener_started(#{}),
   53:     {Port, _, _} = tcp_port_ip(),
   54: 
   55:     meck:new(ejabberd_socket),
   56:     TestPid = self(),
   57:     meck:expect(ejabberd_socket, start,
   58:                 fun(_Module, _SockMode, Socket, Opts) ->
   59:                         TestPid ! {socket_started, Socket, Opts},
   60:                         ok
   61:                 end),
   62: 
   63:     {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
   64:     {ok, SrcPort} = inet:port(Socket),
   65: 
   66:     receive
   67:         {socket_started, _Socket, Opts} ->
   68:             ConnectionDetails = proplists:get_value(connection_details, Opts),
   69:             ?assertEqual(#{proxy => false,
   70:                            src_address => {127, 0, 0, 1},
   71:                            src_port => SrcPort,
   72:                            dest_address => {127, 0, 0, 1},
   73:                            dest_port => Port}, ConnectionDetails)
   74:     after
   75:         5000 ->
   76:             ct:fail(timeout_waiting_for_tcp)
   77:     end.
   78: 
   79: tcp_socket_supports_proxy_protocol(_C) ->
   80:     {ok, _Pid} = listener_started(#{proxy_protocol => true}),
   81: 
   82:     CommonProxyInfo = #{src_address => {1, 2, 3, 4},
   83:                         src_port => 444,
   84:                         dest_address => {192, 168, 0, 1},
   85:                         dest_port => 443,
   86:                         version => 2},
   87:     RanchProxyInfo = CommonProxyInfo#{command => proxy,
   88:                                       transport_family => ipv4,
   89:                                       transport_protocol => stream},
   90:     {Port, _, _} = tcp_port_ip(),
   91: 
   92:     meck:new(ejabberd_socket),
   93:     TestPid = self(),
   94:     meck:expect(ejabberd_socket, start,
   95:                 fun(_Module, _SockMode, Socket, Opts) ->
   96:                         TestPid ! {socket_started, Socket, Opts},
   97:                         ok
   98:                 end),
   99: 
  100:     {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
  101:     ok = gen_tcp:send(Socket, [ranch_proxy_header:header(RanchProxyInfo)]),
  102: 
  103:     receive
  104:         {socket_started, _Socket, Opts} ->
  105:             ConnectionDetails = proplists:get_value(connection_details, Opts),
  106:             ?assertEqual(CommonProxyInfo#{proxy => true}, ConnectionDetails)
  107:     after
  108:         5000 ->
  109:             ct:fail(timeout_waiting_for_tcp_with_proxy_protocol)
  110:     end.
  111: 
  112: udp_socket_is_started_with_defaults(_C) ->
  113:     {ok, _Pid} = receiver_started(#{}),
  114: 
  115:     [{_Pid, {gen_udp, open, [_, Opts]}, _Result}] =  meck:history(gen_udp),
  116: 
  117:     {0,0,0,0} = proplists:get_value(ip, Opts).
  118: 
  119: listener_started(Opts) ->
  120:     mim_ct_sup:start_link(ejabberd_sup),
  121:     ejabberd_listener:start_link(),
  122:     ejabberd_listener:start_listener(maps:merge(listener_opts(tcp), Opts)).
  123: 
  124: receiver_started(Opts) ->
  125:     mim_ct_sup:start_link(ejabberd_sup),
  126:     ejabberd_listener:start_link(),
  127:     ets:new(listen_sockets, [named_table, public]),
  128:     ejabberd_listener:start_listener(maps:merge(listener_opts(udp), Opts)).
  129: 
  130: udp_port_ip() ->
  131:     {1805, {0,0,0,0}, udp}.
  132: 
  133: tcp_port_ip() ->
  134:     {1805, {0,0,0,0}, tcp}.
  135: 
  136: listener_opts(Proto) ->
  137:     #{module => ?MODULE,
  138:       ip_address => "0",
  139:       ip_tuple => {0, 0, 0, 0},
  140:       ip_version => 4,
  141:       port => 1805,
  142:       proto => Proto}.
  143: 
  144: tcp_start_stop_reload(C) ->
  145:     %% start server
  146:     copy(data(C, "mongooseim.basic.toml"), data(C, "mongooseim.toml")),
  147:     ejabberd_helper:start_ejabberd_with_config(C, "mongooseim.toml"),
  148:     ?assert(lists:keymember(mongooseim, 1, application:which_applications())),
  149:     %% make sure all ports are open
  150:     lists:map(fun assert_open/1, ?DEFAULT_PORTS),
  151:     %% stop listeners, now they should be closed
  152:     ejabberd_listener:stop_listeners(),
  153:     lists:map(fun assert_closed/1, ?DEFAULT_PORTS),
  154:     %% and start them all again
  155:     ejabberd_listener:start_listeners(),
  156:     lists:map(fun assert_open/1, ?DEFAULT_PORTS),
  157: 
  158:     %% we want to make sure that connection to an unchanged port survives reload
  159:     UnchPort = 5222,
  160:     {ok, Sock} = gen_tcp:connect("localhost", UnchPort,[{active, false}, {packet, 2}]),
  161:     assert_connected(Sock, UnchPort),
  162: 
  163:     %% and that to the changed port does too (this is current implementation)
  164:     ChgPort = 5269,
  165:     {ok, Sock2} = gen_tcp:connect("localhost", ChgPort,[{active, false}, {packet, 2}]),
  166:     assert_connected(Sock2, ChgPort),
  167: 
  168:     ok = ejabberd_helper:stop_ejabberd(),
  169:     ok.
  170: 
  171: assert_open(PortNo) ->
  172:     case gen_tcp:connect("localhost", PortNo, [{active, false}, {packet, 2}]) of
  173:         {ok, Socket} ->
  174:             gen_tcp:close(Socket),
  175:             ok;
  176:         E ->
  177:             ct:fail("Failed: port ~p is closed, should be open; error was: ~p", [PortNo, E])
  178:     end .
  179: 
  180: assert_closed(PortNo) ->
  181:     F = fun() ->
  182:               gen_tcp:connect("localhost", PortNo, [{active, false}, {packet, 2}])
  183:         end,
  184:     async_helper:wait_until(F, {error, econnrefused}).
  185: 
  186: 
  187: assert_connected(Sock, Port) ->
  188:     case gen_tcp:recv(Sock, 0, 500) of
  189:         {error, timeout} ->
  190:             ok;
  191:         Else ->
  192:             ct:fail("Failed: connection to ~p is broken, error was: ~p", [Port, Else])
  193:     end.
  194: 
  195: socket_type() ->
  196:     xml_stream.