diff --git a/c_src/quicer_connection.c b/c_src/quicer_connection.c index 72abdf15..81291003 100644 --- a/c_src/quicer_connection.c +++ b/c_src/quicer_connection.c @@ -24,6 +24,7 @@ limitations under the License. #include extern QuicerRegistrationCTX *G_r_ctx; +extern pthread_mutex_t GRegLock; #if defined(DEBUG) && !defined(QUICER_LOGGING_STDOUT) extern inline void @@ -606,6 +607,15 @@ open_connectionX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) } CxPlatRefInitialize(&(c_ctx->ref_count)); + + // link to registration + if (r_ctx) + { + enif_mutex_lock(r_ctx->lock); + CxPlatListInsertTail(&r_ctx->Connections, &c_ctx->RegistrationLink); + enif_mutex_unlock(r_ctx->lock); + } + eHandle = enif_make_resource(env, c_ctx); return SUCCESS(eHandle); @@ -787,6 +797,13 @@ async_connect3(ErlNifEnv *env, } } c_ctx->is_closed = FALSE; // connection opened. + // + if (!is_reuse_handle && r_ctx) + { + enif_mutex_lock(r_ctx->lock); + CxPlatListInsertTail(&r_ctx->Connections, &c_ctx->RegistrationLink); + enif_mutex_unlock(r_ctx->lock); + } // optional set sslkeylogfile parse_sslkeylogfile_option(env, eoptions, c_ctx); @@ -1670,6 +1687,50 @@ parse_conn_event_mask(ErlNifEnv *env, return ATOM_OK; } +ERL_NIF_TERM +get_connectionsX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + QuicerRegistrationCTX *r_ctx = NULL; + ERL_NIF_TERM res = enif_make_list(env, 0); + if (argc == 0) // use global registration + { + pthread_mutex_lock(&GRegLock); + if (!G_r_ctx) + { + pthread_mutex_unlock(&GRegLock); + return res; + } + r_ctx = G_r_ctx; + } + else + { + if (!enif_get_resource(env, argv[0], ctx_reg_t, (void **)&r_ctx)) + { + return ERROR_TUPLE_2(ATOM_BADARG); + } + } + enif_mutex_lock(r_ctx->lock); + CXPLAT_LIST_ENTRY *Entry = r_ctx->Connections.Flink; + while (Entry != &r_ctx->Connections) + { + QuicerConnCTX *c_ctx + = CXPLAT_CONTAINING_RECORD(Entry, QuicerConnCTX, RegistrationLink); + if (get_conn_handle(c_ctx)) + { + res = enif_make_list_cell(env, enif_make_resource(env, c_ctx), res); + put_conn_handle(c_ctx); + } + Entry = Entry->Flink; + } + enif_mutex_unlock(r_ctx->lock); + + if (argc == 0) // use global registration + { + pthread_mutex_unlock(&GRegLock); + } + return res; +} + ///_* Emacs ///==================================================================== /// Local Variables: diff --git a/c_src/quicer_connection.h b/c_src/quicer_connection.h index b24ae0dc..1c1a5a21 100644 --- a/c_src/quicer_connection.h +++ b/c_src/quicer_connection.h @@ -54,4 +54,7 @@ QUIC_STATUS continue_connection_handshake(QuicerConnCTX *c_ctx); ERL_NIF_TERM peercert1(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM +get_connectionsX(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); + #endif // __QUICER_CONNECTION_H_ diff --git a/c_src/quicer_ctx.c b/c_src/quicer_ctx.c index 5ee06674..bb1b1296 100644 --- a/c_src/quicer_ctx.c +++ b/c_src/quicer_ctx.c @@ -153,6 +153,7 @@ init_c_ctx() c_ctx->is_closed = TRUE; // init c_ctx->config_resource = NULL; c_ctx->peer_cert = NULL; + CxPlatListInitializeHead(&c_ctx->RegistrationLink); return c_ctx; } @@ -188,6 +189,21 @@ destroy_c_ctx(QuicerConnCTX *c_ctx) X509_STORE_free(c_ctx->trusted); c_ctx->trusted = NULL; } + + QuicerRegistrationCTX *r_ctx; + if (c_ctx->r_ctx) + { + r_ctx = c_ctx->r_ctx; + } + else + { + r_ctx = G_r_ctx; + } + + enif_mutex_lock(r_ctx->lock); + CxPlatListEntryRemove(&c_ctx->RegistrationLink); + enif_mutex_unlock(r_ctx->lock); + enif_demonitor_process(c_ctx->env, c_ctx, &c_ctx->owner_mon); enif_release_resource(c_ctx); } diff --git a/c_src/quicer_ctx.h b/c_src/quicer_ctx.h index 7c0bf459..4212c553 100644 --- a/c_src/quicer_ctx.h +++ b/c_src/quicer_ctx.h @@ -86,6 +86,7 @@ typedef struct QuicerConnCTX // for client, alloc on its own QuicerConfigCTX *config_resource; QuicerRegistrationCTX *r_ctx; + CXPLAT_LIST_ENTRY RegistrationLink; HQUIC Connection; QUICER_ACCEPTOR_QUEUE *acceptor_queue; ACCEPTOR *owner; diff --git a/c_src/quicer_listener.c b/c_src/quicer_listener.c index 91c975e0..93b04a9b 100644 --- a/c_src/quicer_listener.c +++ b/c_src/quicer_listener.c @@ -47,6 +47,7 @@ ServerListenerCallback(__unused_parm__ HQUIC Listener, // Note, c_ctx is newly init here, don't grab lock. // c_ctx = init_c_ctx(); + c_ctx->r_ctx = l_ctx->r_ctx; ErlNifEnv *env = c_ctx->env; if (!c_ctx) @@ -195,6 +196,23 @@ ServerListenerCallback(__unused_parm__ HQUIC Listener, (void *)ServerConnectionCallback, c_ctx); + QuicerRegistrationCTX *r_ctx; + if (l_ctx->r_ctx) + { + r_ctx = l_ctx->r_ctx; + } + else + { + r_ctx = G_r_ctx; + } + + if (r_ctx) + { + enif_mutex_lock(r_ctx->lock); + CxPlatListInsertTail(&r_ctx->Connections, &c_ctx->RegistrationLink); + enif_mutex_unlock(r_ctx->lock); + } + c_ctx->is_closed = FALSE; // new connection enif_clear_env(env); break; diff --git a/c_src/quicer_nif.c b/c_src/quicer_nif.c index be864f0c..861449d1 100644 --- a/c_src/quicer_nif.c +++ b/c_src/quicer_nif.c @@ -1568,7 +1568,9 @@ static ErlNifFunc nif_funcs[] = { { "get_conn_rid", 1, get_conn_rid1, 1}, { "get_stream_rid", 1, get_stream_rid1, 1}, { "get_listeners", 0, get_listenersX, 0}, - { "get_listeners", 1, get_listenersX, 0} + { "get_listeners", 1, get_listenersX, 0}, + { "get_connections", 0, get_connectionsX, 0}, + { "get_connections", 1, get_connectionsX, 0}, // clang-format on }; diff --git a/src/quicer.erl b/src/quicer.erl index cd531019..83f14473 100644 --- a/src/quicer.erl +++ b/src/quicer.erl @@ -114,6 +114,8 @@ , open_connection/0 , get_listeners/0 , get_listeners/1 + , get_connections/0 + , get_connections/1 , close_registration/1 ]). @@ -990,6 +992,19 @@ get_listeners(global) -> get_listeners(Reg) -> quicer_nif:get_listeners(Reg). + +%% @doc Get a list connections under global registration +-spec get_connections() -> quicer_nif:get_connections(). +get_connections() -> + quicer_nif:get_connections(). + +%% @doc Get a list of connections under registration handle +-spec get_connections(Reg | global) -> quicer_nif:get_connections(Reg). +get_connections(global) -> + quicer_nif:get_connections(); +get_connections(Reg) -> + quicer_nif:get_connections(Reg). + %% @doc set controlling process for Connection/Stream. %% mimic {@link ssl:controlling_process/2} %% @see wait_for_handoff/2 diff --git a/src/quicer_nif.erl b/src/quicer_nif.erl index e79f362c..3d5297a7 100644 --- a/src/quicer_nif.erl +++ b/src/quicer_nif.erl @@ -55,6 +55,8 @@ , open_connection/1 , get_listeners/0 , get_listeners/1 + , get_connections/0 + , get_connections/1 ]). -export([abi_version/0]). @@ -310,7 +312,15 @@ get_listeners() -> erlang:nif_error(nif_library_not_loaded). -spec get_listeners(reg_handle()) -> [listener_handle()] | {error, badarg}. -get_listeners(_RegHandle) -> +get_listeners(_) -> + erlang:nif_error(nif_library_not_loaded). + +-spec get_connections() -> [connection_handle()]. +get_connections() -> + erlang:nif_error(nif_library_not_loaded). + +-spec get_connections(reg_handle()) -> [connection_handle()] | {error, badarg}. +get_connections(_RegHandle) -> erlang:nif_error(nif_library_not_loaded). %% Internals diff --git a/test/quicer_connection_SUITE.erl b/test/quicer_connection_SUITE.erl index f4d6d610..60461a1c 100644 --- a/test/quicer_connection_SUITE.erl +++ b/test/quicer_connection_SUITE.erl @@ -650,6 +650,39 @@ tc_conn_opt_local_uni_stream_count(Config) -> ct:fail("listener_timeout") end. +tc_conn_list(Config) -> + Port = select_port(), + Owner = self(), + {SPid, _Ref} = spawn_monitor(fun() -> echo_server(Owner, Config, Port) end), + Reg = proplists:get_value(quic_registration, Config, undefined), + receive + listener_ready -> + case Reg of + undefined -> + ?assertEqual(0, length(quicer:get_connections())); + Reg -> + ?assertEqual(0, length(quicer:get_connections(Reg))) + end + after 5000 -> + ct:fail("listener_timeout") + end, + {ok, Conn} = quicer:connect("127.0.0.1", Port, default_conn_opts(Config), 5000), + {ok, Stm} = quicer:start_stream(Conn, []), + {ok, 4} = quicer:send(Stm, <<"ping">>), + {ok, Cnt} = quicer:getopt(Conn, param_conn_local_unidi_stream_count), + ?assert(is_integer(Cnt)), + Conns = case Reg of + undefined -> + quicer:get_connections(); + Reg -> + quicer:get_connections(Reg) + end, + ?assertEqual(2, length(Conns)), + + {ok, ClientName} = quicer:sockname(Conn), + ?assertMatch([{ok, ClientName}, {ok, {_, Port}}], + lists:map(fun quicer:peername/1, Conns)), + SPid ! done. %%% %%% Helpers