From cdfc0e0ea79c94715368a225667aab3330426e0b Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Thu, 29 Jun 2023 01:27:46 +0900 Subject: [PATCH] Add support for ICE consent freshness (requires libnice >= 0.1.19) (#3234) --- conf/janus.jcfg.sample.in | 8 ++++-- configure.ac | 6 +++++ src/ice.c | 54 +++++++++++++++++++++++++++++++++++++-- src/ice.h | 9 +++++++ src/janus.c | 4 +++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/conf/janus.jcfg.sample.in b/conf/janus.jcfg.sample.in index 3ea0e3f168..eb7c5bed70 100644 --- a/conf/janus.jcfg.sample.in +++ b/conf/janus.jcfg.sample.in @@ -270,8 +270,11 @@ media: { # it should work in ICE-Lite mode (by default it doesn't). If libnice is # at least 0.1.15, you can choose which ICE nomination mode to use: valid # values are "regular" and "aggressive" (the default depends on the libnice -# version itself; if we can set it, we set aggressive nomination). You can -# also configure whether to use connectivity checks as keep-alives, which +# version itself; if we can set it, we set aggressive nomination). If +# libnice is at least 0.1.19, you can enable consent freshness checks for +# PeerConnections as well: this will issue regular checks to check whether +# or not the WebRTC peer isn't available anymore. Enabling consent freshness +# will automatically also enable using connectivity checks as keep-alives, which # might help detecting when a peer is no longer available (notice that # current libnice master is breaking connections after 50 seconds when # keepalive-conncheck is being used, so if you want to use it, better @@ -286,6 +289,7 @@ nat: { nice_debug = false #full_trickle = true #ice_nomination = "regular" + #ice_consent_freshness = true #ice_keepalive_conncheck = true #ice_lite = true #ice_tcp = true diff --git a/configure.ac b/configure.ac index 658b9f44b8..0715f79a86 100644 --- a/configure.ac +++ b/configure.ac @@ -424,6 +424,12 @@ AC_CHECK_LIB([nice], [AC_MSG_NOTICE([libnice version does not have nice_agent_new_full])] ) +AC_CHECK_LIB([nice], + [nice_agent_consent_lost], + [AC_DEFINE(HAVE_CONSENT_FRESHNESS)], + [AC_MSG_NOTICE([libnice version does not have nice_agent_consent_lost])] + ) + AC_CHECK_LIB([dl], [dlopen], [JANUS_MANUAL_LIBS="${JANUS_MANUAL_LIBS} -ldl"], diff --git a/src/ice.c b/src/ice.c index 9f7e55c54b..2e2af47370 100644 --- a/src/ice.c +++ b/src/ice.c @@ -140,13 +140,35 @@ const char *janus_ice_get_nomination_mode(void) { } #endif +/* ICE consent freshness */ +static gboolean janus_ice_consent_freshness = FALSE; +void janus_ice_set_consent_freshness_enabled(gboolean enabled) { +#ifndef HAVE_CONSENT_FRESHNESS + if(enabled) { + JANUS_LOG(LOG_WARN, "libnice version doesn't support consent freshness\n"); + return; + } +#endif + janus_ice_consent_freshness = enabled; + if(janus_ice_consent_freshness) { + JANUS_LOG(LOG_INFO, "Using content freshness checks in PeerConnection\n"); + janus_ice_set_keepalive_conncheck_enabled(TRUE); + } +} +gboolean janus_ice_is_consent_freshness_enabled(void) { + return janus_ice_consent_freshness; +} + /* Keepalive via connectivity checks */ static gboolean janus_ice_keepalive_connchecks = FALSE; void janus_ice_set_keepalive_conncheck_enabled(gboolean enabled) { + if(janus_ice_consent_freshness && !enabled) { + JANUS_LOG(LOG_WARN, "Can't disable connectivity checks as PeerConnection keep-alives, consent freshness is enabled\n"); + return; + } janus_ice_keepalive_connchecks = enabled; if(janus_ice_keepalive_connchecks) { JANUS_LOG(LOG_INFO, "Using connectivity checks as PeerConnection keep-alives\n"); - JANUS_LOG(LOG_WARN, "Notice that the current libnice master is breaking connections after 50s when keepalive-conncheck enabled. As such, better to stick to 0.1.18 until the issue is addressed upstream\n"); } } gboolean janus_ice_is_keepalive_conncheck_enabled(void) { @@ -862,6 +884,25 @@ static void janus_ice_notify_media(janus_ice_handle *handle, char *mid, gboolean } } +static void janus_ice_notify_ice_failed(janus_ice_handle *handle) { + if(handle == NULL) + return; + /* Prepare JSON event to notify user/application */ + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Notifying WebRTC ICE failure; %p\n", handle->handle_id, handle); + janus_session *session = (janus_session *)handle->session; + if(session == NULL) + return; + json_t *event = json_object(); + json_object_set_new(event, "janus", json_string("ice-failed")); + json_object_set_new(event, "session_id", json_integer(session->session_id)); + json_object_set_new(event, "sender", json_integer(handle->handle_id)); + if(opaqueid_in_api && handle->opaque_id != NULL) + json_object_set_new(event, "opaque_id", json_string(handle->opaque_id)); + /* Send the event */ + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Sending event to transport...; %p\n", handle->handle_id, handle); + janus_session_notify_event(session, event); +} + void janus_ice_notify_hangup(janus_ice_handle *handle, const char *reason) { if(handle == NULL) return; @@ -2076,6 +2117,7 @@ static void janus_ice_cb_component_state_changed(NiceAgent *agent, guint stream_ JANUS_LOG(LOG_ERR, "[%"SCNu64"] No stream %d??\n", handle->handle_id, stream_id); return; } + guint prev_state = pc->state; pc->state = state; /* Notify event handlers */ if(janus_events_is_enabled()) { @@ -2093,6 +2135,11 @@ static void janus_ice_cb_component_state_changed(NiceAgent *agent, guint stream_ gboolean alert_set = janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALERT); if(alert_set) return; + if(prev_state == NICE_COMPONENT_STATE_CONNECTED || prev_state == NICE_COMPONENT_STATE_READY) { + /* Failed after connected/ready means consent freshness detected something broken: + * notify the user via a Janus API event and then fire the 'failed' timer as sual */ + janus_ice_notify_ice_failed(handle); + } gboolean trickle_recv = (!janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_TRICKLE) || janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_ALL_TRICKLES)); gboolean answer_recv = janus_flags_is_set(&handle->webrtc_flags, JANUS_ICE_HANDLE_WEBRTC_GOT_ANSWER); JANUS_LOG(LOG_WARN, "[%"SCNu64"] ICE failed for component %d in stream %d, but let's give it some time... (trickle %s, answer %s, alert %s)\n", @@ -3527,12 +3574,15 @@ int janus_ice_setup_local(janus_ice_handle *handle, gboolean offer, gboolean tri JANUS_LOG(LOG_INFO, "[%"SCNu64"] Creating ICE agent (ICE %s mode, %s)\n", handle->handle_id, janus_ice_lite_enabled ? "Lite" : "Full", handle->controlling ? "controlling" : "controlled"); handle->agent = g_object_new(NICE_TYPE_AGENT, - "compatibility", NICE_COMPATIBILITY_DRAFT19, + "compatibility", NICE_COMPATIBILITY_RFC5245, "main-context", handle->mainctx, "reliable", FALSE, "full-mode", janus_ice_lite_enabled ? FALSE : TRUE, #ifdef HAVE_ICE_NOMINATION "nomination-mode", janus_ice_nomination, +#endif +#ifdef HAVE_CONSENT_FRESHNESS + "consent-freshness", janus_ice_consent_freshness ? TRUE : FALSE, #endif "keepalive-conncheck", janus_ice_keepalive_connchecks ? TRUE : FALSE, #ifdef HAVE_LIBNICE_TCP diff --git a/src/ice.h b/src/ice.h index c65eb1a486..2c28d1b5c3 100644 --- a/src/ice.h +++ b/src/ice.h @@ -151,6 +151,15 @@ void janus_ice_set_nomination_mode(const char *nomination); * @returns "regular" or "aggressive" */ const char *janus_ice_get_nomination_mode(void); #endif +/*! \brief Method to enable/disable consent freshness in PeerConnections. + * \note This is only available on libnice >= 0.1.19, and automatically enables + * keepalive connectivity checks too. Documentation for the setting: + * https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent--consent-freshness + * @param[in] enabled Whether the functionality should be enabled or disabled */ +void janus_ice_set_consent_freshness_enabled(gboolean enabled); +/*! \brief Method to check whether consent fresnhess will be enabled in ICE + * @returns true if enabled, false (default) otherwise */ +gboolean janus_ice_is_consent_freshness_enabled(void); /*! \brief Method to enable/disable connectivity checks as keepalives for PeerConnections. * \note The main rationale behind this setting is provided in the libnice documentation: * https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent--keepalive-conncheck diff --git a/src/janus.c b/src/janus.c index 0f99ae1c9c..80be2646c0 100644 --- a/src/janus.c +++ b/src/janus.c @@ -366,6 +366,7 @@ static json_t *janus_info(const char *transaction) { #ifdef HAVE_ICE_NOMINATION json_object_set_new(info, "ice-nomination", json_string(janus_ice_get_nomination_mode())); #endif + json_object_set_new(info, "ice-consent-freshness", janus_ice_is_consent_freshness_enabled() ? json_true() : json_false()); json_object_set_new(info, "ice-keepalive-conncheck", janus_ice_is_keepalive_conncheck_enabled() ? json_true() : json_false()); json_object_set_new(info, "full-trickle", janus_ice_is_full_trickle_enabled() ? json_true() : json_false()); json_object_set_new(info, "mdns-enabled", janus_ice_is_mdns_enabled() ? json_true() : json_false()); @@ -5201,6 +5202,9 @@ gint main(int argc, char *argv[]) { janus_ice_set_nomination_mode(item->value); #endif } + item = janus_config_get(config, config_nat, janus_config_type_item, "ice_consent_freshness"); + if(item && item->value) + janus_ice_set_consent_freshness_enabled(janus_is_true(item->value)); item = janus_config_get(config, config_nat, janus_config_type_item, "ice_keepalive_conncheck"); if(item && item->value) janus_ice_set_keepalive_conncheck_enabled(janus_is_true(item->value));