diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index ff7fee47f010..a0b3820c5820 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -512,13 +512,20 @@ cipher(Type, Event, State) -> #hello_request{} | #client_hello{}| term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -connection(enter, _, #state{connection_states = Cs0} = State0) -> - State = case maps:is_key(previous_cs, Cs0) of - false -> - State0; - true -> - Cs = maps:remove(previous_cs, Cs0), - State0#state{connection_states = Cs} +connection(enter, _, #state{connection_states = Cs0, + static_env = Env} = State0) -> + State = case Env of + #static_env{socket = {Listener, {Client, _}}} -> + dtls_packet_demux:connection_setup(Listener, Client), + case maps:is_key(previous_cs, Cs0) of + false -> + State0; + true -> + Cs = maps:remove(previous_cs, Cs0), + State0#state{connection_states = Cs} + end; + _ -> %% client + State0 end, {keep_state, State}; connection(info, Event, State) -> @@ -572,14 +579,20 @@ connection(internal, #client_hello{}, #state{static_env = #static_env{role = ser dtls_gen_connection:next_event(?FUNCTION_NAME, Record, State); connection(internal, new_connection, #state{ssl_options=SSLOptions, handshake_env=HsEnv, + static_env = #static_env{socket = {Listener, {Client, _}}}, connection_states = OldCs} = State) -> case maps:get(previous_cs, OldCs, undefined) of undefined -> - BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), - ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation), - ConnectionStates = ConnectionStates0#{previous_cs => OldCs}, - {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}}, - connection_states = ConnectionStates}}; + case dtls_packet_demux:new_connection(Listener, Client) of + true -> + {keep_state, State}; + false -> + BeastMitigation = maps:get(beast_mitigation, SSLOptions, disabled), + ConnectionStates0 = dtls_record:init_connection_states(server, BeastMitigation), + ConnectionStates = ConnectionStates0#{previous_cs => OldCs}, + {next_state, hello, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {false, first}}, + connection_states = ConnectionStates}} + end; _ -> %% Someone spamming new_connection, just drop them {keep_state, State} diff --git a/lib/ssl/src/dtls_gen_connection.erl b/lib/ssl/src/dtls_gen_connection.erl index 4964d3d21f2d..aa634a321822 100644 --- a/lib/ssl/src/dtls_gen_connection.erl +++ b/lib/ssl/src/dtls_gen_connection.erl @@ -572,6 +572,12 @@ handle_info(new_cookie_secret, StateName, {next_state, StateName, State#state{protocol_specific = CookieInfo#{current_cookie_secret => dtls_v1:cookie_secret(), previous_cookie_secret => Secret}}}; +handle_info({socket_reused, Client}, StateName, + #state{static_env = #static_env{socket = {_, {Client, _}}}} = State) -> + Alert = ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY, transport_closed), + ssl_gen_statem:handle_normal_shutdown(Alert#alert{role = server}, StateName, State), + {stop, {shutdown, transport_closed}, State}; + handle_info(Msg, StateName, State) -> ssl_gen_statem:handle_info(Msg, StateName, State). diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index 19f1d2359cf4..4ad0e9ee039b 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -33,6 +33,8 @@ sockname/1, close/1, new_owner/1, + new_connection/2, + connection_setup/2, get_all_opts/1, set_all_opts/2, get_sock_opts/2, @@ -55,7 +57,6 @@ dtls_options, emulated_options, dtls_msq_queues = kv_new(), - clients = set_new(), dtls_processes = kv_new(), accepters = queue:new(), first, @@ -85,6 +86,12 @@ close(PacketSocket) -> new_owner(PacketSocket) -> call(PacketSocket, new_owner). +new_connection(PacketSocket, Client) -> + call(PacketSocket, {new_connection, Client, self()}). + +connection_setup(PacketSocket, Client) -> + gen_server:cast(PacketSocket, {connection_setup, Client}). + get_sock_opts(PacketSocket, SplitSockOpts) -> call(PacketSocket, {get_sock_opts, SplitSockOpts}). get_all_opts(PacketSocket) -> @@ -146,6 +153,18 @@ handle_call(close, _, #state{dtls_processes = Processes, end; handle_call(new_owner, _, State) -> {reply, ok, State#state{close = false, first = true}}; +handle_call({new_connection, Old, _Pid}, _, + #state{accepters = Accepters, dtls_msq_queues = MsgQs0} = State) -> + case queue:is_empty(Accepters) of + false -> + OldQueue = kv_get(Old, MsgQs0), + MsgQs1 = kv_delete(Old, MsgQs0), + MsgQs = kv_insert({old,Old}, OldQueue, MsgQs1), + {reply, true, State#state{dtls_msq_queues = MsgQs}}; + true -> + {reply, false, State} + end; + handle_call({get_sock_opts, {SocketOptNames, EmOptNames}}, _, #state{listener = Socket, emulated_options = EmOpts} = State) -> case get_socket_opts(Socket, SocketOptNames) of @@ -170,7 +189,16 @@ handle_call({getstat, Options}, _, #state{listener = Socket, transport = {Tran handle_cast({active_once, Client, Pid}, State0) -> State = handle_active_once(Client, Pid, State0), - {noreply, State}. + {noreply, State}; +handle_cast({connection_setup, Client}, #state{dtls_msq_queues = MsgQueues} = State) -> + case kv_lookup({old, Client}, MsgQueues) of + none -> + {noreply, State}; + {value, {Pid, _}} -> + Pid ! {socket_reused, Client}, + %% Will be deleted when handling DOWN message + {noreply, State} + end. handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket, transport = {_,Transport,_,_,_}} = State0) -> State = handle_datagram({IP, InPortNo}, Msg, State0), @@ -190,24 +218,40 @@ handle_info({udp_error, Socket, econnreset = Error}, #state{listener = Socket, t ?LOG_NOTICE(Report), {noreply, State}; handle_info({ErrorTag, Socket, Error}, #state{listener = Socket, transport = {_,_,_, ErrorTag,_}} = State) -> - Report = io_lib:format("SSL Packet muliplxer shutdown: Socket error: ~p ~n", [Error]), + Report = io_lib:format("SSL Packet muliplexer shutdown: Socket error: ~p ~n", [Error]), ?LOG_NOTICE(Report), {noreply, State#state{close=true}}; -handle_info({'DOWN', _, process, Pid, _}, #state{clients = Clients, - dtls_processes = Processes0, - dtls_msq_queues = MsgQueues0, - close = ListenClosed} = State) -> +handle_info({'DOWN', _, process, Pid, _}, + #state{dtls_processes = Processes0, + dtls_msq_queues = MsgQueues0, + close = ListenClosed} = State0) -> Client = kv_get(Pid, Processes0), Processes = kv_delete(Pid, Processes0), - MsgQueues = kv_delete(Client, MsgQueues0), + State = case kv_lookup(Client, MsgQueues0) of + none -> + MsgQueues1 = kv_delete({old, Client}, MsgQueues0), + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1}; + {value, {Pid, _}} -> + MsgQueues1 = kv_delete(Client, MsgQueues0), + %% Restore old process if exists + case kv_lookup({old, Client}, MsgQueues1) of + none -> + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1}; + {value, Old} -> + MsgQueues2 = kv_delete({old, Client}, MsgQueues1), + MsgQueues = kv_insert(Client, Old, MsgQueues2), + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues} + end; + {value, _} -> %% Old process died (just delete its queue) + MsgQueues1 = kv_delete({old, Client}, MsgQueues0), + State0#state{dtls_processes = Processes, dtls_msq_queues = MsgQueues1} + end, case ListenClosed andalso kv_empty(Processes) of true -> {stop, normal, State}; false -> - {noreply, State#state{clients = set_delete(Client, Clients), - dtls_processes = Processes, - dtls_msq_queues = MsgQueues}} + {noreply, State} end. terminate(_Reason, _State) -> @@ -219,55 +263,57 @@ code_change(_OldVsn, State, _Extra) -> %%%=================================================================== %%% Internal functions %%%=================================================================== -handle_datagram(Client, Msg, #state{clients = Clients, - accepters = AcceptorsQueue0} = State) -> - case set_is_member(Client, Clients) of - false -> +handle_datagram(Client, Msg, #state{dtls_msq_queues = MsgQueues, accepters = AcceptorsQueue0} = State) -> + case kv_lookup(Client, MsgQueues) of + none -> case queue:out(AcceptorsQueue0) of - {{value, {UserPid, From}}, AcceptorsQueue} -> - setup_new_connection(UserPid, From, Client, Msg, + {{value, {UserPid, From}}, AcceptorsQueue} -> + setup_new_connection(UserPid, From, Client, Msg, State#state{accepters = AcceptorsQueue}); {empty, _} -> %% Drop packet client will resend State end; - true -> - dispatch(Client, Msg, State) + {value, Queue} -> + dispatch(Queue, Client, Msg, State) end. -dispatch(Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) -> - case kv_lookup(Client, MsgQueues) of - {value, Queue0} -> - case queue:out(Queue0) of - {{value, Pid}, Queue} when is_pid(Pid) -> - Pid ! Msg, - State#state{dtls_msq_queues = - kv_update(Client, Queue, MsgQueues)}; - {{value, _UDP}, _Queue} -> - State#state{dtls_msq_queues = - kv_update(Client, queue:in(Msg, Queue0), MsgQueues)}; - {empty, Queue} -> - State#state{dtls_msq_queues = - kv_update(Client, queue:in(Msg, Queue), MsgQueues)} - end +dispatch({Pid, Queue0}, Client, Msg, #state{dtls_msq_queues = MsgQueues} = State) -> + case queue:out(Queue0) of + {{value, Pid}, Queue} when is_pid(Pid) -> + Pid ! Msg, + State#state{dtls_msq_queues = + kv_update(Client, {Pid, Queue}, MsgQueues)}; + {{value, _UDP}, _Queue} -> + State#state{dtls_msq_queues = + kv_update(Client, {Pid, queue:in(Msg, Queue0)}, MsgQueues)}; + {empty, Queue} -> + State#state{dtls_msq_queues = + kv_update(Client, {Pid, queue:in(Msg, Queue)}, MsgQueues)} end. + next_datagram(Socket, N) -> inet:setopts(Socket, [{active, N}]). handle_active_once(Client, Pid, #state{dtls_msq_queues = MsgQueues} = State0) -> - Queue0 = kv_get(Client, MsgQueues), + {Key, Queue0} = case kv_lookup(Client, MsgQueues) of + {value, {Pid, Q0}} -> {Client, Q0}; + _ -> + OldKey = {old, Client}, + {Pid, Q0} = kv_get(OldKey, MsgQueues), + {OldKey, Q0} + end, case queue:out(Queue0) of - {{value, Pid}, _} when is_pid(Pid) -> - State0; - {{value, Msg}, Queue} -> - Pid ! Msg, - State0#state{dtls_msq_queues = kv_update(Client, Queue, MsgQueues)}; - {empty, Queue0} -> - State0#state{dtls_msq_queues = kv_update(Client, queue:in(Pid, Queue0), MsgQueues)} + {{value, Pid}, _} when is_pid(Pid) -> + State0; + {{value, Msg}, Queue} -> + Pid ! Msg, + State0#state{dtls_msq_queues = kv_update(Key, {Pid, Queue}, MsgQueues)}; + {empty, Queue0} -> + State0#state{dtls_msq_queues = kv_update(Key, {Pid, queue:in(Pid, Queue0)}, MsgQueues)} end. setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes, - clients = Clients, dtls_msq_queues = MsgQueues, dtls_options = DTLSOpts, port = Port, @@ -281,8 +327,7 @@ setup_new_connection(User, From, Client, Msg, #state{dtls_processes = Processes, erlang:monitor(process, Pid), gen_server:reply(From, {ok, Pid, {Client, Socket}}), Pid ! Msg, - State#state{clients = set_insert(Client, Clients), - dtls_msq_queues = kv_insert(Client, queue:new(), MsgQueues), + State#state{dtls_msq_queues = kv_insert(Client, {Pid, queue:new()}, MsgQueues), dtls_processes = kv_insert(Pid, Client, Processes)}; {error, Reason} -> gen_server:reply(From, {error, Reason}), @@ -295,7 +340,7 @@ kv_lookup(Key, Store) -> gb_trees:lookup(Key, Store). kv_insert(Key, Value, Store) -> gb_trees:insert(Key, Value, Store). -kv_get(Key, Store) -> +kv_get(Key, Store) -> gb_trees:get(Key, Store). kv_delete(Key, Store) -> gb_trees:delete(Key, Store). @@ -304,15 +349,6 @@ kv_new() -> kv_empty(Store) -> gb_trees:is_empty(Store). -set_new() -> - gb_sets:empty(). -set_insert(Item, Set) -> - gb_sets:insert(Item, Set). -set_delete(Item, Set) -> - gb_sets:delete(Item, Set). -set_is_member(Item, Set) -> - gb_sets:is_member(Item, Set). - call(Server, Msg) -> try gen_server:call(Server, Msg, infinity) diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 6efc2dc8be88..72bd54a51a46 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -175,9 +175,9 @@ current_connection_state_epoch(#{current_write := #{epoch := Epoch}}, %% and returns it as a list of tls_compressed binaries also returns leftover %% data %%-------------------------------------------------------------------- -get_dtls_records(Data, Vinfo, Buffer, SslOpts) -> +get_dtls_records(Data, Vinfo, Buffer, #{log_level := LogLevel}) -> BinData = list_to_binary([Buffer, Data]), - get_dtls_records_aux(Vinfo, BinData, [], SslOpts). + get_dtls_records_aux(Vinfo, BinData, [], LogLevel). %%==================================================================== %% Encoding DTLS records @@ -423,40 +423,48 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> max_fragment_length => undefined }. -get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <> = RawDTLSRecord, - Acc, #{log_level := LogLevel} = SslOpts) +get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, + <> = RawDTLSRecord, + Acc0, LogLevel) when ((StateName == hello) orelse ((StateName == certify) andalso (DataTag == udp)) - orelse ((StateName == abbreviated) andalso (DataTag == udp))) andalso ((Type == ?HANDSHAKE) - orelse - (Type == ?ALERT)) -> + orelse ((StateName == abbreviated) andalso (DataTag == udp))) + andalso ((Type == ?HANDSHAKE) orelse (Type == ?ALERT)) -> ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]), + Acc = [#ssl_tls{type = Type, version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc0], case is_acceptable_version({MajVer, MinVer}, Versions) of true -> - get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); + get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel); false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end; -get_dtls_records_aux({_, _, Version, _} = Vinfo, <> = RawDTLSRecord, - Acc, #{log_level := LogLevel} = SslOpts) when (Type == ?APPLICATION_DATA) orelse - (Type == ?HANDSHAKE) orelse - (Type == ?ALERT) orelse - (Type == ?CHANGE_CIPHER_SPEC) -> + Acc0, LogLevel) + when (Type == ?APPLICATION_DATA) orelse + (Type == ?HANDSHAKE) orelse + (Type == ?ALERT) orelse + (Type == ?CHANGE_CIPHER_SPEC) -> ssl_logger:debug(LogLevel, inbound, 'record', [RawDTLSRecord]), - case {MajVer, MinVer} of - Version -> - get_dtls_records_aux(Vinfo, Rest, [#ssl_tls{type = Type, - version = {MajVer, MinVer}, - epoch = Epoch, sequence_number = SequenceNumber, - fragment = Data} | Acc], SslOpts); - _ -> + Acc = [#ssl_tls{type = Type, version = {MajVer,MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc0], + if {MajVer, MinVer} =:= Version -> + get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel); + Type == ?HANDSHAKE -> + case is_acceptable_version({MajVer, MinVer}, Versions) of + true -> + get_dtls_records_aux(Vinfo, Rest, Acc, LogLevel); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; + true -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) end; get_dtls_records_aux(_, < dtls_listen_two_sockets_4, dtls_listen_two_sockets_5, dtls_listen_two_sockets_6, - client_restarts + client_restarts, + client_restarts_multiple_acceptors ]. init_per_suite(Config0) -> @@ -354,59 +356,159 @@ client_restarts() -> [{doc, "Test re-connection "}]. client_restarts(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ClientOpts = [{verify, verify_none},{reuse_sessions, save} | ClientOpts0], Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, ServerOpts}]), + {options, [{verify, verify_none}|ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, no_result, []}}, {from, self()}, - {options, [{reuse_sessions, save} | ClientOpts]}]), + {options, ClientOpts}]), + + ssl_test_lib:send(Client0, Msg1 = "from client 0"), + ssl_test_lib:send(Server, Msg2 = "from server to client 0"), + + Server ! {active_receive, Msg1}, + Client0 ! {active_receive, Msg2}, + + Msgs = lists:sort([{Server, Msg1}, {Client0, Msg2}]), + Msgs = lists:sort(flush()), + ReConnect = %% Whitebox re-connect test fun({sslsocket, {gen_udp,_,dtls_gen_connection}, [Pid]} = Socket, ssl) -> ct:log("~p Client Socket: ~p ~n", [self(), Socket]), - {ok, {{Address,CPort},UDPSocket}=IntSocket} = gen_statem:call(Pid, {downgrade, self()}), - + {ok, IntSocket} = gen_statem:call(Pid, {downgrade, self()}), + {{Address,CPort},UDPSocket}=IntSocket, ct:log("Info: ~p~n", [inet:info(UDPSocket)]), {ok, #config{transport_info = CbInfo, connection_cb = ConnectionCb, - ssl = SslOpts0}} = ssl:handle_options(ClientOpts, client, Address), + ssl = SslOpts0}} = + ssl:handle_options(ClientOpts, client, Address), SslOpts = {SslOpts0, #socket_options{}, undefined}, ct:sleep(250), ct:log("Client second connect: ~p ~p~n", [Socket, CbInfo]), - Res = ssl_gen_statem:connect(ConnectionCb, Address, CPort, IntSocket, SslOpts, self(), CbInfo, infinity), - {Res, Pid} + {ok, NewSocket} = ssl_gen_statem:connect(ConnectionCb, Address, CPort, IntSocket, + SslOpts, self(), CbInfo, infinity), + {replace, NewSocket} end, Client0 ! {apply, self(), ReConnect}, receive - {apply_res, {Res, _Prev}} -> + {apply_res, {replace, Res}} -> ct:log("Apply res: ~p~n", [Res]), ok; - Msg -> - ct:log("Unhandled: ~p~n", [Msg]), - ct:fail({wrong_msg, Msg}) + ErrMsg -> + ct:log("Unhandled: ~p~n", [ErrMsg]), + ct:fail({wrong_msg, ErrMsg}) end, + ssl_test_lib:send(Client0, Msg1 = "from client 0"), + ssl_test_lib:send(Server, Msg2 = "from server to client 0"), + + Server ! {active_receive, Msg1}, + Client0 ! {active_receive, Msg2}, + + Msgs = lists:sort(flush()), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + ok. + + +flush() -> + receive Msg -> [Msg|flush()] + after 500 -> [] + end. + +client_restarts_multiple_acceptors(Config) -> + %% Can also be tested with openssl by connecting a client and hit + %% Ctrl-C to kill openssl process, so that the connection is not + %% closed. + %% Then do a new openssl connect with the same client port. + + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ClientOpts = [{verify, verify_none},{reuse_sessions, save} | ClientOpts0], + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {accepters, 2}, + {options, [{verify, verify_none}|ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, + {options, ClientOpts}]), + + Server2 = receive {accepter, 2, Server2Pid} -> Server2Pid + after 5000 -> ct:fail(msg_timeout) + end, + + ssl_test_lib:send(Client0, Msg1 = "from client 0"), + ssl_test_lib:send(Server, Msg2 = "from server to client 0"), + + Server ! {active_receive, Msg1}, + Client0 ! {active_receive, Msg2}, + + Msgs = lists:sort([{Server, Msg1}, {Client0, Msg2}]), + Msgs = lists:sort(flush()), + + ReConnect = %% Whitebox re-connect test + fun({sslsocket, {gen_udp,_,dtls_gen_connection}, [Pid]} = Socket, ssl) -> + ct:log("~p Client Socket: ~p ~n", [self(), Socket]), + {ok, IntSocket} = gen_statem:call(Pid, {downgrade, self()}), + {{Address,CPort},UDPSocket}=IntSocket, + ct:log("Info: ~p~n", [inet:info(UDPSocket)]), + + {ok, #config{transport_info = CbInfo, connection_cb = ConnectionCb, + ssl = SslOpts0}} = + ssl:handle_options(ClientOpts, client, Address), + SslOpts = {SslOpts0, #socket_options{}, undefined}, + + ct:sleep(250), + ct:log("Client second connect: ~p ~p~n", [Socket, CbInfo]), + {ok, NewSocket} = ssl_gen_statem:connect(ConnectionCb, Address, CPort, IntSocket, + SslOpts, self(), CbInfo, infinity), + {replace, NewSocket} + end, + + Client0 ! {apply, self(), ReConnect}, receive - Msg2 -> - ct:log("Unhandled: ~p~n", [Msg2]), - ct:fail({wrong_msg, Msg2}) - after 200 -> - ct:log("Nothing received~n", []) + {apply_res, {replace, Res}} -> + ct:log("Apply res: ~p~n", [Res]), + ok; + ErrMsg -> + ct:log("Unhandled: ~p~n", [ErrMsg]), + ct:fail({wrong_msg, ErrMsg}) end, + ok = ssl_test_lib:send(Client0, Msg3 = "from client 2"), + ok = ssl_test_lib:send(Server2, Msg4 = "from server 2 to client 2"), + {error, closed} = ssl_test_lib:send(Server, "Should be closed"), + + Msgs2 = lists:sort([{Server2, Msg3}, {Client0, Msg4}]), + + Server2 ! {active_receive, Msg3}, + Client0 ! {active_receive, Msg4}, + + Msgs2 = lists:sort(flush()), + ssl_test_lib:close(Server), + ssl_test_lib:close(Server2), ssl_test_lib:close(Client0), - ok. %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 5a15283e825e..feeedca4eea4 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -1107,13 +1107,17 @@ client_loop_core(Socket, Pid, Transport) -> {gen_tcp, closed} -> ok; {apply, From, Fun} -> - try - Res = Fun(Socket, Transport), - From ! {apply_res, Res} + try Fun(Socket, Transport) of + {replace, NewSocket} = Res -> + From ! {apply_res, Res}, + client_loop_core(NewSocket, Pid, Transport); + Res -> + From ! {apply_res, Res}, + client_loop_core(Socket, Pid, Transport) catch E:R:ST -> - From ! {apply_res, {E,R,ST}} - end, - client_loop_core(Socket, Pid, Transport) + From ! {apply_res, {E,R,ST}}, + client_loop_core(Socket, Pid, Transport) + end end. client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) ->