%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2007-2020. 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( [skip_old_solaris/1, 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() -> G = case is_old_solaris() of true -> old_solaris; false -> extensive end, [{group,smoke}, {group,G}]. groups() -> [{smoke,[],[basic,basic_stream]}, {old_solaris,[],[skip_old_solaris]}, {extensive,[], [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, 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]}]. 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). is_old_solaris() -> os:type() =:= {unix,sunos} andalso os:version() < {5,12,0}. skip_old_solaris(_Config) -> {skip,"Unreliable test cases and/or implementation on old Solaris"}. %% 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}, StatOpts = [recv_avg,recv_cnt,recv_max,recv_oct, send_avg,send_cnt,send_max,send_oct], {ok,Sb} = gen_sctp:open([{type,seqpacket}]), {ok,SbStat1} = inet:getstat(Sb, StatOpts), {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,SbStat2} = inet:getstat(Sb, StatOpts), [] = filter_stat_eq(SbStat1, SbStat2), ok = gen_sctp:close(Sb), receive Msg -> ct:fail({received,Msg}) after 17 -> ok end, ok. filter_stat_eq([], []) -> []; filter_stat_eq([{Tag,Val1}=Stat|SbStat1], [{Tag,Val2}|SbStat2]) -> if Val1 == Val2 -> [Stat|filter_stat_eq(SbStat1, SbStat2)]; true -> filter_stat_eq(SbStat1, SbStat2) end. %% 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), S1AssocId = case recv_event(log_ok(gen_sctp:recv(S1))) of {Loopback,P2, #sctp_assoc_change{ state=comm_up, error=0, assoc_id=AssocId}} -> AssocId; {Loopback,P2, #sctp_paddr_change{ state=addr_confirmed, error=0, assoc_id=AssocId}} -> {Loopback,P2, #sctp_assoc_change{ state=comm_up, error=0, assoc_id=AssocId}} = recv_event(log_ok(gen_sctp:recv(S1))), AssocId end, ?LOGVAR(S1AssocId), #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 -> 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, StartTime = timestamp(), 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], StartTime) end, ?LOGVAR(S2Ai), S1Ai = receive {S1,{Addr,P2, #sctp_assoc_change{ state=comm_up, assoc_id=AssocId1}}} -> AssocId1 after Timeout -> socket_bailout([S1,S2], StartTime) 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], StartTime) 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], StartTime) 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], StartTime) 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], StartTime) end, %% inet:i(sctp), socket_close_verbose(S1, StartTime), socket_close_verbose(S2, StartTime), receive {S3,{Addr,P2,#sctp_shutdown_event{assoc_id=S3Ai_X}}} -> match_unless_solaris(S3Ai, S3Ai_X) after Timeout -> socket_bailout([S3], StartTime) end, receive {S3,{Addr,P2,#sctp_assoc_change{state=shutdown_comp, assoc_id=S3Ai}}} -> ok after Timeout -> socket_bailout([S3], StartTime) end, socket_close_verbose(S3, StartTime), [] = flush(), ok. %% Check sndbuf and recbuf behaviour. buffers(Config) when is_list(Config) -> Limit = 4096, Addr = {127,0,0,1}, Stream = 1, Timeout = 3333, StartTime = timestamp(), 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], StartTime) end, S1Ai = receive {S1,{Addr,P2, #sctp_assoc_change{ state=comm_up, assoc_id=AssocId1}}} -> AssocId1 after Timeout -> socket_bailout([S1,S2], StartTime) 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], StartTime) end, %% socket_close_verbose(S1, StartTime), receive {S2,{Addr,P1,#sctp_shutdown_event{assoc_id=S2Ai}}} -> ok after Timeout -> socket_bailout([S2], StartTime) end, receive {S2,{Addr,P1,#sctp_assoc_change{state=shutdown_comp, assoc_id=S2Ai}}} -> ok after Timeout -> socket_bailout([S2], StartTime) end, socket_close_verbose(S2, StartTime), [] = flush(), ok. mk_data(Bytes) -> mk_data(0, Bytes, <<>>). %% mk_data(N, Bytes, Bin) when N < Bytes -> mk_data(N+4, Bytes, <>); 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 <> = <>, <> = <>, {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, ServerOpts} = 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|ServerOpts])), 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, [{ipv6_v6only, false}]}; [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, StartTime) -> History = socket_history(socket_close(S), StartTime), 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], StartTime) -> History = socket_history(socket_close(S), StartTime), io:format("bailout ~p:~n ~p.~n", [S,History]), socket_bailout(Ss, StartTime); socket_bailout([], _) -> io:format("flush: ~p.~n", [flush()]), ct:fail(socket_bailout). socket_history({State,Flush}, StartTime) -> {lists:keysort( 2, lists:flatten( [[{Key,{TS-StartTime,Val}} || {TS,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 -> 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, NewState = gb_push(Key, Socket, State), Parent ! {self(),Ref,{NewState,flush()}}; {Parent,Ref,{Msg}} -> Result = Handler(Msg), Key = req, NewState = gb_push(Key, {Msg,Result}, 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}, NewState = gb_push(Key, {Addr,Port,SRI,Data}, 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}, 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, {Addr,Port,SAC}, 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}, NewState = gb_push(Key, {Addr,Port,SPC}, 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}, NewState = gb_push(Key, {Addr,Port}, 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) -> TS = timestamp(), case gb_trees:lookup(Key, GBT) of none -> gb_trees:insert(Key, [{TS,Val}], GBT); {value,V} -> gb_trees:update(Key, [{TS,Val}|V], GBT) end. gb_get(Key, GBT) -> case gb_trees:lookup(Key, GBT) of none -> []; {value,V} -> [Val || {_TS,Val} <- V] end. match_unless_solaris(A, B) -> case os:type() of {unix,sunos} -> B; _ -> A = B end. timestamp() -> erlang:monotonic_time().