%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
-module(gen_sctp_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
%%-compile(export_all).
-export([all/0, suite/0,groups/0,
init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
init_per_testcase/2, end_per_testcase/2]).
-export(
[basic/1,
api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1,
xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1,
open_multihoming_ipv4_socket/1,
open_unihoming_ipv6_socket/1,
open_multihoming_ipv6_socket/1,
open_multihoming_ipv4_and_ipv6_socket/1,
basic_stream/1, xfer_stream_min/1, active_n/1,
peeloff_active_once/1, peeloff_active_true/1, peeloff_active_n/1,
buffers/1,
names_unihoming_ipv4/1, names_unihoming_ipv6/1,
names_multihoming_ipv4/1, names_multihoming_ipv6/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{minutes,1}}].
all() ->
[basic, api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
open_multihoming_ipv4_socket,
open_unihoming_ipv6_socket,
open_multihoming_ipv6_socket,
open_multihoming_ipv4_and_ipv6_socket, active_n,
basic_stream, xfer_stream_min, peeloff_active_once,
peeloff_active_true, peeloff_active_n, buffers,
names_unihoming_ipv4, names_unihoming_ipv6,
names_multihoming_ipv4, names_multihoming_ipv6].
groups() ->
[].
init_per_suite(_Config) ->
case gen_sctp:open() of
{ok,Socket} ->
gen_sctp:close(Socket),
[];
{error,Error}
when Error =:= eprotonosupport;
Error =:= esocktnosupport ->
{skip,"SCTP not supported on this machine"}
end.
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
init_per_testcase(_Func, Config) ->
Config.
end_per_testcase(_Func, _Config) ->
ok.
-define(LOGVAR(Var), begin io:format(??Var" = ~p~n", [Var]) end).
%% Hello world.
basic(Config) when is_list(Config) ->
{ok,S} = gen_sctp:open(),
ok = gen_sctp:close(S),
ok.
%% Minimal data transfer.
xfer_min(Config) when is_list(Config) ->
Stream = 0,
Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
Loopback = {127,0,0,1},
{ok,Sb} = gen_sctp:open([{type,seqpacket}]),
{ok,Pb} = inet:port(Sb),
ok = gen_sctp:listen(Sb, true),
{ok,Sa} = gen_sctp:open(),
{ok,Pa} = inet:port(Sa),
{ok,#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=SaOutboundStreams,
inbound_streams=SaInboundStreams,
assoc_id=SaAssocId}=SaAssocChange} =
gen_sctp:connect(Sa, Loopback, Pb, []),
{SbAssocId,SaOutboundStreams,SaInboundStreams} =
case recv_event(log_ok(gen_sctp:recv(Sb, infinity))) of
{Loopback,Pa,
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=SbOutboundStreams,
inbound_streams=SbInboundStreams,
assoc_id=AssocId}} ->
{AssocId,SbInboundStreams,SbOutboundStreams};
{Loopback,Pa,
#sctp_paddr_change{state=addr_confirmed,
addr={Loopback,Pa},
error=0,
assoc_id=AssocId}} ->
{Loopback,Pa,
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=SbOutboundStreams,
inbound_streams=SbInboundStreams,
assoc_id=AssocId}} =
recv_event(log_ok(gen_sctp:recv(Sb, infinity))),
{AssocId,SbInboundStreams,SbOutboundStreams}
end,
ok = gen_sctp:send(Sa, SaAssocId, 0, Data),
case log_ok(gen_sctp:recv(Sb, infinity)) of
{Loopback,
Pa,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
Data} -> ok;
Event1 ->
case recv_event(Event1) of
{Loopback,Pa,
#sctp_paddr_change{addr = {Loopback,_},
state = State,
error = 0,
assoc_id = SbAssocId}}
when State =:= addr_available;
State =:= addr_confirmed ->
{Loopback,
Pa,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
Data} = log_ok(gen_sctp:recv(Sb, infinity))
end
end,
ok = gen_sctp:send(Sb, SbAssocId, 0, Data),
case log_ok(gen_sctp:recv(Sa, infinity)) of
{Loopback,Pb,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SaAssocId}],
Data} ->
ok;
Event2 ->
{Loopback,Pb,
#sctp_paddr_change{addr={_,Pb},
state=addr_confirmed,
error=0,
assoc_id=SaAssocId}} =
recv_event(Event2),
{Loopback,
Pb,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SaAssocId}],
Data} =
log_ok(gen_sctp:recv(Sa, infinity))
end,
%%
ok = gen_sctp:eof(Sa, SaAssocChange),
{Loopback,Pa,#sctp_shutdown_event{assoc_id=SbAssocId}} =
recv_event(log_ok(gen_sctp:recv(Sb, infinity))),
{Loopback,Pb,
#sctp_assoc_change{state=shutdown_comp,
error=0,
assoc_id=SaAssocId}} =
recv_event(log_ok(gen_sctp:recv(Sa, infinity))),
{Loopback,Pa,
#sctp_assoc_change{state=shutdown_comp,
error=0,
assoc_id=SbAssocId}} =
recv_event(log_ok(gen_sctp:recv(Sb, infinity))),
ok = gen_sctp:close(Sa),
ok = gen_sctp:close(Sb),
receive
Msg -> ct:fail({received,Msg})
after 17 -> ok
end,
ok.
%% Minimal data transfer in active mode.
xfer_active(Config) when is_list(Config) ->
Timeout = 2000,
Stream = 0,
Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
Loopback = {127,0,0,1},
{ok,Sb} = gen_sctp:open([{active,true}]),
{ok,Pb} = inet:port(Sb),
ok = gen_sctp:listen(Sb, true),
{ok,Sa} = gen_sctp:open([{active,true}]),
{ok,Pa} = inet:port(Sa),
ok = gen_sctp:connect_init(Sa, Loopback, Pb, []),
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=SaOutboundStreams,
inbound_streams=SaInboundStreams,
assoc_id=SaAssocId} = SaAssocChange =
recv_assoc_change(Sa, Loopback, Pb, Timeout),
io:format("Sa=~p, Pa=~p, Sb=~p, Pb=~p, SaAssocId=~p, "
"SaOutboundStreams=~p, SaInboundStreams=~p~n",
[Sa,Pa,Sb,Pb,SaAssocId,
SaOutboundStreams,SaInboundStreams]),
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=SbOutboundStreams,
inbound_streams=SbInboundStreams,
assoc_id=SbAssocId} =
recv_assoc_change(Sb, Loopback, Pa, Timeout),
SbOutboundStreams = SaInboundStreams,
SbInboundStreams = SaOutboundStreams,
io:format("SbAssocId=~p~n", [SbAssocId]),
case recv_paddr_change(Sa, Loopback, Pb, 314) of
#sctp_paddr_change{state=addr_confirmed,
addr={_,Pb},
error=0,
assoc_id=SaAssocId} -> ok;
#sctp_paddr_change{state=addr_available,
addr={_,Pb},
error=0,
assoc_id=SaAssocId} -> ok;
timeout -> ok
end,
case recv_paddr_change(Sb, Loopback, Pa, 314) of
#sctp_paddr_change{state=addr_confirmed,
addr={Loopback,Pa},
error=0,
assoc_id=SbAssocId} -> ok;
#sctp_paddr_change{state=addr_available,
addr={Loopback,P},
error=0,
assoc_id=SbAssocId} ->
match_unless_solaris(Pa, P);
timeout -> ok
end,
[] = flush(),
ok =
do_from_other_process(
fun () -> gen_sctp:send(Sa, SaAssocId, 0, Data) end),
receive
{sctp,Sb,Loopback,Pa,
{[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
Data}} -> ok
after Timeout ->
ct:fail({timeout,flush()})
end,
ok = gen_sctp:send(Sb, SbAssocId, 0, Data),
receive
{sctp,Sa,Loopback,Pb,
{[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SaAssocId}],
Data}} -> ok
after Timeout ->
ct:fail({timeout,flush()})
end,
%%
ok = gen_sctp:abort(Sa, SaAssocChange),
case recv_assoc_change(Sb, Loopback, Pa, Timeout) of
#sctp_assoc_change{state=comm_lost,
assoc_id=SbAssocId} -> ok;
timeout ->
ct:fail({timeout,flush()})
end,
ok = gen_sctp:close(Sb),
case recv_assoc_change(Sa, Loopback, Pb, Timeout) of
#sctp_assoc_change{state=comm_lost,
assoc_id=SaAssocId} -> ok;
timeout ->
io:format("timeout waiting for comm_lost on Sa~n"),
match_unless_solaris(ok, {timeout,flush()})
end,
receive
{sctp_error,Sa,enotconn} -> ok % Solaris
after 17 -> ok
end,
ok = gen_sctp:close(Sa),
%%
receive
Msg -> ct:fail({unexpected,[Msg]++flush()})
after 17 -> ok
end,
ok.
recv_assoc_change(S, Addr, Port, Timeout) ->
receive
{sctp,S,Addr,Port,{[], #sctp_assoc_change{}=AssocChange}} ->
AssocChange;
{sctp,S,Addr,Port,
{[#sctp_sndrcvinfo{assoc_id=AssocId}],
#sctp_assoc_change{assoc_id=AssocId}=AssocChange}} ->
AssocChange
after Timeout ->
timeout
end.
recv_paddr_change(S, Addr, Port, Timeout) ->
receive
{sctp,S,Addr,Port,{[], #sctp_paddr_change{}=PaddrChange}} ->
PaddrChange;
{sctp,S,Addr,Port,
{[#sctp_sndrcvinfo{assoc_id=AssocId}],
#sctp_paddr_change{assoc_id=AssocId}=PaddrChange}} ->
PaddrChange
after Timeout ->
timeout
end.
%% Test that #sctp_sndrcvinfo{} parameters set on a socket
%% are used by gen_sctp:send/4.
def_sndrcvinfo(Config) when is_list(Config) ->
Loopback = {127,0,0,1},
Data = <<"What goes up, must come down.">>,
%%
S1 =
log_ok(gen_sctp:open(
0, [{sctp_default_send_param,#sctp_sndrcvinfo{ppid=17}}])),
?LOGVAR(S1),
P1 =
log_ok(inet:port(S1)),
?LOGVAR(P1),
#sctp_sndrcvinfo{ppid=17, context=0, timetolive=0, assoc_id=0} =
getopt(S1, sctp_default_send_param),
ok =
gen_sctp:listen(S1, true),
%%
S2 =
log_ok(gen_sctp:open()),
?LOGVAR(S2),
P2 =
log_ok(inet:port(S2)),
?LOGVAR(P2),
#sctp_sndrcvinfo{ppid=0, context=0, timetolive=0, assoc_id=0} =
getopt(S2, sctp_default_send_param),
%%
#sctp_assoc_change{
state=comm_up,
error=0,
assoc_id=S2AssocId} = S2AssocChange =
log_ok(gen_sctp:connect(S2, Loopback, P1, [])),
?LOGVAR(S2AssocChange),
case recv_event(log_ok(gen_sctp:recv(S1))) of
{Loopback,P2,
#sctp_assoc_change{
state=comm_up,
error=0,
assoc_id=S1AssocId}} ->
?LOGVAR(S1AssocId);
{Loopback,P2,
#sctp_paddr_change{
state=addr_confirmed,
error=0,
assoc_id=S1AssocId}} ->
?LOGVAR(S1AssocId),
{Loopback,P2,
#sctp_assoc_change{
state=comm_up,
error=0,
assoc_id=S1AssocId}} =
recv_event(log_ok(gen_sctp:recv(S1)))
end,
#sctp_sndrcvinfo{
ppid=17, context=0, timetolive=0} = %, assoc_id=S1AssocId} =
getopt(
S1, sctp_default_send_param, #sctp_sndrcvinfo{assoc_id=S1AssocId}),
#sctp_sndrcvinfo{
ppid=0, context=0, timetolive=0} = %, assoc_id=S2AssocId} =
getopt(
S2, sctp_default_send_param, #sctp_sndrcvinfo{assoc_id=S2AssocId}),
%%
ok =
gen_sctp:send(S1, S1AssocId, 1, <<"1: ",Data/binary>>),
case log_ok(gen_sctp:recv(S2)) of
{Loopback,P1,
[#sctp_sndrcvinfo{
stream=1, ppid=17, context=0, assoc_id=S2AssocId}],
<<"1: ",Data/binary>>} -> ok;
Event1 ->
{Loopback,P1,
#sctp_paddr_change{state=addr_confirmed,
addr={_,P1},
error=0,
assoc_id=S2AssocId}} =
recv_event(Event1),
{Loopback,P1,
[#sctp_sndrcvinfo{
stream=1, ppid=17, context=0, assoc_id=S2AssocId}],
<<"1: ",Data/binary>>} =
log_ok(gen_sctp:recv(S2))
end,
%%
ok =
setopt(
S1, sctp_default_send_param, #sctp_sndrcvinfo{ppid=18}),
ok =
setopt(
S1, sctp_default_send_param,
#sctp_sndrcvinfo{ppid=19, assoc_id=S1AssocId}),
#sctp_sndrcvinfo{
ppid=18, context=0, timetolive=0, assoc_id=0} =
getopt(S1, sctp_default_send_param),
#sctp_sndrcvinfo{
ppid=19, context=0, timetolive=0, assoc_id=S1AssocId} =
getopt(
S1, sctp_default_send_param, #sctp_sndrcvinfo{assoc_id=S1AssocId}),
%%
ok =
gen_sctp:send(S1, S1AssocId, 0, <<"2: ",Data/binary>>),
case log_ok(gen_sctp:recv(S2)) of
{Loopback,P1,
[#sctp_sndrcvinfo{
stream=0, ppid=19, context=0, assoc_id=S2AssocId}],
<<"2: ",Data/binary>>} -> ok
end,
ok =
gen_sctp:send(S2, S2AssocChange, 1, <<"3: ",Data/binary>>),
case log_ok(gen_sctp:recv(S1)) of
{Loopback,P2,
[#sctp_sndrcvinfo{
stream=1, ppid=0, context=0, assoc_id=S1AssocId}],
<<"3: ",Data/binary>>} -> ok;
Event2 ->
case recv_event(Event2) of
{Loopback,P2,
#sctp_paddr_change{
addr={Loopback,_},
state=State,
error=0, assoc_id=S1AssocId}}
when State =:= addr_available;
State =:= addr_confirmed ->
case log_ok(gen_sctp:recv(S1)) of
{Loopback,P2,
[#sctp_sndrcvinfo{
stream=1, ppid=0, context=0,
assoc_id=S1AssocId}],
<<"3: ",Data/binary>>} -> ok
end
end
end,
ok =
do_from_other_process(
fun () ->
gen_sctp:send(
S2,
#sctp_sndrcvinfo{stream=0, ppid=20, assoc_id=S2AssocId},
<<"4: ",Data/binary>>)
end),
case log_ok(do_from_other_process(fun() -> gen_sctp:recv(S1) end)) of
{Loopback,P2,
[#sctp_sndrcvinfo{
stream=0, ppid=20, context=0, assoc_id=S1AssocId}],
<<"4: ",Data/binary>>} -> ok
end,
%%
ok =
gen_sctp:close(S1),
ok =
gen_sctp:close(S2),
receive
Msg ->
ct:fail({received,Msg})
after 17 -> ok
end,
ok.
getopt(S, Opt) ->
{ok,[{Opt,Val}]} = inet:getopts(S, [Opt]),
Val.
getopt(S, Opt, Param) ->
{ok,[{Opt,Val}]} = inet:getopts(S, [{Opt,Param}]),
Val.
setopt(S, Opt, Val) ->
inet:setopts(S, [{Opt,Val}]).
log_ok(X) -> log(ok(X)).
ok({ok,X}) -> X.
err([], Result) ->
erlang:error(Result);
err([Reason|_], {error,Reason}) ->
ok;
err([_|Reasons], Result) ->
err(Reasons, Result).
log(X) ->
io:format("LOG[~w]: ~p~n", [self(),X]),
X.
flush() ->
receive
Msg ->
[Msg|flush()]
after 17 ->
[]
end.
%% Test the API function open/1,2 and close/1.
api_open_close(Config) when is_list(Config) ->
{ok,S1} = gen_sctp:open(0),
{ok,P} = inet:port(S1),
ok = gen_sctp:close(S1),
{ok,S2} = gen_sctp:open(P),
{ok,P} = inet:port(S2),
ok = gen_sctp:close(S2),
{ok,S3} = gen_sctp:open([{port,P}]),
{ok,P} = inet:port(S3),
ok = gen_sctp:close(S3),
{ok,S4} = gen_sctp:open(P, []),
{ok,P} = inet:port(S4),
ok = gen_sctp:close(S4),
{ok,S5} = gen_sctp:open(P, [{ifaddr,any}]),
{ok,P} = inet:port(S5),
ok = gen_sctp:close(S5),
ok = gen_sctp:close(S5),
try gen_sctp:close(0)
catch error:badarg -> ok
end,
try gen_sctp:open({})
catch error:badarg -> ok
end,
try gen_sctp:open(-1)
catch error:badarg -> ok
end,
try gen_sctp:open(65536)
catch error:badarg -> ok
end,
try gen_sctp:open(make_ref(), [])
catch error:badarg -> ok
end,
try gen_sctp:open(0, {})
catch error:badarg -> ok
end,
try gen_sctp:open(0, [make_ref()])
catch error:badarg -> ok
end,
try gen_sctp:open([{invalid_option,0}])
catch error:badarg -> ok
end,
try gen_sctp:open(0, [{mode,invalid_mode}])
catch error:badarg -> ok
end,
ok.
%% Test the API function listen/2.
api_listen(Config) when is_list(Config) ->
Localhost = {127,0,0,1},
try gen_sctp:listen(0, true)
catch error:badarg -> ok
end,
{ok,S} = gen_sctp:open(),
{ok,Pb} = inet:port(S),
try gen_sctp:listen(S, not_allowed_for_listen)
catch error:badarg -> ok
end,
ok = gen_sctp:close(S),
{error,closed} = gen_sctp:listen(S, true),
{ok,Sb} = gen_sctp:open(Pb),
{ok,Sa} = gen_sctp:open(),
case gen_sctp:connect(Sa, localhost, Pb, []) of
{error,econnrefused} ->
{ok,{Localhost,
Pb,[],
#sctp_assoc_change{
state=comm_lost}}} =
gen_sctp:recv(Sa, infinity);
{error,#sctp_assoc_change{state=cant_assoc}} ->
ok%;
%% {error,{Localhost,Pb,_,#sctp_assoc_change{state=cant_assoc}}} ->
%% ok
end,
ok = gen_sctp:listen(Sb, true),
{ok,#sctp_assoc_change{state=comm_up,
error=0}} =
gen_sctp:connect(Sa, localhost, Pb, []),
ok = gen_sctp:close(Sa),
ok = gen_sctp:close(Sb),
ok.
%% Test the API function connect_init/4.
api_connect_init(Config) when is_list(Config) ->
Localhost = {127,0,0,1},
{ok,S} = gen_sctp:open(),
{ok,Pb} = inet:port(S),
try gen_sctp:connect_init(S, Localhost, not_allowed_for_port, [])
catch error:badarg -> ok
end,
try gen_sctp:connect_init(S, Localhost, 12345, not_allowed_for_opts)
catch error:badarg -> ok
end,
ok = gen_sctp:close(S),
{error,closed} = gen_sctp:connect_init(S, Localhost, 12345, []),
{ok,Sb} = gen_sctp:open(Pb),
{ok,Sa} = gen_sctp:open(),
case gen_sctp:connect_init(Sa, localhost, Pb, []) of
{error,econnrefused} ->
{Localhost,Pb,#sctp_assoc_change{state=comm_lost}} =
recv_event(log_ok(gen_sctp:recv(Sa, infinity)));
ok ->
{Localhost,Pb,#sctp_assoc_change{state=cant_assoc}} =
recv_event(log_ok(gen_sctp:recv(Sa, infinity)))
end,
ok = gen_sctp:listen(Sb, true),
case gen_sctp:connect_init(Sa, localhost, Pb, []) of
ok ->
{Localhost,Pb,#sctp_assoc_change{state=comm_up}} =
recv_event(log_ok(gen_sctp:recv(Sa, infinity)))
end,
ok = gen_sctp:close(Sa),
ok = gen_sctp:close(Sb),
ok.
recv_event({Addr,Port,[],#sctp_assoc_change{}=AssocChange}) ->
{Addr,Port,AssocChange};
recv_event({Addr,Port,
[#sctp_sndrcvinfo{assoc_id=Assoc}],
#sctp_assoc_change{assoc_id=Assoc}=AssocChange}) ->
{Addr,Port,AssocChange};
recv_event({Addr,Port,[],#sctp_paddr_change{}=PaddrChange}) ->
{Addr,Port,PaddrChange};
recv_event({Addr,Port,
[#sctp_sndrcvinfo{assoc_id=Assoc}],
#sctp_paddr_change{assoc_id=Assoc}=PaddrChange}) ->
{Addr,Port,PaddrChange};
recv_event({Addr,Port,[],#sctp_shutdown_event{}=ShutdownEvent}) ->
{Addr,Port,ShutdownEvent};
recv_event({Addr,Port,
[#sctp_sndrcvinfo{assoc_id=Assoc}],
#sctp_shutdown_event{assoc_id=Assoc}=ShutdownEvent}) ->
{Addr,Port,ShutdownEvent}.
%% Test socket options.
api_opts(Config) when is_list(Config) ->
Sndbuf = 32768,
Recbuf = 65536,
{ok,S} = gen_sctp:open(0),
OSType = os:type(),
case {inet:setopts(S, [{linger,{true,2}}]),OSType} of
{ok,_} ->
ok;
{{error,einval},{unix,sunos}} ->
ok
end,
ok = inet:setopts(S, [{sndbuf,Sndbuf}]),
ok = inet:setopts(S, [{recbuf,Recbuf}]),
case inet:getopts(S, [sndbuf]) of
{ok,[{sndbuf,SB}]} when SB >= Sndbuf -> ok
end,
case inet:getopts(S, [recbuf]) of
{ok,[{recbuf,RB}]} when RB >= Recbuf -> ok
end.
implicit_inet6(Config) when is_list(Config) ->
Hostname = log_ok(inet:gethostname()),
case gen_sctp:open(0, [inet6]) of
{ok,S1} ->
case inet:getaddr(Hostname, inet6) of
{ok,Host} ->
Loopback = {0,0,0,0,0,0,0,1},
io:format("~s ~p~n", ["Loopback",Loopback]),
implicit_inet6(S1, Loopback),
ok = gen_sctp:close(S1),
%%
Localhost =
log_ok(inet:getaddr("localhost", inet6)),
io:format("~s ~p~n", ["localhost",Localhost]),
S2 =
log_ok(gen_sctp:open(0, [{ip,Localhost}])),
implicit_inet6(S2, Localhost),
ok = gen_sctp:close(S2),
%%
io:format("~s ~p~n", [Hostname,Host]),
S3 =
log_ok(gen_sctp:open(0, [{ifaddr,Host}])),
implicit_inet6(S3, Host),
ok = gen_sctp:close(S1);
{error,eafnosupport} ->
ok = gen_sctp:close(S1),
{skip,"Can not look up IPv6 address"}
end;
_ ->
{skip,"IPv6 not supported"}
end.
implicit_inet6(S1, Addr) ->
ok = gen_sctp:listen(S1, true),
P1 = log_ok(inet:port(S1)),
S2 = log_ok(gen_sctp:open(0, [inet6])),
P2 = log_ok(inet:port(S2)),
#sctp_assoc_change{state=comm_up} =
log_ok(gen_sctp:connect(S2, Addr, P1, [])),
case recv_event(log_ok(gen_sctp:recv(S1))) of
{Addr,P2,#sctp_assoc_change{state=comm_up}} ->
ok;
{Addr,P2,#sctp_paddr_change{state=addr_confirmed,
addr={Addr,P2},
error=0}} ->
{Addr,P2,#sctp_assoc_change{state=comm_up}} =
recv_event(log_ok(gen_sctp:recv(S1)))
end,
case log_ok(inet:sockname(S1)) of
{Addr,P1} -> ok;
{{0,0,0,0,0,0,0,0},P1} -> ok
end,
case log_ok(inet:sockname(S2)) of
{Addr,P2} -> ok;
{{0,0,0,0,0,0,0,0},P2} -> ok
end,
ok = gen_sctp:close(S2).
%% Verify {active,N} socket management.
active_n(Config) when is_list(Config) ->
N = 3,
S1 = ok(gen_sctp:open([{active,N}])),
[{active,N}] = ok(inet:getopts(S1, [active])),
ok = inet:setopts(S1, [{active,-N}]),
receive
{sctp_passive, S1} -> ok
after
5000 ->
exit({error,sctp_passive_failure})
end,
[{active,false}] = ok(inet:getopts(S1, [active])),
ok = inet:setopts(S1, [{active,0}]),
receive
{sctp_passive, S1} -> ok
after
5000 ->
exit({error,sctp_passive_failure})
end,
ok = inet:setopts(S1, [{active,32767}]),
{error,einval} = inet:setopts(S1, [{active,1}]),
{error,einval} = inet:setopts(S1, [{active,-32769}]),
ok = inet:setopts(S1, [{active,-32768}]),
receive
{sctp_passive, S1} -> ok
after
5000 ->
exit({error,sctp_passive_failure})
end,
[{active,false}] = ok(inet:getopts(S1, [active])),
ok = inet:setopts(S1, [{active,N}]),
ok = inet:setopts(S1, [{active,true}]),
[{active,true}] = ok(inet:getopts(S1, [active])),
receive
_ -> exit({error,active_n})
after
0 ->
ok
end,
ok = inet:setopts(S1, [{active,N}]),
ok = inet:setopts(S1, [{active,once}]),
[{active,once}] = ok(inet:getopts(S1, [active])),
receive
_ -> exit({error,active_n})
after
0 ->
ok
end,
{error,einval} = inet:setopts(S1, [{active,32768}]),
ok = inet:setopts(S1, [{active,false}]),
[{active,false}] = ok(inet:getopts(S1, [active])),
ok = gen_sctp:listen(S1, true),
S1Port = ok(inet:port(S1)),
S2 = ok(gen_sctp:open(0, [{active,false}])),
Assoc = ok(gen_sctp:connect(S2, "localhost", S1Port, [])),
ok = inet:setopts(S1, [{active,N}]),
[{active,N}] = ok(inet:getopts(S1, [active])),
LoopFun = fun(Count, Count, _Fn) ->
receive
{sctp_passive,S1} ->
ok
after
5000 ->
exit({error,timeout})
end;
(I, Count, Fn) ->
Msg = list_to_binary("message "++integer_to_list(I)),
ok = gen_sctp:send(S2, Assoc, 0, Msg),
receive
{sctp,S1,_,_,{[SR],Msg}} when is_record(SR, sctp_sndrcvinfo) ->
Fn(I+1, Count, Fn);
{sctp,S1,_,_,_} ->
%% ignore non-data messages
ok = inet:setopts(S1, [{active,1}]),
Fn(I, Count, Fn);
Other ->
exit({unexpected, Other})
after
5000 ->
exit({error,timeout})
end
end,
ok = LoopFun(1, N, LoopFun),
S3 = ok(gen_sctp:open([{active,0}])),
receive
{sctp_passive,S3} ->
[{active,false}] = ok(inet:getopts(S3, [active]))
after
5000 ->
exit({error,udp_passive})
end,
ok = gen_sctp:close(S3),
ok = gen_sctp:close(S2),
ok = gen_sctp:close(S1),
ok.
%% Hello world stream socket.
basic_stream(Config) when is_list(Config) ->
{ok,S} = gen_sctp:open([{type,stream}]),
ok = gen_sctp:listen(S, true),
ok =
do_from_other_process(
fun () -> gen_sctp:listen(S, 10) end),
ok = gen_sctp:close(S),
ok.
%% Minimal data transfer.
xfer_stream_min(Config) when is_list(Config) ->
Stream = 0,
Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>,
Loopback = {127,0,0,1},
{ok,Sb} = gen_sctp:open([{type,seqpacket}]),
?LOGVAR(Sb),
{ok,Pb} = inet:port(Sb),
?LOGVAR(Pb),
ok = gen_sctp:listen(Sb, true),
{ok,Sa} = gen_sctp:open([{type,stream}]),
?LOGVAR(Sa),
{ok,Pa} = inet:port(Sa),
?LOGVAR(Pa),
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=SaOutboundStreams,
inbound_streams=SaInboundStreams,
assoc_id=SaAssocId_X} =
log_ok(gen_sctp:connect(Sa, Loopback, Pb, [])),
?LOGVAR(SaAssocId_X),
[{_,#sctp_paddrinfo{assoc_id=SaAssocId,state=active}}] =
log_ok(inet:getopts(Sa, [{sctp_get_peer_addr_info,
#sctp_paddrinfo{address={Loopback,Pb}}}])),
?LOGVAR(SaAssocId),
match_unless_solaris(SaAssocId_X, SaAssocId),
{SbOutboundStreams,SbInboundStreams,SbAssocId} =
case recv_event(log_ok(gen_sctp:recv(Sb, infinity))) of
{Loopback,Pa,
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=OS,
inbound_streams=IS,
assoc_id=AI}} ->
{OS,IS,AI};
{Loopback,Pa,
#sctp_paddr_change{state=addr_confirmed,
addr={Loopback,Pa},
error=0,
assoc_id=AI}} ->
{Loopback,Pa,
#sctp_assoc_change{state=comm_up,
error=0,
outbound_streams=OS,
inbound_streams=IS,
assoc_id=AI}} =
recv_event(log_ok(gen_sctp:recv(Sb, infinity))),
{OS,IS,AI}
end,
?LOGVAR(SbAssocId),
SaOutboundStreams = SbInboundStreams,
?LOGVAR(SaOutboundStreams),
SbOutboundStreams = SaInboundStreams,
?LOGVAR(SbOutboundStreams),
ok = gen_sctp:send(Sa, SaAssocId, 0, Data),
case log_ok(gen_sctp:recv(Sb, infinity)) of
{Loopback,
Pa,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
Data} -> ok;
{Loopback,
Pa,[],
#sctp_paddr_change{addr = {Loopback,_},
state = addr_available,
error = 0,
assoc_id = SbAssocId}} ->
{Loopback,
Pa,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
Data} = log_ok(gen_sctp:recv(Sb, infinity));
{Loopback,
Pa,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
#sctp_paddr_change{addr = {Loopback,_},
state = addr_confirmed,
error = 0,
assoc_id = SbAssocId}} ->
{Loopback,
Pa,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SbAssocId}],
Data} = log_ok(gen_sctp:recv(Sb, infinity))
end,
ok =
do_from_other_process(
fun () -> gen_sctp:send(Sb, SbAssocId, 0, Data) end),
case log_ok(gen_sctp:recv(Sa, infinity)) of
{Loopback,Pb,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SaAssocId}],
Data} -> ok;
Event1 ->
{Loopback,Pb,
#sctp_paddr_change{state=addr_confirmed,
addr={_,Pb},
error=0,
assoc_id=SaAssocId}} =
recv_event(Event1),
{Loopback,Pb,
[#sctp_sndrcvinfo{stream=Stream,
assoc_id=SaAssocId}],
Data} =
log_ok(gen_sctp:recv(Sa, infinity))
end,
ok = gen_sctp:close(Sa),
{Loopback,Pa,
#sctp_shutdown_event{assoc_id=SbAssocId}} =
recv_event(log_ok(gen_sctp:recv(Sb, infinity))),
{Loopback,Pa,
#sctp_assoc_change{state=shutdown_comp,
error=0,
assoc_id=SbAssocId}} =
recv_event(log_ok(gen_sctp:recv(Sb, infinity))),
ok = gen_sctp:close(Sb),
receive
Msg -> ct:fail({received,Msg})
after 17 -> ok
end,
ok.
do_from_other_process(Fun) ->
Parent = self(),
Ref = make_ref(),
Child =
spawn(fun () ->
try Fun() of
Result ->
Parent ! {Ref,Result}
catch
Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
Parent ! {Ref,Class,Reason,Stacktrace}
end
end),
Mref = erlang:monitor(process, Child),
receive
{Ref,Result} ->
receive {'DOWN',Mref,_,_,_} -> Result end;
{Ref,Class,Reason,Stacktrace} ->
receive {'DOWN',Mref,_,_,_} ->
erlang:raise(Class, Reason, Stacktrace)
end;
{'DOWN',Mref,_,_,Reason} ->
erlang:exit(Reason)
end.
%% Peel off an SCTP stream socket ({active,once}).
peeloff_active_once(Config) ->
peeloff(Config, [{active,once}]).
%% Peel off an SCTP stream socket ({active,true}).
peeloff_active_true(Config) ->
peeloff(Config, [{active,true}]).
%% Peel off an SCTP stream socket ({active,N}).
peeloff_active_n(Config) ->
peeloff(Config, [{active,1}]).
peeloff(Config, SockOpts) when is_list(Config) ->
Addr = {127,0,0,1},
Stream = 0,
Timeout = 333,
S1 = socket_open([{ifaddr,Addr}|SockOpts], Timeout),
?LOGVAR(S1),
P1 = socket_call(S1, get_port),
?LOGVAR(P1),
Socket1 = socket_call(S1, get_socket),
?LOGVAR(Socket1),
socket_call(S1, {listen,true}),
S2 = socket_open([{ifaddr,Addr}|SockOpts], Timeout),
?LOGVAR(S2),
P2 = socket_call(S2, get_port),
?LOGVAR(P2),
Socket2 = socket_call(S2, get_socket),
?LOGVAR(Socket2),
%%
socket_call(S2, {connect_init,Addr,P1,[]}),
S2Ai =
receive
{S2,{Addr,P1,
#sctp_assoc_change{
state=comm_up,
assoc_id=AssocId2}}} -> AssocId2
after Timeout ->
socket_bailout([S1,S2])
end,
?LOGVAR(S2Ai),
S1Ai =
receive
{S1,{Addr,P2,
#sctp_assoc_change{
state=comm_up,
assoc_id=AssocId1}}} -> AssocId1
after Timeout ->
socket_bailout([S1,S2])
end,
?LOGVAR(S1Ai),
%%
socket_call(S2, {send,S2Ai,Stream,<<"Number one">>}),
receive
{S1,{Addr,P2,S1Ai,Stream,<<"Number one">>}} -> ok
after Timeout ->
socket_bailout([S1,S2])
end,
socket_call(S2, {send,Socket1,S1Ai,Stream,<<"Number two">>}),
receive
{S2,{Addr,P1,S2Ai,Stream,<<"Number two">>}} -> ok
after Timeout ->
socket_bailout([S1,S2])
end,
%%
S3 = socket_peeloff(Socket1, S1Ai, SockOpts, Timeout),
?LOGVAR(S3),
P3_X = socket_call(S3, get_port),
?LOGVAR(P3_X),
P3 = case P3_X of 0 -> P1; _ -> P3_X end,
[{_,#sctp_paddrinfo{assoc_id=S3Ai,state=active}}] =
socket_call(S3,
{getopts,[{sctp_get_peer_addr_info,
#sctp_paddrinfo{address={Addr,P2}}}]}),
%%S3Ai = S1Ai,
?LOGVAR(S3Ai),
%%
socket_call(S3, {send,S3Ai,Stream,<<"Number three">>}),
receive
{S2,{Addr,P3,S2Ai,Stream,<<"Number three">>}} -> ok
after Timeout ->
socket_bailout([S1,S2,S3])
end,
socket_call(S3, {send,Socket2,S2Ai,Stream,<<"Number four">>}),
receive
{S3,{Addr,P2,S3Ai,Stream,<<"Number four">>}} -> ok
after Timeout ->
socket_bailout([S1,S2,S3])
end,
%%
inet:i(sctp),
socket_close_verbose(S1),
socket_close_verbose(S2),
receive
{S3,{Addr,P2,#sctp_shutdown_event{assoc_id=S3Ai_X}}} ->
match_unless_solaris(S3Ai, S3Ai_X)
after Timeout ->
socket_bailout([S3])
end,
receive
{S3,{Addr,P2,#sctp_assoc_change{state=shutdown_comp,
assoc_id=S3Ai}}} -> ok
after Timeout ->
socket_bailout([S3])
end,
socket_close_verbose(S3),
[] = flush(),
ok.
%% Check sndbuf and recbuf behaviour.
buffers(Config) when is_list(Config) ->
Limit = 4096,
Addr = {127,0,0,1},
Stream = 1,
Timeout = 3333,
S1 = socket_open([{ip,Addr}], Timeout),
?LOGVAR(S1),
P1 = socket_call(S1, get_port),
?LOGVAR(P1),
ok = socket_call(S1, {listen,true}),
S2 = socket_open([{ip,Addr}], Timeout),
?LOGVAR(S2),
P2 = socket_call(S2, get_port),
?LOGVAR(P2),
%%
socket_call(S2, {connect_init,Addr,P1,[]}),
S2Ai =
receive
{S2,{Addr,P1,
#sctp_assoc_change{
state=comm_up,
assoc_id=AssocId2}}} -> AssocId2
after Timeout ->
socket_bailout([S1,S2])
end,
S1Ai =
receive
{S1,{Addr,P2,
#sctp_assoc_change{
state=comm_up,
assoc_id=AssocId1}}} -> AssocId1
after Timeout ->
socket_bailout([S1,S2])
end,
%%
socket_call(S1, {setopts,[{recbuf,Limit}]}),
Recbuf =
case socket_call(S1, {getopts,[recbuf]}) of
[{recbuf,RB1}] when RB1 >= Limit -> RB1
end,
Data = mk_data(Recbuf+Limit),
socket_call(S2, {setopts,[{sndbuf,Recbuf+Limit}]}),
socket_call(S2, {send,S2Ai,Stream,Data}),
receive
{S1,{Addr,P2,S1Ai,Stream,Data}} -> ok
after Timeout ->
socket_bailout([S1,S2])
end,
%%
socket_close_verbose(S1),
receive
{S2,{Addr,P1,#sctp_shutdown_event{assoc_id=S2Ai}}} -> ok
after Timeout ->
socket_bailout([S2])
end,
receive
{S2,{Addr,P1,#sctp_assoc_change{state=shutdown_comp,
assoc_id=S2Ai}}} -> ok
after Timeout ->
socket_bailout([S2])
end,
socket_close_verbose(S2),
[] = flush(),
ok.
mk_data(Bytes) ->
mk_data(0, Bytes, <<>>).
%%
mk_data(N, Bytes, Bin) when N < Bytes ->
mk_data(N+4, Bytes, <<Bin/binary,N:32>>);
mk_data(_, _, Bin) ->
Bin.
%% Test opening a multihoming ipv4 socket.
open_multihoming_ipv4_socket(Config) when is_list(Config) ->
case get_addrs_by_family(inet, 2) of
{ok, [Addr1, Addr2]} ->
do_open_and_connect([Addr1, Addr2], Addr1);
{error, Reason} ->
{skip, Reason}
end.
%% This test is mostly aimed to indicate whether host has a
%% non-working ipv6 setup. Test opening a unihoming (non-multihoming)
%% ipv6 socket.
open_unihoming_ipv6_socket(Config) when is_list(Config) ->
case get_addrs_by_family(inet6, 1) of
{ok, [Addr]} ->
do_open_and_connect([Addr], Addr);
{error, Reason} ->
{skip, Reason}
end.
%% Test opening a multihoming ipv6 socket.
open_multihoming_ipv6_socket(Config) when is_list(Config) ->
case get_addrs_by_family(inet6, 2) of
{ok, [Addr1, Addr2]} ->
do_open_and_connect([Addr1, Addr2], Addr1);
{error, Reason} ->
{skip, Reason}
end.
%% Test opening a multihoming ipv6 socket with ipv4 and ipv6 addresses.
open_multihoming_ipv4_and_ipv6_socket(Config) when is_list(Config) ->
case get_addrs_by_family(inet_and_inet6, 2) of
{ok, [[InetAddr1, InetAddr2], [Inet6Addr1, Inet6Addr2]]} ->
%% Connect to the first address to test bind
do_open_and_connect([InetAddr1, Inet6Addr1, InetAddr2],
InetAddr1),
do_open_and_connect([Inet6Addr1, InetAddr1],
Inet6Addr1),
%% Connect an address, not the first,
%% to test sctp_bindx
do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1],
Inet6Addr2),
do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1],
InetAddr1);
{error, Reason} ->
{skip, Reason}
end.
%% Test inet:socknames/peernames on unihoming IPv4 sockets.
names_unihoming_ipv4(Config) when is_list(Config) ->
do_names(Config, inet, 1).
%% Test inet:socknames/peernames on unihoming IPv6 sockets.
names_unihoming_ipv6(Config) when is_list(Config) ->
do_names(Config, inet6, 1).
%% Test inet:socknames/peernames on multihoming IPv4 sockets.
names_multihoming_ipv4(Config) when is_list(Config) ->
do_names(Config, inet, 2).
%% Test inet:socknames/peernames on multihoming IPv6 sockets.
names_multihoming_ipv6(Config) when is_list(Config) ->
do_names(Config, inet6, 2).
do_names(_, FamilySpec, AddressCount) ->
Fun =
fun (ServerSocket, _, ServerAssoc, ClientSocket, _, ClientAssoc) ->
ServerSocknamesNoassoc =
lists:sort(ok(inet:socknames(ServerSocket))),
?LOGVAR(ServerSocknamesNoassoc),
ServerSocknames =
lists:sort(ok(inet:socknames(ServerSocket, ServerAssoc))),
?LOGVAR(ServerSocknames),
[_|_] =
ordsets:intersection
(ServerSocknamesNoassoc, ServerSocknames),
ClientSocknamesNoassoc =
lists:sort(ok(inet:socknames(ClientSocket))),
?LOGVAR(ClientSocknamesNoassoc),
ClientSocknames =
lists:sort(ok(inet:socknames(ClientSocket, ClientAssoc))),
?LOGVAR(ClientSocknames),
[_|_] =
ordsets:intersection
(ClientSocknamesNoassoc, ClientSocknames),
err([einval,enotconn], inet:peernames(ServerSocket)),
ServerPeernames =
lists:sort(ok(inet:peernames(ServerSocket, ServerAssoc))),
?LOGVAR(ServerPeernames),
err([einval,enotconn], inet:peernames(ClientSocket)),
ClientPeernames =
lists:sort(ok(inet:peernames(ClientSocket, ClientAssoc))),
?LOGVAR(ClientPeernames),
ServerSocknames = ClientPeernames,
ClientSocknames = ServerPeernames,
{ok,Socket} =
gen_sctp:peeloff(ServerSocket, ServerAssoc),
SocknamesNoassoc =
lists:sort(ok(inet:socknames(Socket))),
?LOGVAR(SocknamesNoassoc),
Socknames =
lists:sort(ok(inet:socknames(Socket, ServerAssoc))),
?LOGVAR(Socknames),
true =
ordsets:is_subset(SocknamesNoassoc, Socknames),
Peernames =
lists:sort(ok(inet:peernames(Socket, ServerAssoc))),
?LOGVAR(Peernames),
ok = gen_sctp:close(Socket),
Socknames = ClientPeernames,
ClientSocknames = Peernames,
ok
end,
case get_addrs_by_family(FamilySpec, AddressCount) of
{ok, Addresses} when length(Addresses) =:= AddressCount ->
do_open_and_connect(Addresses, hd(Addresses), Fun);
{error, Reason} ->
{skip, Reason}
end.
get_addrs_by_family(Family, NumAddrs) ->
case os:type() of
{unix,linux} ->
get_addrs_by_family_aux(Family, NumAddrs);
{unix,freebsd} ->
get_addrs_by_family_aux(Family, NumAddrs);
{unix,sunos} ->
case get_addrs_by_family_aux(Family, NumAddrs) of
{ok, [InetAddrs, Inet6Addrs]} when Family =:= inet_and_inet6 ->
%% Man page for sctp_bindx on Solaris says: "If sock is an
%% Internet Protocol Version 6 (IPv6) socket, addrs should
%% be an array of sockaddr_in6 structures containing IPv6
%% or IPv4-mapped IPv6 addresses."
{ok, [ipv4_map_addrs(InetAddrs), Inet6Addrs]};
{ok, Addrs} ->
{ok, Addrs};
{error, Reason} ->
{error, Reason}
end;
Os ->
Reason = if Family =:= inet_and_inet6 ->
f("Mixing ipv4 and ipv6 addresses for multihoming "
" has not been verified on ~p", [Os]);
true ->
f("Multihoming for ~p has not been verified on ~p",
[Family, Os])
end,
{error, Reason}
end.
get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet;
Family =:= inet6 ->
case inet:getaddr(localhost, Family) of
{error,eafnosupport} ->
{skip, f("No support for ~p", Family)};
{ok, _} ->
IfAddrs = ok(inet:getifaddrs()),
case filter_addrs_by_family(IfAddrs, Family) of
Addrs when length(Addrs) >= NumAddrs ->
{ok, lists:sublist(Addrs, NumAddrs)};
[] ->
{error, f("Need ~p ~p address(es) found none~n",
[NumAddrs, Family])};
Addrs ->
{error,
f("Need ~p ~p address(es) found only ~p: ~p~n",
[NumAddrs, Family, length(Addrs), Addrs])}
end
end;
get_addrs_by_family_aux(inet_and_inet6, NumAddrs) ->
catch {ok, [case get_addrs_by_family_aux(Family, NumAddrs) of
{ok, Addrs} -> Addrs;
{error, Reason} -> throw({error, Reason})
end || Family <- [inet, inet6]]}.
filter_addrs_by_family(IfAddrs, Family) ->
lists:flatten([[Addr || {addr, Addr} <- Info,
is_good_addr(Addr, Family)]
|| {_IfName, Info} <- IfAddrs]).
is_good_addr(Addr, inet) when tuple_size(Addr) =:= 4 ->
true;
is_good_addr({0,0,0,0,0,16#ffff,_,_}, inet6) ->
false; %% ipv4 mapped
is_good_addr({16#fe80,_,_,_,_,_,_,_}, inet6) ->
false; %% link-local
is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 ->
true;
is_good_addr(_Addr, _Family) ->
false.
ipv4_map_addrs(InetAddrs) ->
[begin
<<AB:16>> = <<A,B>>,
<<CD:16>> = <<C,D>>,
{0, 0, 0, 0, 0, 16#ffff, AB, CD}
end || {A,B,C,D} <- InetAddrs].
f(F, A) ->
lists:flatten(io_lib:format(F, A)).
do_open_and_connect(ServerAddresses, AddressToConnectTo) ->
Fun = fun (_, _, _, _, _, _) -> ok end,
do_open_and_connect(ServerAddresses, AddressToConnectTo, Fun).
%%
do_open_and_connect(ServerAddresses, AddressToConnectTo, Fun) ->
ServerFamily = get_family_by_addrs(ServerAddresses),
io:format("Serving ~p addresses: ~p~n",
[ServerFamily, ServerAddresses]),
S1 = ok(gen_sctp:open(0, [{ip,Addr} || Addr <- ServerAddresses] ++
[ServerFamily])),
ok = gen_sctp:listen(S1, true),
P1 = ok(inet:port(S1)),
ClientFamily = get_family_by_addr(AddressToConnectTo),
io:format("Connecting to ~p ~p~n",
[ClientFamily, AddressToConnectTo]),
ClientOpts =
[ClientFamily |
case ClientFamily of
inet6 ->
[{ipv6_v6only,true}];
_ ->
[]
end],
S2 = ok(gen_sctp:open(0, ClientOpts)),
log(open),
%% Verify client can connect
#sctp_assoc_change{state=comm_up} = S2Assoc =
ok(gen_sctp:connect(S2, AddressToConnectTo, P1, [])),
log(comm_up),
%% verify server side also receives comm_up from client
S1Assoc = recv_comm_up_eventually(S1),
Result = Fun(S1, ServerFamily, S1Assoc, S2, ClientFamily, S2Assoc),
ok = gen_sctp:close(S2),
ok = gen_sctp:close(S1),
Result.
%% If at least one of the addresses is an ipv6 address, return inet6, else inet.
get_family_by_addrs(Addresses) ->
case lists:usort([get_family_by_addr(Addr) || Addr <- Addresses]) of
[inet, inet6] -> inet6;
[inet] -> inet;
[inet6] -> inet6
end.
get_family_by_addr(Addr) when tuple_size(Addr) =:= 4 -> inet;
get_family_by_addr(Addr) when tuple_size(Addr) =:= 8 -> inet6.
recv_comm_up_eventually(S) ->
case ok(gen_sctp:recv(S)) of
{_Addr, _Port, _Info,
#sctp_assoc_change{state=comm_up} = Assoc} ->
Assoc;
{_Addr, _Port, _Info, _OtherSctpMsg} = Msg ->
log({unexpected,Msg}),
recv_comm_up_eventually(S)
end.
%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light
socket_open(SockOpts0, Timeout) ->
SockOpts =
case lists:keyfind(active,1,SockOpts0) of
false ->
[{active,once}|SockOpts0];
_ ->
SockOpts0
end,
Opts = [{type,seqpacket},binary|SockOpts],
Starter =
fun () ->
{ok,Socket} =
gen_sctp:open(Opts),
Socket
end,
s_start(Starter, Timeout).
socket_peeloff(Socket, AssocId, SocketOpts, Timeout) ->
Opts = [binary|SocketOpts],
Starter =
fun () ->
{ok,NewSocket} =
gen_sctp:peeloff(Socket, AssocId),
ok = inet:setopts(NewSocket, Opts),
NewSocket
end,
s_start(Starter, Timeout).
socket_close_verbose(S) ->
History = socket_history(socket_close(S)),
io:format("socket_close ~p:~n ~p.~n", [S,History]),
History.
socket_close(S) ->
s_req(S, close).
socket_call(S, Request) ->
s_req(S, {Request}).
%% socket_get(S, Key) ->
%% s_req(S, {get,Key}).
socket_bailout([S|Ss]) ->
History = socket_history(socket_close(S)),
io:format("bailout ~p:~n ~p.~n", [S,History]),
socket_bailout(Ss);
socket_bailout([]) ->
io:format("flush: ~p.~n", [flush()]),
ct:fail(socket_bailout).
socket_history({State,Flush}) ->
{lists:keysort(
2,
lists:flatten(
[[{Key,Val} || Val <- Vals]
|| {Key,Vals} <- gb_trees:to_list(State)])),
Flush}.
s_handler(Socket) ->
fun ({listen,Listen}) ->
ok = gen_sctp:listen(Socket, Listen);
(get_port) ->
ok(inet:port(Socket));
(get_socket) ->
Socket;
({connect_init,ConAddr,ConPort,ConOpts}) ->
ok = gen_sctp:connect_init(Socket, ConAddr, ConPort, ConOpts);
({send,AssocId,Stream,Data}) ->
ok = gen_sctp:send(Socket, AssocId, Stream, Data);
({send,OtherSocket,AssocId,Stream,Data}) ->
ok = gen_sctp:send(OtherSocket, AssocId, Stream, Data);
({setopts,Opts}) ->
ok = inet:setopts(Socket, Opts);
({getopts,Optnames}) ->
ok(inet:getopts(Socket, Optnames))
end.
s_req(S, Req) ->
Mref = erlang:monitor(process, S),
S ! {self(),Mref,Req},
receive
{'DOWN',Mref,_,_,Error} ->
exit(Error);
{S,Mref,Reply} ->
erlang:demonitor(Mref, [flush]),
Reply
end.
s_start(Starter, Timeout) ->
Parent = self(),
Owner =
spawn_link(
fun () ->
s_start(Starter(), Timeout, Parent)
end),
Owner.
s_start(Socket, Timeout, Parent) ->
Handler = s_handler(Socket),
try
s_loop(Socket, Timeout, Parent, Handler, gb_trees:empty())
catch
Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
io:format(?MODULE_STRING":socket exception ~w:~w at~n"
"~p.~n", [Class,Reason,Stacktrace]),
erlang:raise(Class, Reason, Stacktrace)
end.
s_loop(Socket, Timeout, Parent, Handler, State) ->
receive
{Parent,Ref,close} -> % socket_close()
erlang:send_after(Timeout, self(), {Parent,Ref,exit}),
s_loop(Socket, Timeout, Parent, Handler, State);
{Parent,Ref,exit} ->
ok = gen_sctp:close(Socket),
Key = exit,
Val = {now(),Socket},
NewState = gb_push(Key, Val, State),
Parent ! {self(),Ref,{NewState,flush()}};
{Parent,Ref,{Msg}} ->
Result = Handler(Msg),
Key = req,
Val = {now(),{Msg,Result}},
NewState = gb_push(Key, Val, State),
Parent ! {self(),Ref,Result},
s_loop(Socket, Timeout, Parent, Handler, NewState);
%% {Parent,Ref,{get,Key}} ->
%% Parent ! {self(),Ref,gb_get(Key, State)},
%% s_loop(Socket, Timeout, Parent, Handler, State);
{sctp,Socket,Addr,Port,
{[#sctp_sndrcvinfo{stream=Stream,assoc_id=AssocId}=SRI],Data}}
when not is_tuple(Data) ->
case gb_get({assoc_change,AssocId}, State) of
[{_,{Addr,Port,
#sctp_assoc_change{
state=comm_up,
inbound_streams=Is}}}|_]
when 0 =< Stream, Stream < Is-> ok;
[] -> ok
end,
Key = {msg,AssocId,Stream},
Val = {now(),{Addr,Port,SRI,Data}},
NewState = gb_push(Key, Val, State),
Parent ! {self(),{Addr,Port,AssocId,Stream,Data}},
again(Socket),
s_loop(Socket, Timeout, Parent, Handler, NewState);
{sctp,Socket,Addr,Port,
{SRI,#sctp_assoc_change{assoc_id=AssocId,state=St}=SAC}} ->
case SRI of
[#sctp_sndrcvinfo{assoc_id=AssocId,stream=0}] -> ok;
[] -> ok
end,
Key = {assoc_change,AssocId},
Val = {now(),{Addr,Port,SAC}},
case {gb_get(Key, State),St} of
{[],_} -> ok;
{[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_],_}
when St =:= comm_lost; St =:= shutdown_comp -> ok
end,
NewState = gb_push(Key, Val, State),
Parent ! {self(),{Addr,Port,SAC}},
again(Socket),
s_loop(Socket, Timeout, Parent, Handler, NewState);
{sctp,Socket,Addr,Port,
{SRI,#sctp_paddr_change{assoc_id=AssocId,
addr={_,P},
state=St}=SPC}} ->
match_unless_solaris(Port, P),
case SRI of
[#sctp_sndrcvinfo{assoc_id=AssocId,stream=0}] -> ok;
[] -> ok
end,
case {gb_get({assoc_change,AssocId}, State),St} of
{[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_],_}
when St =:= addr_available;
St =:= addr_confirmed -> ok;
{[],addr_confirmed} -> ok
end,
Key = {paddr_change,AssocId},
Val = {now(),{Addr,Port,SPC}},
NewState = gb_push(Key, Val, State),
again(Socket),
s_loop(Socket, Timeout, Parent, Handler, NewState);
{sctp,Socket,Addr,Port,
{SRI,#sctp_shutdown_event{assoc_id=AssocId}=SSE}} ->
case SRI of
[#sctp_sndrcvinfo{assoc_id=AssocId,stream=0}] -> ok;
[] -> ok
end,
case gb_get({assoc_change,AssocId}, State) of
[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_] -> ok;
[] -> ok
end,
Key = {shutdown_event,AssocId},
Val = {now(),{Addr,Port}},
NewState = gb_push(Key, Val, State),
Parent ! {self(), {Addr,Port,SSE}},
again(Socket),
s_loop(Socket, Timeout, Parent, Handler, NewState);
Unexpected ->
erlang:error({unexpected,Unexpected})
end.
again(Socket) ->
receive
{sctp_passive,Socket} ->
[{active, false}] = ok(inet:getopts(Socket, [active])),
ok = inet:setopts(Socket,[{active,1}])
after 0 ->
ok = inet:setopts(Socket, [{active,once}])
end.
gb_push(Key, Val, GBT) ->
case gb_trees:lookup(Key, GBT) of
none ->
gb_trees:insert(Key, [Val], GBT);
{value,V} ->
gb_trees:update(Key, [Val|V], GBT)
end.
gb_get(Key, GBT) ->
case gb_trees:lookup(Key, GBT) of
none ->
[];
{value,V} ->
V
end.
match_unless_solaris(A, B) ->
case os:type() of
{unix,sunos} -> B;
_ -> A = B
end.