diff options
Diffstat (limited to 'lib/kernel/test/gen_udp_SUITE.erl')
-rw-r--r-- | lib/kernel/test/gen_udp_SUITE.erl | 866 |
1 files changed, 586 insertions, 280 deletions
diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index 8d8c953303..af9985de45 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2013. All Rights Reserved. +%% Copyright Ericsson AB 1998-2018. 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. @@ -17,17 +17,16 @@ %% %% %CopyrightEnd% %% -% -% test the behavior of gen_udp. Testing udp is really a very unfunny task, -% because udp is not deterministic. -% --module(gen_udp_SUITE). --include_lib("test_server/include/test_server.hrl"). +%% +%% Test the behavior of gen_udp. Testing udp is really a very unfunny task, +%% because udp is not deterministic. +%% +-module(gen_udp_SUITE). +-include_lib("common_test/include/ct.hrl"). --define(default_timeout, ?t:minutes(1)). -% XXX - we should pick a port that we _know_ is closed. That's pretty hard. +%% XXX - we should pick a port that we _know_ is closed. That's pretty hard. -define(CLOSED_PORT, 6666). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, @@ -35,18 +34,27 @@ -export([init_per_testcase/2, end_per_testcase/2]). -export([send_to_closed/1, active_n/1, - buffer_size/1, binary_passive_recv/1, bad_address/1, - read_packets/1, open_fd/1, connect/1, implicit_inet6/1]). + buffer_size/1, binary_passive_recv/1, max_buffer_size/1, bad_address/1, + read_packets/1, open_fd/1, connect/1, implicit_inet6/1, + recvtos/1, recvtosttl/1, recvttl/1, recvtclass/1, + local_basic/1, local_unbound/1, + local_fdopen/1, local_fdopen_unbound/1, local_abstract/1]). -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. all() -> - [send_to_closed, buffer_size, binary_passive_recv, + [send_to_closed, buffer_size, binary_passive_recv, max_buffer_size, bad_address, read_packets, open_fd, connect, - implicit_inet6, active_n]. + implicit_inet6, active_n, + recvtos, recvtosttl, recvttl, recvtclass, + {group, local}]. groups() -> - []. + [{local, [], + [local_basic, local_unbound, + local_fdopen, local_fdopen_unbound, local_abstract]}]. init_per_suite(Config) -> Config. @@ -54,36 +62,40 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_group(local, Config) -> + case gen_udp:open(0, [local]) of + {ok,S} -> + ok = gen_udp:close(S), + Config; + {error,eafnosupport} -> + {skip, "AF_LOCAL not supported"} + end; init_per_group(_GroupName, Config) -> Config. +end_per_group(local, _Config) -> + delete_local_filenames(); end_per_group(_GroupName, Config) -> Config. init_per_testcase(_Case, Config) -> - ?line Dog=test_server:timetrap(?default_timeout), - [{watchdog, Dog}|Config]. + Config. -end_per_testcase(_Case, Config) -> - Dog=?config(watchdog, Config), - test_server:timetrap_cancel(Dog), +end_per_testcase(_Case, _Config) -> ok. %%------------------------------------------------------------- %% Send two packets to a closed port (on some systems this causes the socket %% to be closed). -send_to_closed(doc) -> - ["Tests core functionality."]; -send_to_closed(suite) -> - []; +%% Tests core functionality. send_to_closed(Config) when is_list(Config) -> - ?line {ok, Sock} = gen_udp:open(0), - ?line ok = gen_udp:send(Sock, {127,0,0,1}, ?CLOSED_PORT, "foo"), + {ok, Sock} = gen_udp:open(0), + ok = gen_udp:send(Sock, {127,0,0,1}, ?CLOSED_PORT, "foo"), timer:sleep(2), - ?line ok = gen_udp:send(Sock, {127,0,0,1}, ?CLOSED_PORT, "foo"), - ?line ok = gen_udp:close(Sock), + ok = gen_udp:send(Sock, {127,0,0,1}, ?CLOSED_PORT, "foo"), + ok = gen_udp:close(Sock), ok. @@ -91,19 +103,16 @@ send_to_closed(Config) when is_list(Config) -> %%------------------------------------------------------------- %% Test that the UDP socket buffer sizes are settable -buffer_size(suite) -> - []; -buffer_size(doc) -> - ["Test UDP buffer size setting."]; +%% Test UDP buffer size setting. buffer_size(Config) when is_list(Config) -> - ?line Len = 256, - ?line Bin = list_to_binary(lists:seq(0, Len-1)), - ?line M = 8192 div Len, - ?line Spec0 = + Len = 256, + Bin = list_to_binary(lists:seq(0, Len-1)), + M = 8192 div Len, + Spec0 = [{opt,M},{safe,M-3},{long,M+1}, {opt,2*M},{safe,2*M-3},{long,2*M+1}, {opt,4*M},{safe,4*M-3},{long,4*M+1}], - ?line Spec = + Spec = [case Tag of opt -> [{recbuf,Val*Len},{sndbuf,(Val + 2)*Len}]; @@ -115,12 +124,12 @@ buffer_size(Config) when is_list(Config) -> [truncated,emsgsize,timeout]} end || {Tag,Val} <- Spec0], %% - ?line {ok, ClientSocket} = gen_udp:open(0, [binary]), - ?line {ok, ClientPort} = inet:port(ClientSocket), - ?line Client = self(), - ?line ClientIP = {127,0,0,1}, - ?line ServerIP = {127,0,0,1}, - ?line Server = + {ok, ClientSocket} = gen_udp:open(0, [binary]), + {ok, ClientPort} = inet:port(ClientSocket), + Client = self(), + ClientIP = {127,0,0,1}, + ServerIP = {127,0,0,1}, + Server = spawn_link( fun () -> {ok, ServerSocket} = gen_udp:open(0, [binary]), @@ -130,78 +139,77 @@ buffer_size(Config) when is_list(Config) -> ServerSocket, 1, Spec), ok = gen_udp:close(ServerSocket) end), - ?line Mref = erlang:monitor(process, Server), - ?line receive - {Server,port,ServerPort} -> - ?line buffer_size_client(Server, ServerIP, ServerPort, - ClientSocket, 1, Spec) - end, - ?line ok = gen_udp:close(ClientSocket), - ?line receive - {'DOWN',Mref,_,_,normal} -> - ?line ok - end. + Mref = erlang:monitor(process, Server), + receive + {Server,port,ServerPort} -> + buffer_size_client(Server, ServerIP, ServerPort, + ClientSocket, 1, Spec) + end, + ok = gen_udp:close(ClientSocket), + receive + {'DOWN',Mref,_,_,normal} -> + ok + end. buffer_size_client(_, _, _, _, _, []) -> - ?line ok; + ok; buffer_size_client(Server, IP, Port, Socket, Cnt, [Opts|T]) when is_list(Opts) -> - ?line io:format("buffer_size_client Cnt=~w setopts ~p.~n", [Cnt,Opts]), - ?line ok = inet:setopts(Socket, Opts), - ?line Server ! {self(),setopts,Cnt}, - ?line receive {Server,setopts,Cnt} -> ok end, - ?line buffer_size_client(Server, IP, Port, Socket, Cnt+1, T); + io:format("buffer_size_client Cnt=~w setopts ~p.~n", [Cnt,Opts]), + ok = inet:setopts(Socket, Opts), + Server ! {self(),setopts,Cnt}, + receive {Server,setopts,Cnt} -> ok end, + buffer_size_client(Server, IP, Port, Socket, Cnt+1, T); buffer_size_client(Server, IP, Port, Socket, Cnt, [{B,Replies}|T]=Opts) when is_binary(B) -> - ?line io:format( - "buffer_size_client Cnt=~w send size ~w expecting ~p.~n", - [Cnt,size(B),Replies]), - ?line ok = gen_udp:send(Socket, IP, Port, <<Cnt,B/binary>>), - ?line receive - {Server,Cnt,Reply} -> - ?line Tag = - if - is_tuple(Reply) -> - element(1, Reply); - is_atom(Reply) -> - Reply - end, - ?line case lists:member(Tag, Replies) of - true -> ok; - false -> - ?line - ?t:fail({reply_mismatch,Cnt,Reply,Replies, - byte_size(B), - inet:getopts(Socket, - [sndbuf,recbuf])}) - end, - ?line buffer_size_client(Server, IP, Port, Socket, Cnt+1, T) - after 1313 -> - ?line buffer_size_client(Server, IP, Port, Socket, Cnt, Opts) - end. + io:format( + "buffer_size_client Cnt=~w send size ~w expecting ~p.~n", + [Cnt,size(B),Replies]), + ok = gen_udp:send(Socket, IP, Port, <<Cnt,B/binary>>), + receive + {Server,Cnt,Reply} -> + Tag = + if + is_tuple(Reply) -> + element(1, Reply); + is_atom(Reply) -> + Reply + end, + case lists:member(Tag, Replies) of + true -> ok; + false -> + ct:fail({reply_mismatch,Cnt,Reply,Replies, + byte_size(B), + inet:getopts(Socket, + [sndbuf,recbuf])}) + end, + buffer_size_client(Server, IP, Port, Socket, Cnt+1, T) + after 1313 -> + buffer_size_client(Server, IP, Port, Socket, Cnt, Opts) + end. buffer_size_server(_, _, _, _, _, []) -> ok; buffer_size_server(Client, IP, Port, Socket, Cnt, [Opts|T]) when is_list(Opts) -> receive {Client,setopts,Cnt} -> ok end, - ?line io:format("buffer_size_server Cnt=~w setopts ~p.~n", [Cnt,Opts]), + io:format("buffer_size_server Cnt=~w setopts ~p.~n", [Cnt,Opts]), ok = inet:setopts(Socket, Opts), Client ! {self(),setopts,Cnt}, buffer_size_server(Client, IP, Port, Socket, Cnt+1, T); buffer_size_server(Client, IP, Port, Socket, Cnt, [{B,_}|T]) when is_binary(B) -> - ?line io:format( - "buffer_size_server Cnt=~w expecting size ~w.~n", - [Cnt,size(B)]), + io:format( + "buffer_size_server Cnt=~w expecting size ~w.~n", + [Cnt,size(B)]), Client ! {self(),Cnt, case buffer_size_server_recv(Socket, IP, Port, Cnt) of D when is_binary(D) -> SizeD = byte_size(D), - ?line io:format( - "buffer_size_server Cnt=~w received size ~w.~n", - [Cnt,SizeD]), + io:format( + "buffer_size_server Cnt=~w received size ~w.~n", + [Cnt,SizeD]), case B of D -> correct; @@ -211,9 +219,9 @@ buffer_size_server(Client, IP, Port, {unexpected,D} end; Error -> - ?line io:format( - "buffer_size_server Cnt=~w received error ~w.~n", - [Cnt,Error]), + io:format( + "buffer_size_server Cnt=~w received error ~w.~n", + [Cnt,Error]), Error end}, buffer_size_server(Client, IP, Port, Socket, Cnt+1, T). @@ -231,55 +239,57 @@ buffer_size_server_recv(Socket, IP, Port, Cnt) -> end. +%%------------------------------------------------------------- +%% OTP-15206: Keep buffer small for udp +%%------------------------------------------------------------- +max_buffer_size(Config) when is_list(Config) -> + {ok, Socket} = gen_udp:open(0, [binary]), + ok = inet:setopts(Socket,[{recbuf, 1 bsl 20}]), + {ok, [{buffer, 65536}]} = inet:getopts(Socket,[buffer]), + gen_udp:close(Socket). %%------------------------------------------------------------- %% OTP-3823 gen_udp:recv does not return address in binary mode %% -binary_passive_recv(suite) -> - []; -binary_passive_recv(doc) -> - ["OTP-3823 gen_udp:recv does not return address in binary mode"]; +%% OTP-3823 gen_udp:recv does not return address in binary mode. binary_passive_recv(Config) when is_list(Config) -> - ?line D1 = "The quick brown fox jumps over a lazy dog", - ?line D2 = list_to_binary(D1), - ?line D3 = ["The quick", <<" brown ">>, "fox jumps ", <<"over ">>, - <<>>, $a, [[], " lazy ", <<"dog">>]], - ?line D2 = iolist_to_binary(D3), - ?line B = D2, - ?line {ok, R} = gen_udp:open(0, [binary, {active, false}]), - ?line {ok, RP} = inet:port(R), - ?line {ok, S} = gen_udp:open(0), - ?line {ok, SP} = inet:port(S), - ?line ok = gen_udp:send(S, localhost, RP, D1), - ?line {ok, {{127, 0, 0, 1}, SP, B}} = gen_udp:recv(R, byte_size(B)+1), - ?line ok = gen_udp:send(S, localhost, RP, D2), - ?line {ok, {{127, 0, 0, 1}, SP, B}} = gen_udp:recv(R, byte_size(B)+1), - ?line ok = gen_udp:send(S, localhost, RP, D3), - ?line {ok, {{127, 0, 0, 1}, SP, B}} = gen_udp:recv(R, byte_size(B)+1), - ?line ok = gen_udp:close(S), - ?line ok = gen_udp:close(R), + D1 = "The quick brown fox jumps over a lazy dog", + D2 = list_to_binary(D1), + D3 = ["The quick", <<" brown ">>, "fox jumps ", <<"over ">>, + <<>>, $a, [[], " lazy ", <<"dog">>]], + D2 = iolist_to_binary(D3), + B = D2, + {ok, R} = gen_udp:open(0, [binary, {active, false}]), + {ok, RP} = inet:port(R), + {ok, S} = gen_udp:open(0), + {ok, SP} = inet:port(S), + ok = gen_udp:send(S, localhost, RP, D1), + {ok, {{127, 0, 0, 1}, SP, B}} = gen_udp:recv(R, byte_size(B)+1), + ok = gen_udp:send(S, localhost, RP, D2), + {ok, {{127, 0, 0, 1}, SP, B}} = gen_udp:recv(R, byte_size(B)+1), + ok = gen_udp:send(S, localhost, RP, D3), + {ok, {{127, 0, 0, 1}, SP, B}} = gen_udp:recv(R, byte_size(B)+1), + ok = gen_udp:close(S), + ok = gen_udp:close(R), ok. %%------------------------------------------------------------- %% OTP-3836 inet_udp crashes when IP-address is larger than 255. -bad_address(suite) -> - []; -bad_address(doc) -> - ["OTP-3836 inet_udp crashes when IP-address is larger than 255."]; +%% OTP-3836 inet_udp crashes when IP-address is larger than 255. bad_address(Config) when is_list(Config) -> - ?line {ok, R} = gen_udp:open(0), - ?line {ok, RP} = inet:port(R), - ?line {ok, S} = gen_udp:open(0), - ?line {ok, _SP} = inet:port(S), - ?line {'EXIT', badarg} = + {ok, R} = gen_udp:open(0), + {ok, RP} = inet:port(R), + {ok, S} = gen_udp:open(0), + {ok, _SP} = inet:port(S), + {'EXIT', badarg} = (catch gen_udp:send(S, {127,0,0,1,0}, RP, "void")), - ?line {'EXIT', badarg} = + {'EXIT', badarg} = (catch gen_udp:send(S, {127,0,0,256}, RP, "void")), - ?line ok = gen_udp:close(S), - ?line ok = gen_udp:close(R), + ok = gen_udp:close(S), + ok = gen_udp:close(R), ok. @@ -288,58 +298,55 @@ bad_address(Config) when is_list(Config) -> %% %% Starts a slave node that on command sends a bunch of messages %% to our UDP port. The receiving process just receives and -%% ignores the incoming messages, but counts them. -%% A tracing process traces the receiving process for -%% 'receive' and scheduling events. From the trace, -%% message contents is verified; and, how many messages -%% are received per in/out scheduling, which should be -%% the same as the read_packets parameter. -%% -%% What happens on the SMP emulator remains to be seen... -%% +%% ignores the incoming messages. +%% A tracing process traces the receiving port for +%% 'send' and scheduling events. From the trace, +%% how many messages are received per in/out scheduling, +%% which should never be more than the read_packet parameter. -read_packets(doc) -> - ["OTP-6249 UDP option for number of packet reads."]; +%% OTP-6249 UDP option for number of packet reads. read_packets(Config) when is_list(Config) -> - case erlang:system_info(smp_support) of - false -> - read_packets_1(); - true -> - %% We would need some new sort of tracing to test this - %% option reliably in an SMP emulator. - {skip,"SMP emulator"} - end. - -read_packets_1() -> - ?line N1 = 5, - ?line N2 = 7, - ?line {ok,R} = gen_udp:open(0, [{read_packets,N1}]), - ?line {ok,RP} = inet:port(R), - ?line {ok,Node} = start_node(gen_udp_SUITE_read_packets), - ?line Die = make_ref(), - ?line Loop = erlang:spawn_link(fun () -> infinite_loop(Die) end), + N1 = 5, + N2 = 1, + Msgs = 30000, + {ok,R} = gen_udp:open(0, [{read_packets,N1}]), + {ok,RP} = inet:port(R), + {ok,Node} = start_node(gen_udp_SUITE_read_packets), + Die = make_ref(), %% - ?line Msgs1 = [erlang:integer_to_list(M) || M <- lists:seq(1, N1*3)], - ?line [V1|_] = read_packets_test(R, RP, Msgs1, Node), - ?line {ok,[{read_packets,N1}]} = inet:getopts(R, [read_packets]), + {V1, Trace1} = read_packets_test(R, RP, Msgs, Node), + {ok,[{read_packets,N1}]} = inet:getopts(R, [read_packets]), %% - ?line ok = inet:setopts(R, [{read_packets,N2}]), - ?line Msgs2 = [erlang:integer_to_list(M) || M <- lists:seq(1, N2*3)], - ?line [V2|_] = read_packets_test(R, RP, Msgs2, Node), - ?line {ok,[{read_packets,N2}]} = inet:getopts(R, [read_packets]), + ok = inet:setopts(R, [{read_packets,N2}]), + {V2, Trace2} = read_packets_test(R, RP, Msgs, Node), + {ok,[{read_packets,N2}]} = inet:getopts(R, [read_packets]), %% - ?line stop_node(Node), - ?line Mref = erlang:monitor(process, Loop), - ?line Loop ! Die, - ?line receive - {'DOWN',Mref,_,_, normal} -> - case {V1,V2} of - {N1,N2} -> - ok; - _ when V1 =/= N1, V2 =/= N2 -> - ok - end - end. + stop_node(Node), + ct:log("N1=~p, V1=~p vs N2=~p, V2=~p",[N1,V1,N2,V2]), + + dump_terms(Config, "trace1.terms", Trace2), + dump_terms(Config, "trace2.terms", Trace2), + + %% Because of the inherit racy-ness of the feature it is + %% hard to test that it behaves correctly. + %% Right now (OTP 21) a port task takes 5% of the + %% allotted port task reductions to execute, so + %% the max number of executions a port is allowed to + %% do before being re-scheduled is N * 20 + + if + V1 > (N1 * 20) -> + ct:fail("Got ~p msgs, max was ~p", [V1, N1]); + V2 > (N2 * 20) -> + ct:fail("Got ~p msgs, max was ~p", [V2, N2]); + true -> + ok + end. + +dump_terms(Config, Name, Terms) -> + FName = filename:join(proplists:get_value(priv_dir, Config),Name), + file:write_file(FName, term_to_binary(Terms)), + ct:log("Logged terms to ~s",[FName]). infinite_loop(Die) -> receive @@ -351,7 +358,6 @@ infinite_loop(Die) -> end. read_packets_test(R, RP, Msgs, Node) -> - Len = length(Msgs), Receiver = self(), Tracer = spawn_link( @@ -376,24 +382,24 @@ read_packets_test(R, RP, Msgs, Node) -> [link,{priority,high}]), receive {Sender,{port,SP}} -> - erlang:trace(self(), true, - [running,'receive',{tracer,Tracer}]), + erlang:trace(R, true, + [running_ports,'send',{tracer,Tracer}]), erlang:yield(), Sender ! {Receiver,go}, - read_packets_recv(Len), - erlang:trace(self(), false, [all]), + read_packets_recv(Msgs), + erlang:trace(R, false, [all]), Tracer ! {Receiver,get_trace}, receive {Tracer,{trace,Trace}} -> - read_packets_verify(R, SP, Msgs, Trace) + {read_packets_verify(R, SP, Trace), Trace} end end. -read_packets_send(S, RP, [Msg|Msgs]) -> - ok = gen_udp:send(S, localhost, RP, Msg), - read_packets_send(S, RP, Msgs); -read_packets_send(_S, _RP, []) -> - ok. +read_packets_send(_S, _RP, 0) -> + ok; +read_packets_send(S, RP, Msgs) -> + ok = gen_udp:send(S, localhost, RP, "UDP FLOOOOOOD"), + read_packets_send(S, RP, Msgs - 1). read_packets_recv(0) -> ok; @@ -405,23 +411,24 @@ read_packets_recv(N) -> timeout end. -read_packets_verify(R, SP, Msg, Trace) -> - lists:reverse( - lists:sort(read_packets_verify(R, SP, Msg, Trace, 0))). - -read_packets_verify(R, SP, Msgs, [{trace,Self,OutIn,_}|Trace], M) - when Self =:= self(), OutIn =:= out; - Self =:= self(), OutIn =:= in -> - push(M, read_packets_verify(R, SP, Msgs, Trace, 0)); -read_packets_verify(R, SP, [Msg|Msgs], - [{trace,Self,'receive',{udp,R,{127,0,0,1},SP,Msg}} - |Trace], M) +read_packets_verify(R, SP, Trace) -> + [Max | _] = Pkts = lists:reverse(lists:sort(read_packets_verify(R, SP, Trace, 0))), + ct:pal("~p",[lists:sublist(Pkts,10)]), + Max. + +read_packets_verify(R, SP, [{trace,R,OutIn,_}|Trace], M) + when OutIn =:= out; OutIn =:= in -> + push(M, read_packets_verify(R, SP, Trace, 0)); +read_packets_verify(R, SP, [{trace, R,'receive',timeout}|Trace], M) -> + push(M, read_packets_verify(R, SP, Trace, 0)); +read_packets_verify(R, SP, + [{trace,R,'send',{udp,R,{127,0,0,1},SP,_Msg}, Self} | Trace], M) when Self =:= self() -> - read_packets_verify(R, SP, Msgs, Trace, M+1); -read_packets_verify(_R, _SP, [], [], M) -> + read_packets_verify(R, SP, Trace, M+1); +read_packets_verify(_R, _SP, [], M) -> push(M, []); -read_packets_verify(_R, _SP, Msgs, Trace, M) -> - ?t:fail({read_packets_verify,mismatch,Msgs,Trace,M}). +read_packets_verify(_R, _SP, Trace, M) -> + ct:fail({read_packets_verify,mismatch,Trace,M}). push(0, Vs) -> Vs; @@ -438,10 +445,7 @@ flush() -> -open_fd(suite) -> - []; -open_fd(doc) -> - ["Test that the 'fd' option works"]; +%% Test that the 'fd' option works. open_fd(Config) when is_list(Config) -> Msg = "Det gör ont när knoppar brista. Varför skulle annars våren tveka?", Addr = {127,0,0,1}, @@ -460,10 +464,10 @@ open_fd(Config) when is_list(Config) -> {udp,S3,Addr,P2,Msg} -> ok after 1000 -> - ?t:fail(io_lib:format("~w", [flush()])) + ct:fail(io_lib:format("~w", [flush()])) end after 1000 -> - ?t:fail(io_lib:format("~w", [flush()])) + ct:fail(io_lib:format("~w", [flush()])) end. active_n(Config) when is_list(Config) -> @@ -569,88 +573,390 @@ active_n(Config) when is_list(Config) -> ok = gen_udp:close(S1), ok. -% -% Utils -% + + +recvtos(_Config) -> + test_recv_opts( + inet, [{recvtos,tos,96}], + fun recvtos_ok/2). + +recvtosttl(_Config) -> + test_recv_opts( + inet, [{recvtos,tos,96},{recvttl,ttl,33}], + fun (OSType, OSVer) -> + recvtos_ok(OSType, OSVer) andalso recvttl_ok(OSType, OSVer) + end). + +recvttl(_Config) -> + test_recv_opts( + inet, [{recvttl,ttl,33}], + fun recvttl_ok/2). + +recvtclass(_Config) -> + {ok,IFs} = inet:getifaddrs(), + case + [Name || + {Name,Opts} <- IFs, + lists:member({addr,{0,0,0,0,0,0,0,1}}, Opts)] + of + [_] -> + test_recv_opts( + inet6, [{recvtclass,tclass,224}], + fun recvtclass_ok/2); + [] -> + {skip,ipv6_not_supported,IFs} + end. + +%% These version numbers are just above the highest noted in daily tests +%% where the test fails for a plausible reason, that is the lowest +%% where we can expect that the test mighe succeed, so +%% skip on platforms lower than this. +%% +%% On newer versions it might be fixed, but we'll see about that +%% when machines with newer versions gets installed... +%% If the test still fails for a plausible reason these +%% version numbers simply should be increased. +%% Or maybe we should change to only test on known good platforms? + +%% Using the option returns einval, so it is not implemented. +recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); +%% Using the option returns einval, so it is not implemented. +recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); +%% Using the option returns einval, so it is not implemented. +recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +%% +recvtos_ok({unix,_}, _) -> true; +recvtos_ok(_, _) -> false. + +recvttl_ok({unix,_}, _) -> true; +recvttl_ok(_, _) -> false. + +%% Using the option returns einval, so it is not implemented. +recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {9,9,0}); +recvtclass_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,6,11}); +%% +recvtclass_ok({unix,_}, _) -> true; +recvtclass_ok(_, _) -> false. + +semver_lt({X1,Y1,Z1}, {X2,Y2,Z2}) -> + if + X1 > X2 -> false; + X1 < X2 -> true; + Y1 > Y2 -> false; + Y1 < Y2 -> true; + Z1 > Z2 -> false; + Z1 < Z2 -> true; + true -> false + end; +semver_lt(_, {_,_,_}) -> false. + +test_recv_opts(Family, Spec, OSFilter) -> + OSType = os:type(), + OSVer = os:version(), + case OSFilter(OSType, OSVer) of + true -> + io:format("Os: ~p, ~p~n", [OSType,OSVer]), + test_recv_opts(Family, Spec, OSType, OSVer); + false -> + {skip,{not_supported_for_os_version,{OSType,OSVer}}} + end. +%% +test_recv_opts(Family, Spec, _OSType, _OSVer) -> + Timeout = 5000, + RecvOpts = [RecvOpt || {RecvOpt,_,_} <- Spec], + TrueRecvOpts = [{RecvOpt,true} || {RecvOpt,_,_} <- Spec], + FalseRecvOpts = [{RecvOpt,false} || {RecvOpt,_,_} <- Spec], + Opts = [Opt || {_,Opt,_} <- Spec], + OptsVals = [{Opt,Val} || {_,Opt,Val} <- Spec], + TrueRecvOpts_OptsVals = TrueRecvOpts ++ OptsVals, + Addr = + case Family of + inet -> + {127,0,0,1}; + inet6 -> + {0,0,0,0,0,0,0,1} + end, + %% + {ok,S1} = + gen_udp:open(0, [Family,binary,{active,false}|TrueRecvOpts]), + {ok,P1} = inet:port(S1), + {ok,TrueRecvOpts} = inet:getopts(S1, RecvOpts), + ok = inet:setopts(S1, FalseRecvOpts), + {ok,FalseRecvOpts} = inet:getopts(S1, RecvOpts), + ok = inet:setopts(S1, TrueRecvOpts_OptsVals), + {ok,TrueRecvOpts_OptsVals} = inet:getopts(S1, RecvOpts ++ Opts), + %% + {ok,S2} = + gen_udp:open(0, [Family,binary,{active,true}|FalseRecvOpts]), + {ok,P2} = inet:port(S2), + {ok,FalseRecvOpts_OptsVals2} = inet:getopts(S2, RecvOpts ++ Opts), + OptsVals2 = FalseRecvOpts_OptsVals2 -- FalseRecvOpts, + %% + ok = gen_udp:send(S2, Addr, P1, <<"abcde">>), + ok = gen_udp:send(S1, Addr, P2, <<"fghij">>), + {ok,{_,P2,OptsVals3,<<"abcde">>}} = gen_udp:recv(S1, 0, Timeout), + verify_sets_eq(OptsVals3, OptsVals2), + receive + {udp,S2,_,P1,<<"fghij">>} -> + ok; + Other1 -> + exit({unexpected,Other1}) + after Timeout -> + exit(timeout) + end, + %% + ok = inet:setopts(S1, FalseRecvOpts), + {ok,FalseRecvOpts} = inet:getopts(S1, RecvOpts), + ok = inet:setopts(S2, TrueRecvOpts), + {ok,TrueRecvOpts} = inet:getopts(S2, RecvOpts), + %% + ok = gen_udp:send(S2, Addr, P1, <<"klmno">>), + ok = gen_udp:send(S1, Addr, P2, <<"pqrst">>), + {ok,{_,P2,<<"klmno">>}} = gen_udp:recv(S1, 0, Timeout), + receive + {udp,S2,_,P1,OptsVals4,<<"pqrst">>} -> + verify_sets_eq(OptsVals4, OptsVals); + Other2 -> + exit({unexpected,Other2}) + after Timeout -> + exit(timeout) + end, + ok = gen_udp:close(S1), + ok = gen_udp:close(S2), +%% exit({{OSType,OSVer},success}), % In search for the truth + ok. + +verify_sets_eq(L1, L2) -> + L = lists:sort(L1), + case lists:sort(L2) of + L -> + ok; + _ -> + exit({sets_neq,L1,L2}) + end. + + +local_basic(_Config) -> + SFile = local_filename(server), + SAddr = {local,bin_filename(SFile)}, + CFile = local_filename(client), + CAddr = {local,bin_filename(CFile)}, + _ = file:delete(SFile), + _ = file:delete(CFile), + %% + S = ok(gen_udp:open(0, [{ifaddr,{local,SFile}},{active,false}])), + C = ok(gen_udp:open(0, [{ifaddr,{local,CFile}},{active,false}])), + SAddr = ok(inet:sockname(S)), + CAddr = ok(inet:sockname(C)), + local_handshake(S, SAddr, C, CAddr), + ok = gen_udp:close(S), + ok = gen_udp:close(C), + %% + ok = file:delete(SFile), + ok = file:delete(CFile), + ok. + +local_unbound(_Config) -> + SFile = local_filename(server), + SAddr = {local,bin_filename(SFile)}, + _ = file:delete(SFile), + %% + S = ok(gen_udp:open(0, [{ifaddr,SAddr},{active,false}])), + C = ok(gen_udp:open(0, [local,{active,false}])), + SAddr = ok(inet:sockname(S)), + local_handshake(S, SAddr, C, undefined), + ok = gen_udp:close(S), + ok = gen_udp:close(C), + %% + ok = file:delete(SFile), + ok. + +local_fdopen(_Config) -> + SFile = local_filename(server), + SAddr = {local,bin_filename(SFile)}, + CFile = local_filename(client), + CAddr = {local,bin_filename(CFile)}, + _ = file:delete(SFile), + _ = file:delete(CFile), + %% + S0 = ok(gen_udp:open(0, [{ifaddr,SAddr},{active,false}])), + C = ok(gen_udp:open(0, [{ifaddr,{local,CFile}},{active,false}])), + SAddr = ok(inet:sockname(S0)), + CAddr = ok(inet:sockname(C)), + Fd = ok(prim_inet:getfd(S0)), + S = ok(gen_udp:open(0, [{fd,Fd},local,{active,false}])), + SAddr = ok(inet:sockname(S)), + local_handshake(S, SAddr, C, CAddr), + ok = gen_udp:close(S), + ok = gen_udp:close(S0), + ok = gen_udp:close(C), + %% + ok = file:delete(SFile), + ok = file:delete(CFile), + ok. + +local_fdopen_unbound(_Config) -> + SFile = local_filename(server), + SAddr = {local,bin_filename(SFile)}, + _ = file:delete(SFile), + %% + S = ok(gen_udp:open(0, [{ifaddr,SAddr},{active,false}])), + C0 = ok(gen_udp:open(0, [local,{active,false}])), + SAddr = ok(inet:sockname(S)), + Fd = ok(prim_inet:getfd(C0)), + C = ok(gen_udp:open(0, [{fd,Fd},local,{active,false}])), + local_handshake(S, SAddr, C, undefined), + ok = gen_udp:close(S), + ok = gen_udp:close(C), + ok = gen_udp:close(C0), + %% + ok = file:delete(SFile), + ok. + +local_abstract(_Config) -> + case os:type() of + {unix,linux} -> + S = ok(gen_udp:open(0, [{ifaddr,{local,<<>>}},{active,false}])), + C = ok(gen_udp:open(0, [{ifaddr,{local,<<>>}},{active,false}])), + {local,_} = SAddr = ok(inet:sockname(S)), + {local,_} = CAddr = ok(inet:sockname(C)), + local_handshake(S, SAddr, C, CAddr), + ok = gen_udp:close(S), + ok = gen_udp:close(C), + ok; + _ -> + {skip,"AF_LOCAL Abstract Addresses only supported on Linux"} + end. + + +local_handshake(S, SAddr, C, CAddr) -> + SData = "9876543210", + CData = "0123456789", + ok = gen_udp:send(C, SAddr, 0, CData), + case ok(gen_tcp:recv(S, 112)) of + {{unspec,<<>>}, 0, CData} when CAddr =:= undefined -> + ok; + {{local,<<>>}, 0, CData} when CAddr =:= undefined -> + ok; + {CAddr, 0, CData} when CAddr =/= undefined -> + ok = gen_udp:send(S, CAddr, 0, SData), + {SAddr, 0, SData} = ok(gen_tcp:recv(C, 112)), + ok + + end. + +%% +%% Utils +%% + start_node(Name) -> Pa = filename:dirname(code:which(?MODULE)), - ?t:start_node(Name, slave, [{args, "-pa " ++ Pa}]). + test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]). stop_node(Node) -> - ?t:stop_node(Node). + test_server:stop_node(Node). -connect(suite) -> - []; -connect(doc) -> - ["Test that connect/3 has effect"]; +%% Test that connect/3 has effect. connect(Config) when is_list(Config) -> - ?line Addr = {127,0,0,1}, - ?line {ok,S1} = gen_udp:open(0), - ?line {ok,P1} = inet:port(S1), - ?line {ok,S2} = gen_udp:open(0), - ?line ok = inet:setopts(S2, [{active,false}]), - ?line ok = gen_udp:close(S1), - ?line ok = gen_udp:connect(S2, Addr, P1), - ?line ok = gen_udp:send(S2, <<16#deadbeef:32>>), - ?line ok = case gen_udp:recv(S2, 0, 5) of - {error,econnrefused} -> ok; - {error,econnreset} -> ok; - Other -> Other - end, + Addr = {127,0,0,1}, + {ok,S1} = gen_udp:open(0), + {ok,P1} = inet:port(S1), + {ok,S2} = gen_udp:open(0), + ok = inet:setopts(S2, [{active,false}]), + ok = gen_udp:close(S1), + ok = gen_udp:connect(S2, Addr, P1), + ok = gen_udp:send(S2, <<16#deadbeef:32>>), + ok = case gen_udp:recv(S2, 0, 500) of + {error,econnrefused} -> ok; + {error,econnreset} -> ok; + Other -> Other + end, ok. implicit_inet6(Config) when is_list(Config) -> - ?line Host = ok(inet:gethostname()), - ?line - case inet:getaddr(Host, inet6) of - {ok,Addr} -> - ?line implicit_inet6(Host, Addr); - {error,Reason} -> - {skip, - "Can not look up IPv6 address: " - ++atom_to_list(Reason)} - end. + Host = ok(inet:gethostname()), + case inet:getaddr(Host, inet6) of + {ok,Addr} -> + implicit_inet6(Host, Addr); + {error,Reason} -> + {skip, + "Can not look up IPv6 address: " + ++atom_to_list(Reason)} + end. implicit_inet6(Host, Addr) -> - ?line Active = {active,false}, - ?line - case gen_udp:open(0, [inet6,Active]) of - {ok,S1} -> - ?line Loopback = {0,0,0,0,0,0,0,1}, - ?line io:format("~s ~p~n", ["::1",Loopback]), - ?line implicit_inet6(S1, Active, Loopback), - ?line ok = gen_udp:close(S1), - %% - ?line Localhost = "localhost", - ?line Localaddr = ok(inet:getaddr(Localhost, inet6)), - ?line io:format("~s ~p~n", [Localhost,Localaddr]), - ?line S2 = ok(gen_udp:open(0, [{ip,Localaddr},Active])), - ?line implicit_inet6(S2, Active, Localaddr), - ?line ok = gen_udp:close(S2), - %% - ?line io:format("~s ~p~n", [Host,Addr]), - ?line S3 = ok(gen_udp:open(0, [{ifaddr,Addr},Active])), - ?line implicit_inet6(S3, Active, Addr), - ?line ok = gen_udp:close(S3); - _ -> - {skip,"IPv6 not supported"} - end. + Active = {active,false}, + Loopback = {0,0,0,0,0,0,0,1}, + case gen_udp:open(0, [inet6,Active,{ip, Loopback}]) of + {ok,S1} -> + io:format("~s ~p~n", ["::1",Loopback]), + implicit_inet6(S1, Active, Loopback), + ok = gen_udp:close(S1), + %% + Localaddr = ok(get_localaddr()), + S2 = ok(gen_udp:open(0, [{ip,Localaddr},Active])), + implicit_inet6(S2, Active, Localaddr), + ok = gen_udp:close(S2), + %% + io:format("~s ~p~n", [Host,Addr]), + S3 = ok(gen_udp:open(0, [{ifaddr,Addr},Active])), + implicit_inet6(S3, Active, Addr), + ok = gen_udp:close(S3); + _ -> + {skip,"IPv6 not supported"} + end. implicit_inet6(S1, Active, Addr) -> - ?line P1 = ok(inet:port(S1)), - ?line S2 = ok(gen_udp:open(0, [inet6,Active])), - ?line P2 = ok(inet:port(S2)), - ?line ok = gen_udp:connect(S2, Addr, P1), - ?line ok = gen_udp:connect(S1, Addr, P2), - ?line {Addr,P2} = ok(inet:peername(S1)), - ?line {Addr,P1} = ok(inet:peername(S2)), - ?line {Addr,P1} = ok(inet:sockname(S1)), - ?line {Addr,P2} = ok(inet:sockname(S2)), - ?line ok = gen_udp:send(S1, Addr, P2, "ping"), - ?line {Addr,P1,"ping"} = ok(gen_udp:recv(S2, 1024, 1000)), - ?line ok = gen_udp:send(S2, Addr, P1, "pong"), - ?line {Addr,P2,"pong"} = ok(gen_udp:recv(S1, 1024)), - ?line ok = gen_udp:close(S2). - -ok({ok,V}) -> V. + P1 = ok(inet:port(S1)), + S2 = ok(gen_udp:open(0, [inet6,Active])), + P2 = ok(inet:port(S2)), + ok = gen_udp:connect(S2, Addr, P1), + ok = gen_udp:connect(S1, Addr, P2), + {Addr,P2} = ok(inet:peername(S1)), + {Addr,P1} = ok(inet:peername(S2)), + {Addr,P1} = ok(inet:sockname(S1)), + {Addr,P2} = ok(inet:sockname(S2)), + ok = gen_udp:send(S1, Addr, P2, "ping"), + {Addr,P1,"ping"} = ok(gen_udp:recv(S2, 1024, 1000)), + ok = gen_udp:send(S2, Addr, P1, "pong"), + {Addr,P2,"pong"} = ok(gen_udp:recv(S1, 1024)), + ok = gen_udp:close(S2). + +ok({ok,V}) -> V; +ok(NotOk) -> + try throw(not_ok) + catch + throw:Thrown:Stacktrace -> + erlang:raise( + error, {Thrown, NotOk}, tl(Stacktrace)) + end. + + +local_filename(Tag) -> + "/tmp/" ?MODULE_STRING "_" ++ os:getpid() ++ "_" ++ atom_to_list(Tag). + +bin_filename(String) -> + unicode:characters_to_binary(String, file:native_name_encoding()). + +delete_local_filenames() -> + _ = + [file:delete(F) || + F <- + filelib:wildcard( + "/tmp/" ?MODULE_STRING "_" ++ os:getpid() ++ "_*")], + ok. + +get_localaddr() -> + get_localaddr(["localhost", "localhost6", "ip6-localhost"]). + +get_localaddr([]) -> + {error, localaddr_not_found}; +get_localaddr([Localhost|Ls]) -> + case inet:getaddr(Localhost, inet6) of + {ok, LocalAddr} -> + io:format("~s ~p~n", [Localhost, LocalAddr]), + {ok, LocalAddr}; + _ -> + get_localaddr(Ls) + end. |