From d1769dc388a87434b47b6210d1c2a7e84b0dd2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Pablo=20Labajo=20Izquierdo?= Date: Thu, 28 Apr 2022 15:31:57 +0200 Subject: [PATCH] WebRTC: DTLS quick connection (#37) ## What is the current behavior you want to change? Current Kurento WebRTC connection introduces unneeded time to complete connection when using STUN or TURN candidates. DTLS is started by the peer acting as client (with property "is-client" to TRUE) sending an initial DTLS Hello packet. That packet should be responded by the other peer and exchange of keys will happen. If the other peer does not respond, the client will wait for an amount of time before resending DTLS Hello packet. That amount of time starts in 1 second and doubles on each retry, so the retries comes at 1 second, then 2 seconds, then 4, and so on. In KMS the DTLS connection is managed by the KmsWebrtcTransportSink component, more specifically by the embbeded dtlssrtpenc component. When instantiated in a KmsWebrtcTransportSinkNice component, the sending is managed by a nicesink element associated to the niceagent of the connection. The problem appears because dtlssrtpenc element initiates DTLS connection as soon as it reaches the PAUSED or PLAYING state, and this always happens before the ICE negotiation has reached to a first valid candidate pair, at least when no HOST valid candidates are found. When this happens, the first DTLS Hello packet is silently dropped by the nicesink as there is no ICE connection established yet. And the timeout for next DTLS Hello starts to run. The outcome is that when using STUN or TURN candidates, once a first valid pair is found, one or more DTLS Hello packets have already been sent, and as the timeout doubles each retry, it is likely that after ICE connection is established waiting for next retry will take a similar time to the what it took to establish ICE connection. ## What is the new behavior provided by this change? This change consist on the following modifications: * To prevent dtlssrtpenc to trigger DTLS initiation, we lock its state to NULL until we know for sure that it is ready to work, that is, until we know the peer is acting as server, or, if the peer is acting as client, until ICE gets to a CONNECTED state. At those points we unlock dtlssrtpenc state and synchronize with its parent. In fact, to impact less possible elements it is not the dtlssrtpenc component but the dtlsenc embbeded on it * We added a virtual method to the KmsWebrtcTransportSink to set the is-client property, to allow the KmsWebrtcTransportSinkNice to decide when to start DTLS. This must be taken into account for potential future KmsWebrtcTransportSink derived classes * We added a method to start DTLS connection. This is used on KmsWebrtcTransportSinkNice as soon as the sink knows it is server, or if it is client, as soon as ICE gets to a CONNECTED state * On KmsWebrtcTransportSinkNice configure, we subscribe to the ICE connection event to know when ICE connection reaches to CONNECTED state. --- .../kmswebrtcbundleconnection.c | 2 +- .../webrtcendpoint/kmswebrtcconnection.c | 2 +- .../kmswebrtcrtcpmuxconnection.c | 2 +- .../webrtcendpoint/kmswebrtcsctpconnection.c | 2 +- .../webrtcendpoint/kmswebrtctransportsink.c | 104 ++++++++++++- .../webrtcendpoint/kmswebrtctransportsink.h | 7 + .../kmswebrtctransportsinknice.c | 40 +++++ tests/check/element/webrtcendpoint.c | 1 + tests/server/webRtcEndpoint.cpp | 137 +++++++++++++++++- 9 files changed, 284 insertions(+), 13 deletions(-) diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcbundleconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcbundleconnection.c index 34ae34750..ae6a570d0 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcbundleconnection.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcbundleconnection.c @@ -92,7 +92,7 @@ kms_webrtc_bundle_connection_add (KmsIRtpConnection * base_rtp_conn, KmsWebRtcBundleConnectionPrivate *priv = self->priv; KmsWebRtcTransport *tr = priv->tr; - g_object_set (G_OBJECT (tr->sink->dtlssrtpenc), "is-client", active, NULL); + kms_webrtc_transport_sink_set_dtls_is_client (tr->sink, active); gst_bin_add (bin, GST_ELEMENT (g_object_ref (tr->src))); gst_bin_add (bin, GST_ELEMENT (g_object_ref (tr->sink))); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcconnection.c index 52a74f331..aaee0cdd4 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcconnection.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcconnection.c @@ -79,7 +79,7 @@ kms_webrtc_connection_get_certificate_pem (KmsWebRtcBaseConnection * base_conn) static void add_tr (KmsWebRtcTransport * tr, GstBin * bin, gboolean is_client) { - g_object_set (G_OBJECT (tr->sink->dtlssrtpenc), "is-client", is_client, NULL); + kms_webrtc_transport_sink_set_dtls_is_client(tr->sink, is_client); gst_bin_add (bin, GST_ELEMENT (g_object_ref (tr->src))); gst_bin_add (bin, GST_ELEMENT (g_object_ref (tr->sink))); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcrtcpmuxconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcrtcpmuxconnection.c index d24bf5766..b2165ce2f 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcrtcpmuxconnection.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcrtcpmuxconnection.c @@ -87,7 +87,7 @@ kms_webrtc_rtcp_mux_connection_add (KmsIRtpConnection * base_rtp_conn, KmsWebRtcTransport *tr = priv->tr; /* srcs */ - g_object_set (G_OBJECT (tr->sink->dtlssrtpenc), "is-client", active, NULL); + kms_webrtc_transport_sink_set_dtls_is_client(tr->sink, active); gst_bin_add (bin, GST_ELEMENT (g_object_ref (tr->src))); gst_bin_add (bin, GST_ELEMENT (g_object_ref (tr->sink))); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtcsctpconnection.c b/src/gst-plugins/webrtcendpoint/kmswebrtcsctpconnection.c index 1f7d4406d..1e47390b0 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtcsctpconnection.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtcsctpconnection.c @@ -79,7 +79,7 @@ kms_webrtc_sctp_connection_add (KmsIRtpConnection * base_conn, GstBin * bin, KmsWebRtcSctpConnectionPrivate *priv = self->priv; KmsWebRtcTransport *tr = priv->tr; - g_object_set (G_OBJECT (tr->sink->dtlssrtpenc), "is-client", active, NULL); + kms_webrtc_transport_sink_set_dtls_is_client(tr->sink, active); gst_bin_add (bin, GST_ELEMENT (g_object_ref (self->priv->tr->src))); gst_bin_add (bin, GST_ELEMENT (g_object_ref (self->priv->tr->sink))); diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.c b/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.c index 87edf5c4e..31382d5b7 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.c @@ -29,39 +29,88 @@ GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define kms_webrtc_transport_sink_parent_class parent_class G_DEFINE_TYPE (KmsWebrtcTransportSink, kms_webrtc_transport_sink, GST_TYPE_BIN); -#define FUNNEL_NAME "funnel" -#define SRTPENC_NAME "srtp-encoder" +#define FUNNEL_FACTORY_NAME "funnel" +#define SRTPENC_FACTORY_NAME "srtpenc" +#define DTLS_ENCODER_FACTORY_NAME "dtlsenc" + + + +static GstElement* +kms_webrtc_transport_sink_get_element_in_dtlssrtpenc (KmsWebrtcTransportSink *self, const gchar *factory_name) +{ + GstIterator *iterator; + GValue item = G_VALUE_INIT; + GstElement *element; + GstElementFactory *factory; + + factory = gst_element_factory_find (factory_name); + + if (factory == NULL) { + GST_WARNING_OBJECT(self, "Factory %s not installed", factory_name); + return NULL; + } + + // Until KMS is updated to GStreamer 1.18 and method https://gstreamer.freedesktop.org/documentation/gstreamer/gstbin.html#gst_bin_iterate_all_by_element_factory_name + // is available, this will do + iterator = gst_bin_iterate_elements (GST_BIN(self->dtlssrtpenc)); + while (gst_iterator_next (iterator, &item) == GST_ITERATOR_OK) { + element = (GstElement *) g_value_get_object (&item); + if (factory == gst_element_get_factory (element)) { + break; + } else { + element = NULL; + } + } + gst_iterator_free (iterator); + g_object_unref (factory); + + if (element != NULL) { + element = g_value_dup_object (&item); + g_value_unset (&item); + } + return element; +} static void kms_webrtc_transport_sink_init (KmsWebrtcTransportSink * self) { + GstElement *dtls_encoder; + self->dtlssrtpenc = gst_element_factory_make ("dtlssrtpenc", NULL); + dtls_encoder = kms_webrtc_transport_sink_get_element_in_dtlssrtpenc (self, DTLS_ENCODER_FACTORY_NAME); + if (dtls_encoder != NULL) { + gst_element_set_locked_state (dtls_encoder, TRUE); + g_object_unref (dtls_encoder); + } else { + GST_WARNING_OBJECT (self, "Cannot get DTLS encoder"); + } } void -kms_webrtc_transport_sink_connect_elements (KmsWebrtcTransportSink * self) +kms_webrtc_transport_sink_connect_elements (KmsWebrtcTransportSink *self) { GstElement *funnel, *srtpenc; gst_bin_add_many (GST_BIN (self), self->dtlssrtpenc, self->sink, NULL); gst_element_link (self->dtlssrtpenc, self->sink); - funnel = gst_bin_get_by_name (GST_BIN (self->dtlssrtpenc), FUNNEL_NAME); + funnel = kms_webrtc_transport_sink_get_element_in_dtlssrtpenc (self, FUNNEL_FACTORY_NAME); if (funnel != NULL) { g_object_set (funnel, "forward-sticky-events-mode", 0 /* never */ , NULL); g_object_unref (funnel); } else { - GST_WARNING ("Cannot get funnel with name %s", FUNNEL_NAME); + GST_WARNING ("Cannot get funnel with factory %s", FUNNEL_FACTORY_NAME); } - srtpenc = gst_bin_get_by_name (GST_BIN (self->dtlssrtpenc), SRTPENC_NAME); + srtpenc = kms_webrtc_transport_sink_get_element_in_dtlssrtpenc (self, SRTPENC_FACTORY_NAME); if (srtpenc != NULL) { g_object_set (srtpenc, "allow-repeat-tx", TRUE, "replay-window-size", RTP_RTX_SIZE, NULL); g_object_unref (srtpenc); } else { - GST_WARNING ("Cannot get srtpenc with name %s", SRTPENC_NAME); + GST_WARNING ("Cannot get srtpenc with factory %s", SRTPENC_FACTORY_NAME); } + } void @@ -77,6 +126,18 @@ kms_webrtc_transport_sink_configure_default (KmsWebrtcTransportSink * self, } } +void +kms_webrtc_transport_sink_set_dtls_is_client_default (KmsWebrtcTransportSink * self, + gboolean is_client) +{ + g_object_set (G_OBJECT (self->dtlssrtpenc), "is-client", is_client, NULL); + if (is_client) { + GST_DEBUG_OBJECT(self, "Set as DTLS client (handshake initiator)"); + } else { + GST_DEBUG_OBJECT(self, "Set as DTLS server (wait for handshake)"); + } +} + void kms_webrtc_transport_sink_configure (KmsWebrtcTransportSink * self, KmsIceBaseAgent * agent, const char *stream_id, guint component_id) @@ -87,12 +148,23 @@ kms_webrtc_transport_sink_configure (KmsWebrtcTransportSink * self, klass->configure (self, agent, stream_id, component_id); } +void +kms_webrtc_transport_sink_set_dtls_is_client (KmsWebrtcTransportSink * self, + gboolean is_client) +{ + KmsWebrtcTransportSinkClass *klass = + KMS_WEBRTC_TRANSPORT_SINK_CLASS (G_OBJECT_GET_CLASS (self)); + + klass->set_dtls_is_client (self, is_client); +} + static void kms_webrtc_transport_sink_class_init (KmsWebrtcTransportSinkClass * klass) { GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); klass->configure = kms_webrtc_transport_sink_configure_default; + klass->set_dtls_is_client = kms_webrtc_transport_sink_set_dtls_is_client_default; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, GST_DEFAULT_NAME); @@ -104,6 +176,24 @@ kms_webrtc_transport_sink_class_init (KmsWebrtcTransportSinkClass * klass) "Miguel París Díaz "); } +void +kms_webrtc_transport_sink_start_dtls (KmsWebrtcTransportSink * self) +{ + GstElement *dtls_encoder; + + dtls_encoder = kms_webrtc_transport_sink_get_element_in_dtlssrtpenc (self, DTLS_ENCODER_FACTORY_NAME); + if (dtls_encoder != NULL) { + gst_element_set_locked_state (dtls_encoder, FALSE); + gst_element_sync_state_with_parent (dtls_encoder); + GST_DEBUG_OBJECT(self, "Starting DTLS"); + + g_object_unref (dtls_encoder); + } else { + GST_WARNING_OBJECT ("Cannot get DTLS encoder with factory %s", DTLS_ENCODER_FACTORY_NAME); + } +} + + KmsWebrtcTransportSink * kms_webrtc_transport_sink_new () { diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.h b/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.h index 711ecee41..27d538cb5 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.h +++ b/src/gst-plugins/webrtcendpoint/kmswebrtctransportsink.h @@ -55,6 +55,9 @@ struct _KmsWebrtcTransportSinkClass KmsIceBaseAgent *agent, const char *stream_id, guint component_id); + + void (*set_dtls_is_client) (KmsWebrtcTransportSink * self, + gboolean is_client); }; GType kms_webrtc_transport_sink_get_type (void); @@ -65,5 +68,9 @@ void kms_webrtc_transport_sink_configure (KmsWebrtcTransportSink * self, KmsIceBaseAgent *agent, const char *stream_id, guint component_id); +void kms_webrtc_transport_sink_set_dtls_is_client (KmsWebrtcTransportSink * self, + gboolean is_client); +void kms_webrtc_transport_sink_start_dtls (KmsWebrtcTransportSink * self); + G_END_DECLS #endif /* __KMS_WEBRTC_TRANSPORT_SINK_H__ */ diff --git a/src/gst-plugins/webrtcendpoint/kmswebrtctransportsinknice.c b/src/gst-plugins/webrtcendpoint/kmswebrtctransportsinknice.c index 594f6b860..8f2117823 100644 --- a/src/gst-plugins/webrtcendpoint/kmswebrtctransportsinknice.c +++ b/src/gst-plugins/webrtcendpoint/kmswebrtctransportsinknice.c @@ -42,6 +42,25 @@ kms_webrtc_transport_sink_nice_init (KmsWebrtcTransportSinkNice * self) kms_webrtc_transport_sink_connect_elements (parent); } +static void +kms_webrtc_transport_sink_nice_component_state_changed (KmsIceBaseAgent * agent, + char *stream_id, guint component_id, IceState state, + KmsWebrtcTransportSink * self) +{ + gboolean is_client; + + GST_LOG_OBJECT (self, + "[IceComponentStateChanged] state: %s, stream_id: %s, component_id: %u", + kms_ice_base_agent_state_to_string (state), stream_id, component_id); + + g_object_get (G_OBJECT (self->dtlssrtpenc), "is-client", + &is_client, NULL); + + if ((state == ICE_STATE_CONNECTED) && is_client) { + kms_webrtc_transport_sink_start_dtls (self); + } +} + void kms_webrtc_transport_sink_nice_configure (KmsWebrtcTransportSink * self, KmsIceBaseAgent * agent, const char *stream_id, guint component_id) @@ -53,6 +72,26 @@ kms_webrtc_transport_sink_nice_configure (KmsWebrtcTransportSink * self, "agent", kms_ice_nice_agent_get_agent (nice_agent), "stream", id, "component", component_id, "sync", FALSE, "async", FALSE, NULL); + + g_signal_connect (nice_agent, "on-ice-component-state-changed", + G_CALLBACK (kms_webrtc_transport_sink_nice_component_state_changed), self); +} + +void +kms_webrtc_transport_sink_nice_set_dtls_is_client (KmsWebrtcTransportSink * self, + gboolean is_client) +{ + KmsWebrtcTransportSinkNiceClass *klass = + KMS_WEBRTC_TRANSPORT_SINK_NICE_CLASS (G_OBJECT_GET_CLASS (self)); + KmsWebrtcTransportSinkClass *parent_klass = + KMS_WEBRTC_TRANSPORT_SINK_CLASS (g_type_class_peek_parent(klass)); + + parent_klass->set_dtls_is_client (self, is_client); + + if (!is_client) { + kms_webrtc_transport_sink_start_dtls (self); + } + } static void @@ -64,6 +103,7 @@ kms_webrtc_transport_sink_nice_class_init (KmsWebrtcTransportSinkNiceClass * base_class = KMS_WEBRTC_TRANSPORT_SINK_CLASS (klass); base_class->configure = kms_webrtc_transport_sink_nice_configure; + base_class->set_dtls_is_client = kms_webrtc_transport_sink_nice_set_dtls_is_client; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_DEFAULT_NAME, 0, GST_DEFAULT_NAME); diff --git a/tests/check/element/webrtcendpoint.c b/tests/check/element/webrtcendpoint.c index 44272fa54..a8204ab86 100644 --- a/tests/check/element/webrtcendpoint.c +++ b/tests/check/element/webrtcendpoint.c @@ -1744,6 +1744,7 @@ test_data_channels (gboolean bundle) GST_WARNING ("Finishing test"); + g_usleep (500000); gst_element_set_state (pipeline, GST_STATE_NULL); gst_bus_remove_signal_watch (bus); g_object_unref (bus); diff --git a/tests/server/webRtcEndpoint.cpp b/tests/server/webRtcEndpoint.cpp index 70681fe34..075b5bfd7 100644 --- a/tests/server/webRtcEndpoint.cpp +++ b/tests/server/webRtcEndpoint.cpp @@ -32,6 +32,8 @@ #define NUMBER_OF_RECONNECTIONS 5 +#include + using namespace kurento; using namespace boost::unit_test; @@ -180,6 +182,7 @@ ice_state_changes (bool useIpv6) std::condition_variable cv; std::mutex mtx; std::unique_lock lck (mtx); + bool active = true; std::shared_ptr webRtcEpOfferer = createWebrtc(); std::shared_ptr webRtcEpAnswerer = createWebrtc(); @@ -188,11 +191,15 @@ ice_state_changes (bool useIpv6) webRtcEpAnswerer->setName ("answerer"); webRtcEpOfferer->signalOnIceCandidate.connect ([&] (OnIceCandidate event) { - exchange_candidate (event, webRtcEpAnswerer, useIpv6); + if (active) { + exchange_candidate (event, webRtcEpAnswerer, useIpv6); + } }); webRtcEpAnswerer->signalOnIceCandidate.connect ([&] (OnIceCandidate event) { - exchange_candidate (event, webRtcEpOfferer, useIpv6); + if (active) { + exchange_candidate (event, webRtcEpOfferer, useIpv6); + } }); webRtcEpOfferer->signalOnIceComponentStateChanged.connect ([&] ( @@ -213,6 +220,7 @@ ice_state_changes (bool useIpv6) }) ) { BOOST_ERROR ("Timeout waiting for ICE state change"); } + active = false; if (!ice_state_changed) { BOOST_ERROR ("ICE state not chagned"); @@ -234,6 +242,126 @@ ice_state_changes_ipv6 () ice_state_changes (true); } +// This test depends on Gstreamer 1.17+ to be installed and on the +// feature of DTLS connection state and event on property changes as implemented on +// https://github.com/naevatec/kms-elements/tree/dtls-connection-state +// +// That feature will be PR'd when KMS reaches at least GStreamer 1.17 +/****************************************************** +static void +dtls_quick_connection_test (bool useIpv6) +{ + DtlsConnectionState offerer_dtls_connection_state = DtlsConnectionState::FAILED; + DtlsConnectionState answerer_dtls_connection_state = DtlsConnectionState::FAILED; + std::condition_variable cv; + std::mutex mtx; + std::unique_lock lck (mtx); + uint64_t ice_gathering_started = 0; + uint64_t dtls_connection_started_offerer = 0; + uint64_t dtls_connection_started_answerer = 0; + uint64_t dtls_connection_connecting_offerer = 0; + uint64_t dtls_connection_connecting_answerer = 0; + uint64_t dtls_connection_connected_offerer = 0; + uint64_t dtls_connection_connected_answerer = 0; + + std::shared_ptr webRtcEpOfferer = createWebrtc(); + std::shared_ptr webRtcEpAnswerer = createWebrtc(); + + webRtcEpOfferer->setName ("offerer"); + webRtcEpAnswerer->setName ("answerer"); + + webRtcEpOfferer->signalOnIceCandidate.connect ([&] (OnIceCandidate event) { + exchange_candidate (event, webRtcEpAnswerer, useIpv6); + }); + + webRtcEpAnswerer->signalOnIceCandidate.connect ([&] (OnIceCandidate event) { + exchange_candidate (event, webRtcEpOfferer, useIpv6); + }); + + webRtcEpOfferer->signalDtlsConnectionStateChange.connect ([&] ( + DtlsConnectionStateChange event) { + offerer_dtls_connection_state = *(event.getState()); + BOOST_TEST_MESSAGE("Offerer DTLS connection state: " + offerer_dtls_connection_state.getString() + " component: " + event.getComponentId() + " stream " + event.getStreamId()); + // Using current KMS offerer is passive one so it gets to the NEW state immediately + if (offerer_dtls_connection_state.getValue() == DtlsConnectionState::NEW) { + dtls_connection_started_offerer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } else if (offerer_dtls_connection_state.getValue() == DtlsConnectionState::CONNECTED) { + dtls_connection_connected_offerer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + cv.notify_one(); + } else if (offerer_dtls_connection_state.getValue() == DtlsConnectionState::CONNECTING) { + dtls_connection_connecting_offerer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + }); + + + webRtcEpAnswerer->signalDtlsConnectionStateChange.connect ([&] ( + DtlsConnectionStateChange event) { + answerer_dtls_connection_state = *(event.getState()); + BOOST_TEST_MESSAGE("Answerer DTLS connection state: " + answerer_dtls_connection_state.getString() + " component: " + event.getComponentId() + " stream " + event.getStreamId()); + // Using current KMS answerer is active so its is-client is true and should only be reached after the ICE connection gets to + // CONNECTED state with the feature we are testing. + if (answerer_dtls_connection_state.getValue() == DtlsConnectionState::NEW) { + dtls_connection_started_answerer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } else if (answerer_dtls_connection_state.getValue() == DtlsConnectionState::CONNECTED) { + dtls_connection_connected_answerer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + cv.notify_one(); + } else if (answerer_dtls_connection_state.getValue() == DtlsConnectionState::CONNECTING) { + dtls_connection_connecting_answerer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + }); + + + std::string offer = webRtcEpOfferer->generateOffer (); + std::string answer = webRtcEpAnswerer->processOffer (offer); + webRtcEpOfferer->processAnswer (answer); + + + // Just to check the feature we wait for some seconds, if feature is not working, DTLS Hello should be triggered immediately and + // as it is dropped (no valid candidate pair) exponential backoff will take place + // This delay just makes the ICE connection to take longer + // Although testing with delays is always a not so good idea, due to the dependencies it get about the conditions of the testing host + // in this case we are using it just to enhance the difference between the feature working (DTLS NEW state only reached after ICE connection + // reaching CONNECTED state for answerer), and not working (NEW state reached immediately after processOffer is done on answerer) + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + ice_gathering_started = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + webRtcEpOfferer->gatherCandidates (); + webRtcEpAnswerer->gatherCandidates (); + + if (!cv.wait_for (lck, std::chrono::seconds (4*TIMEOUT), [&] () { + return ((offerer_dtls_connection_state.getValue() == DtlsConnectionState::CONNECTED) && (answerer_dtls_connection_state.getValue() == DtlsConnectionState::CONNECTED)); + }) ) { + BOOST_ERROR ("Timeout waiting for ICE state change"); + } + + // Correct outcome is answerer DTLS NEW state reached after ICE gathering is started + // Incorrect outcome is answerer DTLS NEW state reached before ICE gathering is started + // Either case offerer DTLS NEW state should be reached before ICE gathering is started + if (dtls_connection_started_answerer <= ice_gathering_started) { + BOOST_ERROR ("DTLS quick connection is not working"); + } + if (dtls_connection_started_offerer > ice_gathering_started) { + BOOST_ERROR ("This should not happen"); + } + + releaseWebRtc (webRtcEpOfferer); + releaseWebRtc (webRtcEpAnswerer); +} + + +static void +dtls_quick_connection_test_ipv6 () +{ + dtls_quick_connection_test (true); +} + +static void +dtls_quick_connection_test_ipv4 () +{ + dtls_quick_connection_test (false); +} +******************************/ + static void stun_turn_properties () { @@ -714,6 +842,11 @@ init_unit_test_suite ( int , char *[] ) test->add (BOOST_TEST_CASE ( &stun_turn_properties ), 0, /* timeout */ 15); test->add (BOOST_TEST_CASE ( &media_state_changes_ipv4 ), 0, /* timeout */ 15); test->add (BOOST_TEST_CASE ( &media_state_changes_ipv6 ), 0, /* timeout */ 15); + + /* These tests depend on GStreamer 1.17+ and feature on https://github.com/naevatec/kms-elements/tree/dtls-connection-state*/ + //test->add (BOOST_TEST_CASE (&dtls_quick_connection_test_ipv4), 0, /* timeout */ 15); + //test->add (BOOST_TEST_CASE (&dtls_quick_connection_test_ipv6), 0, /* timeout */ 15); + test->add (BOOST_TEST_CASE ( &connection_state_changes_ipv4 ), 0, /* timeout */ 15); test->add (BOOST_TEST_CASE ( &connection_state_changes_ipv6 ),