From 95def0159ce09a0c9632dc45b76801d1aedac218 Mon Sep 17 00:00:00 2001 From: William Yang Date: Tue, 21 Jun 2022 21:33:47 +0200 Subject: [PATCH 1/5] feat(conn): support async_conn and conn retry --- src/quicer.erl | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/quicer.erl b/src/quicer.erl index 33a9eeaa..024e1e9d 100644 --- a/src/quicer.erl +++ b/src/quicer.erl @@ -34,6 +34,7 @@ -export([ listen/2 , close_listener/1 , connect/4 + , async_connect/3 , handshake/1 , handshake/2 , async_handshake/1 @@ -184,6 +185,7 @@ close_listener(Listener) -> %% Initial New Connection (Client) %% %% Initial new connection to remote endpoint with connection opts specified. +%% @see async_connect/3 %% @end -spec connect(inet:hostname() | inet:ip_address(), inet:port_number(), conn_opts(), timeout()) -> @@ -195,26 +197,55 @@ connect(Host, Port, Opts, Timeout) when is_list(Opts) -> connect(Host, Port, Opts, Timeout) when is_tuple(Host) -> connect(inet:ntoa(Host), Port, Opts, Timeout); connect(Host, Port, Opts, Timeout) when is_map(Opts) -> - NewOpts = maps:merge(default_conn_opts(), Opts), + do_connect(Host, Port, Opts, Timeout, 1). +do_connect(_Host, _Port, _Opts, Timeout, _Retries) when Timeout <0 -> + {error, timeout}; +do_connect(Host, Port, Opts, Timeout, Retries) -> + RetryAfter = 200*Retries, + HandshakeTOut = case Timeout > RetryAfter of + true -> + RetryAfter; + _ -> + Timeout + end, + NewOpts = maps:merge(default_conn_opts(), Opts#{handshake_idle_timeout_ms => HandshakeTOut}), case quicer_nif:async_connect(Host, Port, NewOpts) of {ok, H} -> receive {quic, connected, Ctx} -> {ok, Ctx}; + {quic, transport_shutdown, _C, Reason} when Reason == connection_timeout; + Reason == connection_idle + -> + do_connect(Host, Port, Opts, Timeout - HandshakeTOut, Retries + 1); {quic, transport_shutdown, _, Reason} -> {error, transport_down, Reason} - after Timeout -> - %% @TODO caller should provide the method to handle timeout - async_shutdown_connection(H, ?QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0), - {error, timeout} end; {error, _} = Err -> Err end. +%% @doc +%% Initial New Connection (Client) +%% +%% Async variant of connect/4 +%% @see connect/4 +%% @end +-spec async_connect(inet:hostname() | inet:ip_address(), + inet:port_number(), conn_opts()) -> + {ok, connection_handler()} | + {error, conn_open_error | config_error | conn_start_error}. +async_connect(Host, Port, Opts) when is_list(Opts) -> + async_connect(Host, Port, maps:from_list(Opts)); +async_connect(Host, Port, Opts) when is_tuple(Host) -> + async_connect(inet:ntoa(Host), Port, Opts); +async_connect(Host, Port, Opts) when is_map(Opts) -> + NewOpts = maps:merge(default_conn_opts(), Opts), + quicer_nif:async_connect(Host, Port, NewOpts). + %% @doc Complete TLS handshake after accepted a Connection -%% with 5s timeout +%% with 5s timeout (Server) %% @end %% @see accept/3 %% @see handshake/2 From 954d7a206fd36d822f99a49e05896c7ca3cf3598 Mon Sep 17 00:00:00 2001 From: William Yang Date: Tue, 21 Jun 2022 23:18:00 +0200 Subject: [PATCH 2/5] test: async_conn --- test/quicer_snb_SUITE.erl | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/quicer_snb_SUITE.erl b/test/quicer_snb_SUITE.erl index aa640756..fa4443a6 100644 --- a/test/quicer_snb_SUITE.erl +++ b/test/quicer_snb_SUITE.erl @@ -135,6 +135,7 @@ all() -> , tc_conn_gc , tc_conn_resume_old , tc_conn_resume_nst + , tc_conn_resume_nst_async ]. %%-------------------------------------------------------------------- @@ -1069,6 +1070,79 @@ tc_conn_resume_nst(Config) -> end), ok. + +%%% Non-blocking connecion resume, client could send app data without waiting for handshake done. +tc_conn_resume_nst_async(Config) -> + Port = select_port(), + ListenerOpts = [{conn_acceptors, 32} | default_listen_opts(Config)], + ConnectionOpts = [ {conn_callback, quicer_server_conn_callback} + , {stream_acceptors, 32} + | default_conn_opts()], + StreamOpts = [ {stream_callback, quicer_echo_server_stream_callback} + | default_stream_opts() ], + Options = {ListenerOpts, ConnectionOpts, StreamOpts}, + ct:pal("Listener Options: ~p", [Options]), + ?check_trace(#{timetrap => 10000}, + begin + {ok, _QuicApp} = quicer_start_listener(mqtt, Port, Options), + {ok, Conn} = quicer:connect("localhost", Port, [{quic_event_mask, ?QUICER_CONNECTION_EVENT_MASK_NST} | default_conn_opts()], 5000), + {ok, Stm} = quicer:start_stream(Conn, [{active, false}]), + {ok, 4} = quicer:async_send(Stm, <<"ping">>), + {ok, <<"ping">>} = quicer:recv(Stm, 4), + NST = receive + {quic, nst_received, Conn, Ticket} -> + Ticket + after 1000 -> + ct:fail("No ticket received") + end, + quicer:close_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 111), + + {ok, ConnResumed} = quicer:async_connect("localhost", Port, [{nst, NST} | default_conn_opts()]), + {ok, Stm2} = quicer:start_stream(ConnResumed, [{active, false}]), + {ok, 5} = quicer:async_send(Stm2, <<"ping3">>), + {ok, <<"ping3">>} = quicer:recv(Stm2, 5), + ct:pal("stop listener"), + ok = quicer:stop_listener(mqtt) + end, + fun(Result, Trace) -> + ct:pal("Trace is ~p", [Trace]), + ?assertEqual(ok, Result), + %% 1. verify that for each success connect we send a resumption ticket + ?assert(?strict_causality(#{ ?snk_kind := debug + , context := "callback" + , function := "ClientConnectionCallback" + , mark := ?QUIC_CONNECTION_EVENT_CONNECTED + , tag := "event" + , resource_id := _CRid1 + }, + #{ ?snk_kind := debug + , context := "callback" + , function := "ClientConnectionCallback" + , tag := "event" + , mark := ?QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED + , resource_id := _CRid1 + }, + Trace)), + %% 2. verify that resumption ticket is received on client side + %% and client use it to resume success + ?assert(?causality(#{ ?snk_kind := debug + , context := "callback" + , function := "ClientConnectionCallback" + , mark := ?QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED + , tag := "event" + , resource_id := _CRid1 + }, + #{ ?snk_kind := debug + , context := "callback" + , function := "ServerConnectionCallback" + , tag := "event" + , mark := ?QUIC_CONNECTION_EVENT_RESUMED + , resource_id := _SRid1 + }, + Trace)) + end), + ok. + %%% Internal Helpers default_stream_opts() -> []. From ccc36f1878d28217f2b969487fd89ed04a319058 Mon Sep 17 00:00:00 2001 From: William Yang Date: Thu, 23 Jun 2022 11:23:52 +0200 Subject: [PATCH 3/5] feat(conn): support retry connect Co-authored-by: Thales Macedo Garitezi --- src/quicer.erl | 29 +++++++++++++---------------- test/quicer_snb_SUITE.erl | 34 +++++++++++++++++----------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/quicer.erl b/src/quicer.erl index 024e1e9d..bf6fa913 100644 --- a/src/quicer.erl +++ b/src/quicer.erl @@ -182,9 +182,9 @@ close_listener(Listener) -> quicer_nif:close_listener(Listener). %% @doc -%% Initial New Connection (Client) +%% Initiate New Connection (Client) %% -%% Initial new connection to remote endpoint with connection opts specified. +%% Initiate new connection to remote endpoint with connection opts specified. %% @see async_connect/3 %% @end -spec connect(inet:hostname() | inet:ip_address(), @@ -198,26 +198,23 @@ connect(Host, Port, Opts, Timeout) when is_tuple(Host) -> connect(inet:ntoa(Host), Port, Opts, Timeout); connect(Host, Port, Opts, Timeout) when is_map(Opts) -> do_connect(Host, Port, Opts, Timeout, 1). -do_connect(_Host, _Port, _Opts, Timeout, _Retries) when Timeout <0 -> +do_connect(_Host, _Port, _Opts, Timeout, _Retries) when Timeout =< 0 -> {error, timeout}; do_connect(Host, Port, Opts, Timeout, Retries) -> - RetryAfter = 200*Retries, - HandshakeTOut = case Timeout > RetryAfter of - true -> - RetryAfter; - _ -> - Timeout - end, - NewOpts = maps:merge(default_conn_opts(), Opts#{handshake_idle_timeout_ms => HandshakeTOut}), + HandshakeTOut = maps:get(handshake_idle_timeout_ms, Opts, 200), + RetryAfter = HandshakeTOut*Retries, + HandshakeTOut2 = min(Timeout, RetryAfter), + NewOpts = maps:merge(default_conn_opts(), Opts#{handshake_idle_timeout_ms => HandshakeTOut2}), case quicer_nif:async_connect(Host, Port, NewOpts) of {ok, H} -> receive {quic, connected, Ctx} -> {ok, Ctx}; - {quic, transport_shutdown, _C, Reason} when Reason == connection_timeout; - Reason == connection_idle - -> - do_connect(Host, Port, Opts, Timeout - HandshakeTOut, Retries + 1); + {quic, transport_shutdown, C, Reason} when Reason == connection_timeout + orelse Reason == connection_idle -> + %% We must close the old one + quicer:shutdown_connection(C), + do_connect(Host, Port, Opts, Timeout - HandshakeTOut2, Retries * 2); {quic, transport_shutdown, _, Reason} -> {error, transport_down, Reason} end; @@ -226,7 +223,7 @@ do_connect(Host, Port, Opts, Timeout, Retries) -> end. %% @doc -%% Initial New Connection (Client) +%% Initiate New Connection (Client) %% %% Async variant of connect/4 %% @see connect/4 diff --git a/test/quicer_snb_SUITE.erl b/test/quicer_snb_SUITE.erl index fa4443a6..e10bb2ee 100644 --- a/test/quicer_snb_SUITE.erl +++ b/test/quicer_snb_SUITE.erl @@ -1071,13 +1071,13 @@ tc_conn_resume_nst(Config) -> ok. -%%% Non-blocking connecion resume, client could send app data without waiting for handshake done. +%%% Non-blocking connection resume, client could send app data without waiting for handshake done. tc_conn_resume_nst_async(Config) -> Port = select_port(), ListenerOpts = [{conn_acceptors, 32} | default_listen_opts(Config)], ConnectionOpts = [ {conn_callback, quicer_server_conn_callback} , {stream_acceptors, 32} - | default_conn_opts()], + | default_conn_opts()], StreamOpts = [ {stream_callback, quicer_echo_server_stream_callback} | default_stream_opts() ], Options = {ListenerOpts, ConnectionOpts, StreamOpts}, @@ -1125,21 +1125,21 @@ tc_conn_resume_nst_async(Config) -> Trace)), %% 2. verify that resumption ticket is received on client side %% and client use it to resume success - ?assert(?causality(#{ ?snk_kind := debug - , context := "callback" - , function := "ClientConnectionCallback" - , mark := ?QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED - , tag := "event" - , resource_id := _CRid1 - }, - #{ ?snk_kind := debug - , context := "callback" - , function := "ServerConnectionCallback" - , tag := "event" - , mark := ?QUIC_CONNECTION_EVENT_RESUMED - , resource_id := _SRid1 - }, - Trace)) + ?assert(?causality(#{ ?snk_kind := debug + , context := "callback" + , function := "ClientConnectionCallback" + , mark := ?QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED + , tag := "event" + , resource_id := _CRid1 + }, + #{ ?snk_kind := debug + , context := "callback" + , function := "ServerConnectionCallback" + , tag := "event" + , mark := ?QUIC_CONNECTION_EVENT_RESUMED + , resource_id := _SRid1 + }, + Trace)) end), ok. From 0a13f6e284633e5c48332533e1bf1bad5ec90c74 Mon Sep 17 00:00:00 2001 From: William Yang Date: Thu, 23 Jun 2022 11:58:09 +0200 Subject: [PATCH 4/5] ci: remove test for otp 22 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a10913a6..a3101f59 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,7 +53,7 @@ jobs: strategy: matrix: otp: - [22.3.4.9, 23.3.4.5, 24.3.3] + [23.3.4.5, 24.3.3] build_type: - RelWithDebInfo - Debug From 0a4ebe403350131d459e49b5f15f6b2e2cf6cf24 Mon Sep 17 00:00:00 2001 From: William Yang Date: Thu, 23 Jun 2022 13:09:46 +0200 Subject: [PATCH 5/5] ci: add build.sh for hex release --- src/quicer.app.src | 1 + 1 file changed, 1 insertion(+) diff --git a/src/quicer.app.src b/src/quicer.app.src index 7d73bc4e..2daecff5 100644 --- a/src/quicer.app.src +++ b/src/quicer.app.src @@ -26,6 +26,7 @@ , "Makefile" , "get-msquic.sh" , "c_build" + , "build.sh" ]}, {exclude_regexps, ["priv/.*.so"]}