aboutsummaryrefslogblamecommitdiffstats
path: root/lib/diameter/test/diameter_gen_sctp_SUITE.erl
blob: 1e2baa79b301ca3f22c6cac9c681cb0a01580357 (plain) (tree)
1
2
3
4


                   
                                                        































































                                                                              
                           

                                 


                                               






































































































                                                                              

                              














































                                                                     




                                                                      







































































                                                                      
                                   

                                


                                                  


















                                                                
                                                     




















                                                                              
             
 





                                                                  
 








                                                               
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%

%%
%% Some gen_sctp-specific tests demonstrating problems that were
%% encountered during diameter development but have nothing
%% specifically to do with diameter. At least one of them can cause
%% diameter_transport_SUITE testcases to fail.
%%

-module(diameter_gen_sctp_SUITE).

-export([suite/0,
         all/0,
         init_per_suite/1,
         end_per_suite/1]).

%% testcases
-export([send_not_from_controlling_process/1,
         send_from_multiple_clients/1,
         receive_what_was_sent/1]).

-include_lib("kernel/include/inet_sctp.hrl").

%% Message from gen_sctp are of this form.
-define(SCTP(Sock, Data), {sctp, Sock, _, _, Data}).

%% Open sockets on the loopback address.
-define(ADDR, {127,0,0,1}).

%% Snooze, nap, siesta.
-define(SLEEP(T), receive after T -> ok end).

%% An indescribably long number of milliseconds after which everthing
%% that should have happened has.
-define(FOREVER, 2000).

%% The first byte in each message we send as a simple guard against
%% not receiving what was sent.
-define(MAGIC, 42).

%% ===========================================================================

suite() ->
    [{timetrap, {minutes, 2}}].

all() ->
    [send_not_from_controlling_process,
     send_from_multiple_clients,
     receive_what_was_sent].

init_per_suite(Config) ->
    case gen_sctp:open() of
        {ok, Sock} ->
            gen_sctp:close(Sock),
            Config;
        {error, E} when E == eprotonosupport;
                        E == esocktnosupport ->
            {skip, no_sctp}
    end.

end_per_suite(_Config) ->
    ok.

%% ===========================================================================

%% send_not_from_controlling_process/1
%%
%% This testcase failing shows gen_sctp:send/4 hanging when called
%% outside the controlling process of the socket in question.

send_not_from_controlling_process(_) ->
    Pids = send_not_from_controlling_process(),
    ?SLEEP(?FOREVER),
    try
        [] = [{P,I} || P <- Pids, I <- [process_info(P)], I /= undefined]
    after
        lists:foreach(fun(P) -> exit(P, kill) end, Pids)
    end.

%% send_not_from_controlling_process/0
%%
%% Returns the pids of three spawned processes: a listening process, a
%% connecting process and a sending process.
%%
%% The expected behaviour is that all three processes exit:
%%
%% - The listening process exits upon receiving an SCTP message
%%   sent by the sending process.
%% - The connecting process exits upon listening process exit.
%% - The sending process exits upon gen_sctp:send/4 return.
%%
%% The observed behaviour is that all three processes remain alive
%% indefinitely:
%%
%% - The listening process never receives the SCTP message sent
%%   by the sending process.
%% - The connecting process has an inet_reply message in its mailbox
%%   as a consequence of the call to gen_sctp:send/4 call from the
%%   sending process.
%% - The call to gen_sctp:send/4 in the sending process doesn't return,
%%   hanging in prim_inet:getopts/2.

send_not_from_controlling_process() ->
    FPid = self(),
    {L, MRef} = spawn_monitor(fun() -> listen(FPid) end),%% listening process
    receive
        {?MODULE, C, S} ->
            erlang:demonitor(MRef, [flush]),
            [L,C,S];
        {'DOWN', MRef, process, _, _} = T ->
            error(T)
    end.

%% listen/1

listen(FPid) ->
    {ok, Sock} = open(),
    ok = gen_sctp:listen(Sock, true),
    {ok, PortNr} = inet:port(Sock),
    LPid = self(),
    spawn(fun() -> connect1(PortNr, FPid, LPid) end), %% connecting process
    Id = assoc(Sock),
    ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], _Bin})
        = recv(). %% Waits with this as current_function.

%% recv/0

recv() ->
    receive T -> T end.

%% connect1/3

connect1(PortNr, FPid, LPid) ->
    {ok, Sock} = open(),
    ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []),
    Id = assoc(Sock),
    FPid ! {?MODULE,
            self(),
            spawn(fun() -> send(Sock, Id) end)}, %% sending process
    MRef = erlang:monitor(process, LPid),
    down(MRef).  %% Waits with this as current_function.

%% down/1

down(MRef) ->
    receive {'DOWN', MRef, process, _, Reason} -> Reason end.

%% send/2

send(Sock, Id) ->
    ok = gen_sctp:send(Sock, Id, 0, <<0:32>>).

%% ===========================================================================

%% send_from_multiple_clients/0
%%
%% Demonstrates sluggish delivery of messages.

send_from_multiple_clients(_) ->
    {S, Rs} = T = send_from_multiple_clients(8, 1024),
    Max = ?FOREVER*1000,
    {false, [], _} = {Max < S,
                      Rs -- [OI || {O,_} = OI <- Rs, is_integer(O)],
                      T}.

