diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 61f506aa78ed..3a149bdd54e2 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -116,6 +116,7 @@ MODULES= \ tls_server_session_ticket_sup\ tls_server_sup\ tls_socket \ + tls_socket_tcp \ tls_sup \ tls_v1 diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index f3bc8ecb5aae..631ab6a5c7e8 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -13,6 +13,7 @@ tls_record, tls_record_1_3, tls_socket, + tls_socket_tcp, tls_v1, tls_connection_sup, tls_gen_connection, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 7043c040b1a0..422b3de4c160 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -2425,7 +2425,7 @@ handshake(Socket, SslOptions, Timeout) ConnetionCb = connection_cb(SslOptions), {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} = handle_options(Transport, Socket, SslOptions, server, undefined), - ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values()), + ok = tls_socket:setopts(Transport, Socket, tls_socket:internal_inet_values(Transport)), {ok, Port} = tls_socket:port(Transport, Socket), {ok, SessionIdHandle} = tls_socket:session_id_tracker(ssl_unknown_listener, SslOpts), ssl_gen_statem:handshake(ConnetionCb, Port, Socket, @@ -3219,12 +3219,14 @@ See `inet:getstat/2` for further details. Options :: [inet:stat_option()], OptionValues :: [{inet:stat_option(), integer()}]. %%-------------------------------------------------------------------- + getstat(#sslsocket{socket_handle = {Listener, _}, - listener_config = #config{transport_info = Info}}, + listener_config = #config{transport_info = Info, + connection_cb = dtls_gen_connection}}, Options) when is_list(Options) -> Transport = element(1, Info), dtls_socket:getstat(Transport, Listener, Options); -getstat(#sslsocket{socket_handle = Listen, +getstat(#sslsocket{socket_handle = Listen, listener_config = #config{transport_info = Info}}, Options) when is_list(Options) -> Transport = element(1, Info), @@ -3258,8 +3260,7 @@ To handle siutations where the peer has performed a shutdown on the write side, option `{exit_on_close, false}` is useful. """. %%-------------------------------------------------------------------- -shutdown(#sslsocket{listener_config = #config{connection_cb = dtls_gen_connection, - transport_info = Info}}, _) -> +shutdown(#sslsocket{listener_config = #config{transport_info = Info}}, _) -> Transport = element(1, Info), %% enotconn is what gen_tcp:shutdown on a listen socket will result with. %% shutdown really is handling TCP functionality not present @@ -3274,11 +3275,6 @@ shutdown(#sslsocket{listener_config = #config{connection_cb = dtls_gen_connectio _ -> {error, enotconn} end; -shutdown(#sslsocket{socket_handle = Listen, - listener_config = #config{connection_cb = tls_gen_connection, - transport_info = Info}}, How) -> - Transport = element(1, Info), - Transport:shutdown(Listen, How); shutdown(#sslsocket{connection_handler = Controller}, How) when is_pid(Controller) -> ssl_gen_statem:shutdown(Controller, How). @@ -4751,6 +4747,7 @@ set_opt_new(_, _, _, _, Opts) -> %%%% default_cb_info(tls) -> + %% tls_socket_tcp:cb_info(); {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive}; default_cb_info(dtls) -> {gen_udp, udp, udp_closed, udp_error, udp_passive}. @@ -4931,7 +4928,7 @@ tls_validate_version_gap(Versions) -> Versions end. -emulated_options(undefined, undefined, Protocol, Opts) -> +emulated_options(undefined, undefined, Protocol, Opts) -> case Protocol of tls -> tls_socket:emulated_options(Opts); diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index d269db8f588a..606c9363ad8c 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -899,6 +899,17 @@ handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_e handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), {stop, {shutdown,normal}, State}; +handle_info({ErrorTag, Socket, abort, Reason}, StateName, #state{static_env = #static_env{ + role = Role, + socket = Socket, + error_tag = ErrorTag} + } = State) -> + ?SSL_LOG(info, "Socket error", [{error_tag, ErrorTag}, {description, Reason}]), + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, {transport_error, Reason}), + handle_normal_shutdown(Alert#alert{role = Role}, StateName, State), + {stop, {shutdown,normal}, State}; + + handle_info({'DOWN', MonitorRef, _, _, Reason}, _, #state{connection_env = #connection_env{user_application = {MonitorRef, _Pid}}, ssl_options = #{erl_dist := true}}) -> diff --git a/lib/ssl/src/ssl_server_session_cache.erl b/lib/ssl/src/ssl_server_session_cache.erl index 13ba3ca16b48..0da645ad86e1 100644 --- a/lib/ssl/src/ssl_server_session_cache.erl +++ b/lib/ssl/src/ssl_server_session_cache.erl @@ -258,4 +258,4 @@ monitor_listener(ssl_unknown_listener) -> %% global process. undefined; monitor_listener(Listen) -> - inet:monitor(Listen). + tls_socket:monitor_socket(Listen). diff --git a/lib/ssl/src/tls_dtls_gen_connection.erl b/lib/ssl/src/tls_dtls_gen_connection.erl index 879544d076f6..daeaec24380f 100644 --- a/lib/ssl/src/tls_dtls_gen_connection.erl +++ b/lib/ssl/src/tls_dtls_gen_connection.erl @@ -179,7 +179,8 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac protocol_specific = #{sender => Sender, active_n => ssl_config:get_internal_active_n( maps:get(erl_dist, SSLOptions, false)), - active_n_toggle => true + active_n_toggle => true, + socket_active => 0 } }. diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl index d55a941cdd1c..a5b79e88b7ef 100644 --- a/lib/ssl/src/tls_gen_connection.erl +++ b/lib/ssl/src/tls_gen_connection.erl @@ -283,7 +283,22 @@ gen_info(Event, StateName, State) -> StateName, State) end. -%% raw data from socket, upack records +%% socket:socket() have data fetch and unpack records +handle_info({Protocol, Socket, Type, Handle}, StateName, + #state{static_env = #static_env{socket = Socket, + data_tag = Protocol, + transport_cb = Transport}, + protocol_specific = #{socket_active := N}=PS} + = State0) -> + Data = Transport:data_available(Socket, Type, Handle, N > 0), + State1 = State0#state{protocol_specific = PS#{socket_active := N-1}}, + case next_tls_record(Data, StateName, State1) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + ssl_gen_statem:handle_own_alert(Alert, StateName, State0) + end; +%% raw data from (gen_tcp) socket, unpack records handle_info({Protocol, _, Data}, StateName, #state{static_env = #static_env{data_tag = Protocol}} = State0) -> case next_tls_record(Data, StateName, State0) of @@ -296,15 +311,15 @@ handle_info({PassiveTag, Socket}, StateName, #state{static_env = #static_env{socket = Socket, passive_tag = PassiveTag} = StatEnv, recv = #recv{from = From}, protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, - protocol_specific = PS + protocol_specific = PS0 } = State0) -> case (From =/= undefined) andalso (CTs == []) of true -> - do_activate_socket(PS, StatEnv), - State = State0#state{protocol_specific = PS#{active_n_toggle => false}}, + PS = do_activate_socket(PS0, StatEnv), + State = State0#state{protocol_specific = PS}, next_event(StateName, no_record, State); false -> - State = State0#state{protocol_specific = PS#{active_n_toggle => true}}, + State = State0#state{protocol_specific = PS0#{active_n_toggle => true}}, next_event(StateName, no_record, State) end; handle_info({CloseTag, Socket}, StateName, @@ -734,15 +749,17 @@ activate_socket(#state{protocol_specific = #{active_n_toggle := true} = Protocol static_env = StatEnv } = State, PBuffers) -> - do_activate_socket(ProtocolSpec, StatEnv), - {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}, - protocol_buffers = PBuffers}}. + PS = do_activate_socket(ProtocolSpec, StatEnv), + {no_record, State#state{protocol_specific = PS, protocol_buffers = PBuffers}}. -do_activate_socket(#{active_n := N}, +do_activate_socket(#{active_n := N} = PS, #static_env{socket = Socket, close_tag = CloseTag, transport_cb = Transport}) -> case tls_socket:setopts(Transport, Socket, [{active, N}]) of - ok -> ok; - _ -> self() ! {CloseTag, Socket} + ok -> + PS#{active_n_toggle => false, socket_active => N}; + _ -> + self() ! {CloseTag, Socket}, + PS#{active_n_toggle => false} end. %% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 7f50ccdec2d2..f50fc941e0c6 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -102,7 +102,7 @@ init_connection_states(Role, Version, BeastMitigation, MaxEarlyDataSize) -> %%-------------------------------------------------------------------- -spec get_tls_records( - binary(), + binary() | [binary()], [tls_version()] | tls_version(), Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}, tls_max_frag_len(), @@ -115,10 +115,19 @@ init_connection_states(Role, Version, BeastMitigation, MaxEarlyDataSize) -> %% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- -get_tls_records(Data, Versions, Buffer, MaxFragLen, Downgrade) when is_binary(Buffer) -> - parse_tls_records(Versions, {[Data],byte_size(Data),[]}, MaxFragLen, Downgrade, undefined); +get_tls_records([Data], Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, Downgrade) -> + parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, Downgrade, Hdr); +get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, Downgrade) + when is_list(Data) -> + parse_tls_records(Versions, {Front,Size + iolist_size(Data), Data ++ Rear}, MaxFragLen, Downgrade, Hdr); get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, Downgrade) -> - parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, Downgrade, Hdr). + parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, Downgrade, Hdr); +get_tls_records(Data, Versions, <<>>, MaxFragLen, Downgrade) + when is_list(Data) -> + parse_tls_records(Versions, {[], iolist_size(Data), Data}, MaxFragLen, Downgrade, undefined); +get_tls_records(Data, Versions, <<>>, MaxFragLen, Downgrade) -> + parse_tls_records(Versions, {[Data],byte_size(Data),[]}, MaxFragLen, Downgrade, undefined). + %%==================================================================== %% Encoding diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index 8c9ad5736161..ad11a15b7733 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -38,14 +38,15 @@ peername/2, sockname/2, port/2, - close/2]). + close/2, + monitor_socket/1]). -export([split_options/1, get_socket_opts/3]). -export([emulated_options/0, emulated_options/1, - internal_inet_values/0, + internal_inet_values/1, default_inet_values/0, init/1, start_link/3, @@ -78,7 +79,7 @@ send(Transport, Socket, Data) -> listen(Transport, Port, #config{transport_info = {Transport, _, _, _, _}, inet_user = Options, ssl = SslOpts, emulated = EmOpts} = Config) -> - case Transport:listen(Port, Options ++ internal_inet_values()) of + case Transport:listen(Port, Options ++ internal_inet_values(Transport)) of {ok, ListenSocket} -> {ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts), LifeTime = ssl_config:get_ticket_lifetime(), @@ -116,7 +117,7 @@ accept(ListenSocket, #config{transport_info = {Transport,_,_,_,_} = CbInfo, upgrade(Socket, #config{transport_info = {Transport,_,_,_,_}= CbInfo, ssl = SslOptions, emulated = EmOpts, connection_cb = ConnectionCb}, Timeout) -> - ok = setopts(Transport, Socket, tls_socket:internal_inet_values()), + ok = setopts(Transport, Socket, internal_inet_values(Transport)), case peername(Transport, Socket) of {ok, {Address, Port}} -> ssl_gen_statem:connect(ConnectionCb, Address, Port, Socket, @@ -132,7 +133,7 @@ connect(Address, Port, emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb}, Timeout) -> {Transport, _, _, _, _} = CbInfo, - try Transport:connect(Address, Port, SocketOpts, Timeout) of + try Transport:connect(Address, Port, SocketOpts ++ internal_inet_values(Transport), Timeout) of {ok, Socket} -> ssl_gen_statem:connect(ConnetionCb, Address, Port, Socket, {SslOpts, @@ -249,13 +250,20 @@ close(gen_tcp, Socket) -> close(Transport, Socket) -> Transport:close(Socket). +monitor_socket({'$socket', _}=Socket) -> + socket:monitor(Socket); +monitor_socket(InetSocket) -> + inet:monitor(InetSocket). + emulated_options() -> [mode, packet, active, header, packet_size]. emulated_options(Opts) -> - emulated_options(Opts, internal_inet_values(), default_inet_values()). + emulated_options(Opts, [], default_inet_values()). -internal_inet_values() -> +internal_inet_values(tls_socket_tcp) -> + []; +internal_inet_values(_) -> [{packet_size,0}, {packet, 0}, {header, 0}, {active, false}, {mode,binary}]. default_inet_values() -> @@ -329,7 +337,7 @@ start_link(Port, SockOpts, SslOpts) -> init([Listen, Opts, SslOpts]) -> process_flag(trap_exit, true), proc_lib:set_label({tls_listen_tracker, Listen}), - Monitor = inet:monitor(Listen), + Monitor = monitor_socket(Listen), {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), listen_monitor = Monitor, ssl_opts = SslOpts}}. diff --git a/lib/ssl/src/tls_socket_tcp.erl b/lib/ssl/src/tls_socket_tcp.erl new file mode 100644 index 000000000000..f925e0f2c371 --- /dev/null +++ b/lib/ssl/src/tls_socket_tcp.erl @@ -0,0 +1,397 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020-2024. 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% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: Use socket directly in tls instead of gen_tcp +%%---------------------------------------------------------------------- + +-module(tls_socket_tcp). +-moduledoc false. + +-export([cb_info/0, + + setopts/2, + getopts/2, + getstat/2, + peername/1, + sockname/1, + port/1, + controlling_process/2, + + listen/2, + accept/2, + connect/4, +%% open/2, dtls only + close/1, + shutdown/2, + send/2, +%% send/4, + recv/2, recv/3, + + data_available/4 + ]). + +-include_lib("kernel/src/inet_int.hrl"). %% Temporary workaround +-include("ssl_internal.hrl"). + +cb_info() -> + %% {gen_tcp, tcp, tcp_closed, tcp_error, tcp_passive} + {?MODULE, '$socket', '$socket_closed', '$socket', '$socket_passive'}. + + +setopts(Socket, [{active, _N}]) -> + activate_socket(Socket); +setopts(Socket, List) -> + try + Opts = check_opts(List), + [ok = socket:setopt(Socket, Opt, Val) || {_,_}=Opt := Val <- Opts], + ok + catch _:Err -> + Err + end. + +getopts(Socket, Keys) -> + try + Get = fun(Key) -> + SocketKey = maps:get(Key, socket_opts()), + {ok, Val} = socket:getopt(Socket, SocketKey), + Val + end, + {ok, [{Key, Get(Key)} || Key <- Keys]} + catch _:Err -> + Err + end. + +getstat(Socket, Opts) -> + #{counters := Counters} = socket:info(Socket), + {ok, getstat_what(Opts, Counters)}. + +peername(Socket) -> + case socket:peername(Socket) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + Err -> + Err + end. + +sockname(Socket) -> + case socket:sockname(Socket) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + Err -> + Err + end. + +port(Socket) -> + case socket:sockname(Socket) of + {ok, #{port := Port}} -> + {ok, Port}; + Err -> + Err + end. + +controlling_process(Socket, NewOwner) -> + socket:setopt(Socket, {otp, controlling_process}, NewOwner). + +listen(Port, Opts1) when is_integer(Port) -> + {Mod, Opts2} = inet:tcp_module(Opts1), + maybe + {ok, LstOpts} ?= inet:listen_options([{port, Port} | Opts2], Mod), + #listen_opts{fd = Fd, + ifaddr = BindAddr, + port = BindPort, + backlog = Backlog} = LstOpts, + Domain = domain(Mod), + {ok, Sock} ?= socket_open(Fd, Domain, stream, tcp), + SockAddr = bind_addr(Domain, BindAddr, BindPort), + ok ?= socket:bind(Sock, SockAddr), + SetOpts = check_opts(Opts2), + [ok = socket:setopt(Sock, Opt, Val) || {_,_}=Opt := Val <- SetOpts], + ok ?= socket:listen(Sock, Backlog), + {ok, Sock} + else + {error, _} = Error -> Error; + Reason -> {error, Reason} + end. + +accept(Socket, Timeout) -> + socket:accept(Socket, Timeout). + +-spec connect(Address, Port, Opts, Timeout) -> + {ok, Socket} | {error, Reason} when + Address :: inet:socket_address() | inet:hostname(), + Port :: inet:port_number(), + Opts :: [inet:inet_backend() | gen_tcp:connect_option()], + Timeout :: timeout(), + Socket :: socket:socket(), + Reason :: timeout | inet:posix(). + +connect(Address, Port, Opts0, Timeout) -> + Opts1 = check_opts(Opts0), + case inet:is_ip_address(Address) of + true -> + connect_1(Address, Port, Opts1, Timeout, {error,einval}); + false -> + {Mod, Opts} = inet:tcp_module(maps:to_list(Opts1), Address), + case Mod:getaddrs(Address, Timeout) of + {ok, IPs} -> + connect_1(IPs, Port, Opts, Timeout, {error,einval}); + Error -> + Error + end + end. + +close(Socket) -> + socket:close(Socket). + +shutdown(Socket, How) -> + socket:shutdown(Socket, How). + +recv(Socket, Size) -> + recv(Socket, Size, infinity). +recv(Socket, Size, Timeout) -> + socket:recv(Socket, Size, Timeout). + +%%-define(CT_LOG(F,A), ct:log(default, 1, F, A, [esc_chars])). +-define(CT_LOG(F,A), ok). + +send(Socket, Data) -> + ?CT_LOG("~w:~w: ~w send ~w", [?MODULE, ?LINE, get(role), iolist_size(Data)]), + case socket:sendv(Socket, erlang:iolist_to_iovec(Data)) of + ok -> + ok; + {ok, Cont} -> + ?CT_LOG("~w:~w: ~w send loop ~w", [?MODULE, ?LINE, get(role), iolist_size(Cont)]), + send(Socket, Cont); + Err -> + ?CT_LOG("~w:~w: ~w send ~w", [?MODULE, ?LINE, get(role), Err]), + Err + end. + +%% Note: returns the reverse list of packets +data_available(Socket, _select, Handle, Activate) -> + ?CT_LOG("~w:~w: ~w recv ~w ~p",[?MODULE, ?LINE, get(role), Activate, Handle]), + Res = case socket:recv(Socket, 0, Handle) of + {ok, Data0} when Activate -> + ?CT_LOG("~w:~w: ~w data", [?MODULE, ?LINE, get(role)]), + active_socket(Socket, Handle, 5, [Data0]); + {ok, Data0} -> %% Activate is false, fake passive message + self() ! {'$socket_passive', Socket}, + ?CT_LOG("~w:~w: ~w passive", [?MODULE, ?LINE, get(role)]), + Data0; + {select, {select_info, _, _Handle}} -> %% Fix windows handles + %% First time data may not be available + ?CT_LOG("~w:~w: ~w wait_select ~p", [?MODULE, ?LINE, get(role), _Handle]), + []; + {error, Reason} -> + ?SSL_LOG(debug, socket_error, [{error, Reason}]), + ?CT_LOG("~w:~w: ~w error", [?MODULE, ?LINE, get(role)]), + self() ! {'$socket_closed', Socket}, + [] + end, + ?CT_LOG("~w:~w: ~w recv res ~w", [?MODULE, ?LINE, get(role), iolist_size(Res)]), + Res. + +activate_socket(Socket) -> + active_socket(Socket, nowait, 0, []), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Helpers %%%%%%%%%%%%%%%%%%%%%%%% + +active_socket(Socket, Handle, N, Acc) when N > 0 -> + case socket:recv(Socket, 0, Handle) of %% More than 1 ssl packet + {ok, Data} -> + ?CT_LOG("~w:~w: ~w data", [?MODULE, ?LINE, get(role)]), + active_socket(Socket, Handle, N-1, [Data | Acc]); + {select, {select_info, _, _Handle}} -> %% Fix windows + ?CT_LOG("~w:~w: ~w select ~p", [?MODULE, ?LINE, get(role), _Handle]), + Acc; + {select, {{select_info, _, _Handle}, Data}} -> + ?CT_LOG("~w:~w: ~w select with data", [?MODULE, ?LINE, get(role), _Handle]), + [Data | Acc]; + {error, Reason} -> + ?CT_LOG("~w:~w: ~w error", [?MODULE, ?LINE, get(role)]), + ?SSL_LOG(debug, socket_error, [{error, Reason}]), + self() ! {'$socket_closed', Socket}, + Acc + end; +active_socket(Socket, Handle, 0, Acc) -> + ?CT_LOG("~w:~w: ~w to_many_reads", [?MODULE, ?LINE, get(role)]), + self() ! {'$socket', Socket, select, Handle}, + Acc. + +connect_1([IP|IPs], Port, Opts, Timeout, _Err) -> + Family = which_family(IP), + SockAddr = #{family => Family, addr => IP, port => Port}, + {ok, Socket} = socket:open(Family, stream, tcp), + [ok = socket:setopt(Socket, Opt, Val) || {{_,_}=Opt, Val} <- Opts], + case socket:connect(Socket, SockAddr, Timeout) of + ok -> + {ok, Socket}; + Err -> + socket:close(Socket), + connect_1(IPs, Port, Opts, Timeout, Err) + end; +connect_1([], _Port, _Opts, _Timeout, Err) -> + Err. + +socket_open(Fd, Domain, Type, Protocol) when Fd < 0 -> + socket:open(Domain, Type, Protocol); +socket_open(Fd, Domain, Type, Protocol) -> + socket:open(Fd, #{domain => Domain, type => Type, protocol => Protocol}). + +bind_addr(Domain, undefined, Port) -> + #{family => Domain, + addr => any, + port => Port}; +bind_addr(Domain, Addr, Port) -> + #{family => Domain, + addr => Addr, + port => Port}. + +which_family(Addr) when is_tuple(Addr) andalso (tuple_size(Addr) =:= 4) -> + inet; +which_family(Addr) when is_tuple(Addr) andalso (tuple_size(Addr) =:= 8) -> + inet6. + +domain(Mod) -> + case Mod of + inet_tcp -> inet; + inet6_tcp -> inet6; + local_tcp -> local + end. + +check_opts(Opts0) -> + Def = #{ + tcp_module => inet_tcp + }, + lists:foldr(fun check_opts_1/2, Def, Opts0). + +check_opts_1(inet, Opts) -> + Opts#{tcp_module => inet_tcp}; +check_opts_1(inet6, Opts) -> + Opts#{tcp_module => inet6_tcp}; +check_opts_1(local, Opts) -> + Opts#{tcp_module => local_tcp}; +check_opts_1({raw, Level, Key, Value}, Opts) -> + Opts#{raw => {Level, Key, Value}}; +check_opts_1({exit_on_close, false}, Opts) -> + Opts; + +check_opts_1({backlog, Val}, Opts) -> + Opts#{backlog => Val}; + +check_opts_1({Key, Val}, Opts) -> + case maps:get(Key, socket_opts(), undefined) of + undefined -> + logger:log(notice, "Unsupported option ~p ~p", [Key,Val]), + Opts; + SocketKeyOpt -> + Opts#{SocketKeyOpt => Val} + end; + +check_opts_1(Opt, Opts) -> + logger:log(notice, "Unsupported option ~p ", [Opt]), + Opts. + + +socket_opts() -> + #{ + %% Level: otp + buffer => {otp, rcvbuf}, + debug => {otp, debug}, + fd => {otp, fd}, + + %% + %% Level: socket + bind_to_device => {socket, bindtodevice}, + dontroute => {socket, dontroute}, + exclusiveaddruse => {socket, exclusiveaddruse}, + keepalive => {socket, keepalive}, + linger => {socket, linger}, + priority => {socket, priority}, + recbuf => {socket, rcvbuf}, + reuseaddr => {socket, reuseaddr}, + sndbuf => {socket, sndbuf}, + + %% + %% Level: tcp + nodelay => {tcp, nodelay}, + + %% + %% Level: ip + recvtos => {ip, recvtos}, + recvttl => {ip, recvttl}, + tos => {ip, tos}, + ttl => {ip, ttl}, + + %% + %% Level: ipv6 + recvtclass => {ipv6, recvtclass}, + ipv6_v6only => {ipv6, v6only}, + tclass => {ipv6, tclass}, + + %% + %% Raw + raw => raw, + + %% + %% Special cases + %% These are options that cannot be mapped as above, + %% as they, for instance, "belong to" several domains. + %% So, we select which level to use based on the domain + %% of the socket. + + %% This is a special case. + %% Only supported on Linux and then only actually for IPv6, + %% but unofficially also for ip...barf... + %% In both cases this is *no longer valid* as the RFC which + %% introduced this, RFC 2292, is *obsoleted* by RFC 3542, where + %% this "feature" *does not exist*... + pktoptions => + [{inet, {ip, pktoptions}}, {inet6, {ipv6, pktoptions}}] + }. + +counter_key(Tag) -> + case Tag of + recv_oct -> [read_byte]; + recv_cnt -> [read_pkg]; + recv_max -> [read_pkg_max]; + recv_avg -> [read_byte, read_pkg]; + send_oct -> [write_byte]; + send_cnt -> [write_pkg]; + send_max -> [write_pkg_max]; + send_avg -> [write_byte, write_pkg]; + _ -> [] + end. + +getstat_what([], _Counters) -> []; +getstat_what([Tag | Tags], Counters) -> + case counter_key(Tag) of + [SocketTag] -> + [{Tag, maps:get(SocketTag, Counters)} + | getstat_what(Tags, Counters)]; + [NumTag, DenomTag] -> + Denom = max(maps:get(DenomTag, Counters), 1), + [{Tag, maps:get(NumTag, Counters) div Denom} + | getstat_what(Tags, Counters)]; + [] -> + getstat_what(Tags, Counters) + end. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index b294daf53d7c..e151527f63fa 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -1432,7 +1432,7 @@ listen_socket(Config) -> {ok, _} = ssl:sockname(ListenSocket), - {error, enotconn} = ssl:send(ListenSocket, <<"data">>), + {error, _enotconn} = ssl:send(ListenSocket, <<"data">>), {error, enotconn} = ssl:recv(ListenSocket, 0), {error, enotconn} = ssl:connection_information(ListenSocket), {error, enotconn} = ssl:peername(ListenSocket), diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index 9eb87700f816..500ff3a4616f 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -234,7 +234,7 @@ -define(uint64(X), << ?UINT64(X) >> ). -define(MANY, 1000). --define(SOME, 50). +-define(SOME, 102). %% More than def: {active, N=100} -define(BASE_TIMEOUT_SECONDS, 20). -define(SOME_SCALE, 2). -define(MANY_SCALE, 3). @@ -2313,7 +2313,7 @@ passive_recv_packet(Socket, Data, N) -> end. send(Socket,_, 0) -> - ssl:send(Socket, <<>>), + ok = ssl:send(Socket, <<>>), no_result_msg; send(Socket, Data, N) -> case ssl:send(Socket, [Data]) of