diff --git a/src/minato_socket.erl b/src/minato_socket.erl index dadc7db..14c9e44 100644 --- a/src/minato_socket.erl +++ b/src/minato_socket.erl @@ -90,11 +90,18 @@ did something through the driver must not quietly stop doing it here. inet:socket_address() | inet:hostname(), inet:port_number(), [term()], timeout() ) -> {ok, socket()} | {error, term()}. connect(Host, Port, Opts, Timeout) -> - case address(Host, Port) of + case address(Host, Port, family(Opts)) of {ok, {Domain, Address}} -> opened(Domain, Address, Opts, Timeout); {error, Reason} -> {error, Reason} end. +-doc false. +-spec family([term()]) -> inet | inet6 | any. +family([inet | _Rest]) -> inet; +family([inet6 | _Rest]) -> inet6; +family([_Other | Rest]) -> family(Rest); +family([]) -> any. + -doc """ Send every byte, or say why not. @@ -218,6 +225,8 @@ setopt(_Socket, {mode, binary}) -> ok; setopt(_Socket, {packet, raw}) -> ok; setopt(_Socket, {packet, 0}) -> ok; setopt(_Socket, {buffer, _Driver_side}) -> ok; +setopt(_Socket, inet) -> ok; +setopt(_Socket, inet6) -> ok; setopt(_Socket, Option) -> {error, {unsupported_socket_option, Option}}. %%---------------------------------------------------------------------- @@ -270,29 +279,31 @@ failed(Socket, Reason) -> ok = close(Socket), {error, Reason}. --spec address(inet:socket_address() | inet:hostname(), inet:port_number()) -> +-spec address(inet:socket_address() | inet:hostname(), inet:port_number(), inet | inet6 | any) -> {ok, {socket:domain(), socket:sockaddr()}} | {error, term()}. -address({local, Path}, _Port) -> +address({local, Path}, _Port, _Family) -> {ok, {local, #{family => local, path => iolist_to_binary(Path)}}}; -address(Host, Port) when is_list(Host); is_atom(Host) -> - resolved(Host, Port); -address(Address, Port) -> +address(Host, Port, Family) when is_list(Host); is_atom(Host) -> + resolved(Host, Port, Family); +address(Address, Port, _Family) -> {ok, target(Address, Port)}. --spec resolved(inet:hostname(), inet:port_number()) -> - {ok, {socket:domain(), socket:sockaddr()}} | {error, term()}. -resolved(Host, Port) -> - case inet:getaddr(Host, inet) of - {ok, Address} -> {ok, target(Address, Port)}; - {error, _Not_a_v4_address} -> resolved6(Host, Port) - end. +-type resolution() :: {ok, {socket:domain(), socket:sockaddr()}} | {error, term()}. --spec resolved6(inet:hostname(), inet:port_number()) -> - {ok, {socket:domain(), socket:sockaddr()}} | {error, term()}. -resolved6(Host, Port) -> - case inet:getaddr(Host, inet6) of +-spec resolved(inet:hostname(), inet:port_number(), inet | inet6 | any) -> resolution(). +resolved(Host, Port, inet6) -> + resolved(Host, Port, inet6, fun(Reason) -> {error, Reason} end); +resolved(Host, Port, inet) -> + resolved(Host, Port, inet, fun(Reason) -> {error, Reason} end); +resolved(Host, Port, any) -> + resolved(Host, Port, inet, fun(_Not_a_v4_address) -> resolved(Host, Port, inet6) end). + +-spec resolved(inet:hostname(), inet:port_number(), inet | inet6, fun((term()) -> resolution())) -> + resolution(). +resolved(Host, Port, Family, Otherwise) -> + case inet:getaddr(Host, Family) of {ok, Address} -> {ok, target(Address, Port)}; - {error, Reason} -> {error, Reason} + {error, Reason} -> Otherwise(Reason) end. -spec target(inet:ip_address(), inet:port_number()) -> diff --git a/test/minato_tls_SUITE.erl b/test/minato_tls_SUITE.erl index 4041119..e3673b6 100644 --- a/test/minato_tls_SUITE.erl +++ b/test/minato_tls_SUITE.erl @@ -26,7 +26,7 @@ Skips when no TLS server answers. -include_lib("stdlib/include/assert.hrl"). --export([all/0, init_per_suite/1, end_per_suite/1]). +-export([suite/0, all/0, init_per_suite/1, end_per_suite/1]). -export([ a_verified_handshake_opens_a_working_session/1, the_server_reports_the_session_as_encrypted/1, @@ -43,6 +43,16 @@ Skips when no TLS server answers. -define(TIMEOUT, 15000). +%% Without one of these a case that blocks runs into Common Test's thirty minute +%% default, which is longer than CI gives the whole step, so a hang arrives as a +%% killed job with no case name and no stack. Every operation in this suite is +%% bounded by a five second connect or a ten second read, so a case that reaches +%% sixty seconds is stuck somewhere that has no deadline of its own, and the +%% timetrap's stack trace is the thing worth having. +-spec suite() -> [{timetrap, {seconds, pos_integer()}}]. +suite() -> + [{timetrap, {seconds, 60}}]. + -spec all() -> [atom()]. all() -> [