%% send_from_multiple_clients/2
%%
%% Opens a listening socket and then spawns a specified number of
%% processes, each of which connects to the listening socket. Each
%% connecting process then sends a message, whose size in bytes is
%% passed as an argument, the listening process sends a reply
%% containing the time at which the message was received, and the
%% connecting process then exits upon reception of this reply.
%%
%% Returns the elapsed time for all connecting process to exit
%% together with a list of exit reasons for the connecting processes.
%% In the successful case a connecting process exits with the
%% outbound/inbound transit times for the sent/received message as
%% reason.
%%
%% The observed behaviour is that some outbound messages (that is,
%% from a connecting process to the listening process) can take an
%% unexpectedly long time to complete their journey. The more
%% connecting processes, the longer the possible delay it seems.
%%
%% eg. (With F = fun send_from_multiple_clients/2.)
%%
%%     5> F(2, 1024).
%%     {875,[{128,116},{113,139}]}
%%     6> F(4, 1024).
%%     {2995290,[{2994022,250},{2994071,80},{200,130},{211,113}]}
%%     7> F(8, 1024).
%%     {8997461,[{8996161,116},
%%               {2996471,86},
%%               {2996278,116},
%%               {2996360,95},
%%               {246,112},
%%               {213,159},
%%               {373,173},
%%               {376,118}]}
%%     8> F(8, 1024).
%%     {21001891,[{20999968,128},
%%                {8997891,172},
%%                {8997927,91},
%%                {2995716,164},
%%                {2995860,87},
%%                {134,100},
%%                {117,98},
%%                {149,125}]}
%%
%% This turns out to have been due to SCTP resends as a consequence of
%% the listener having an insufficient recbuf. Increasing the size
%% solves the problem.
%%

send_from_multiple_clients(N, Sz)
  when is_integer(N), 0 < N, is_integer(Sz), 0 < Sz ->
    timer:tc(fun listen/2, [N, <<?MAGIC, 0:Sz/unit:8>>]).

%% listen/2

listen(N, Bin) ->
    {ok, Sock} = open(),
    ok = gen_sctp:listen(Sock, true),
    {ok, PortNr} = inet:port(Sock),

    %% Spawn a middleman that in turn spawns N connecting processes,
    %% collects a list of exit reasons and then exits with the list as
    %% reason. loop/3 returns when we receive this list from the
    %% middleman's 'DOWN'.

    Self = self(),
    Fun = fun() -> exit(connect2(Self, PortNr, Bin)) end,
    {_, MRef} = spawn_monitor(fun() -> exit(fold(N, Fun)) end),
    loop(Sock, MRef, Bin).

%% fold/2
%%
%% Spawn N processes and collect their exit reasons in a list.

fold(N, Fun) ->
    start(N, Fun),
    acc(N, []).

start(0, _) ->
    ok;
start(N, Fun) ->
    spawn_monitor(Fun),
    start(N-1, Fun).

acc(0, Acc) ->
    Acc;
acc(N, Acc) ->
    receive
        {'DOWN', _MRef, process, _, RC} ->
            acc(N-1, [RC | Acc])
    end.

%% loop/3

loop(Sock, MRef, Bin) ->
    receive
        ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], B}) ->
            Sz = size(Bin),
            {Sz, Bin} = {size(B), B},  %% assert
            ok = send(Sock, Id, mark(Bin)),
            loop(Sock, MRef, Bin);
        ?SCTP(Sock, _) ->
            loop(Sock, MRef, Bin);
        {'DOWN', MRef, process, _, Reason} ->
            Reason
    end.

%% connect2/3

connect2(Pid, PortNr, Bin) ->
    erlang:monitor(process, Pid),

    {ok, Sock} = open(),
    ok = gen_sctp:connect_init(Sock, ?ADDR, PortNr, []),
    Id = assoc(Sock),

    %% T1 = time before send
    %% T2 = time after listening process received our message
    %% T3 = time after reply is received

    T1 = diameter_util:timestamp(),
    ok = send(Sock, Id, Bin),
    T2 = unmark(recv(Sock, Id)),
    T3 = diameter_util:timestamp(),
    {diameter_lib:micro_diff(T2, T1),  %% Outbound
     diameter_lib:micro_diff(T3, T2)}. %% Inbound

%% recv/2

recv(Sock, Id) ->
    receive
        ?SCTP(Sock, {[#sctp_sndrcvinfo{assoc_id = Id}], Bin}) ->
            Bin;
        T ->  %% eg. 'DOWN'
            exit(T)
    end.

%% send/3

send(Sock, Id, Bin) ->
    gen_sctp:send(Sock, Id, 0, Bin).

%% mark/1

mark(Bin) ->
    Info = term_to_binary(diameter_util:timestamp()),
    <<Info/binary, Bin/binary>>.

%% unmark/1

unmark(Bin) ->
    {_,_,_} = binary_to_term(Bin).

%% ===========================================================================

%% receive_what_was_sent/1
%%
%% Demonstrates reception of a message that differs from that sent.

receive_what_was_sent(_Config) ->
    send_from_multiple_clients(1, 1024*32).  %% fails

%% ===========================================================================

%% open/0

open() ->
    open([]).

%% open/1

open(Opts) ->
    gen_sctp:open([{ip, ?ADDR}, {port, 0}, {active, true}, binary,
                   {recbuf, 1 bsl 16}, {sndbuf, 1 bsl 16}
                   | Opts]).

%% assoc/1

assoc(Sock) ->
    receive
        ?SCTP(Sock, {[], #sctp_assoc_change{state = S,
                                            assoc_id = Id}}) ->
            comm_up = S,  %% assert
            Id
    end.