2 * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
25 #include "nicestream.h"
29 #ifndef NICE_CHECK_VERSION
30 #define NICE_CHECK_VERSION(major,minor,micro) \
31 (NICE_VERSION_MAJOR > (major) || \
32 (NICE_VERSION_MAJOR == (major) && NICE_VERSION_MINOR > (minor)) || \
33 (NICE_VERSION_MAJOR == (major) && NICE_VERSION_MINOR == (minor) && \
34 NICE_VERSION_MICRO >= (micro)))
39 * - are locally generated remote candidates meant to be readded to libnice?
42 static GstUri *_validate_turn_server (GstWebRTCNice * ice, const gchar * s);
44 #define GST_CAT_DEFAULT gst_webrtc_nice_debug
45 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
57 struct _GstWebRTCNicePrivate
59 NiceAgent *nice_agent;
61 GArray *nice_stream_map;
64 GMainContext *main_context;
69 GstWebRTCICEOnCandidateFunc on_candidate;
70 gpointer on_candidate_data;
71 GDestroyNotify on_candidate_notify;
76 GHashTable *turn_servers;
79 #define gst_webrtc_nice_parent_class parent_class
80 G_DEFINE_TYPE_WITH_CODE (GstWebRTCNice, gst_webrtc_nice,
81 GST_TYPE_WEBRTC_ICE, G_ADD_PRIVATE (GstWebRTCNice)
82 GST_DEBUG_CATEGORY_INIT (gst_webrtc_nice_debug, "webrtcnice", 0,
86 _unlock_pc_thread (GMutex * lock)
88 g_mutex_unlock (lock);
89 return G_SOURCE_REMOVE;
93 _gst_nice_thread (GstWebRTCNice * ice)
95 g_mutex_lock (&ice->priv->lock);
96 ice->priv->main_context = g_main_context_new ();
97 ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE);
99 g_cond_broadcast (&ice->priv->cond);
100 g_main_context_invoke (ice->priv->main_context,
101 (GSourceFunc) _unlock_pc_thread, &ice->priv->lock);
103 g_main_loop_run (ice->priv->loop);
105 g_mutex_lock (&ice->priv->lock);
106 g_main_context_unref (ice->priv->main_context);
107 ice->priv->main_context = NULL;
108 g_main_loop_unref (ice->priv->loop);
109 ice->priv->loop = NULL;
110 g_cond_broadcast (&ice->priv->cond);
111 g_mutex_unlock (&ice->priv->lock);
117 _start_thread (GstWebRTCNice * ice)
119 g_mutex_lock (&ice->priv->lock);
120 ice->priv->thread = g_thread_new (GST_OBJECT_NAME (ice),
121 (GThreadFunc) _gst_nice_thread, ice);
123 while (!ice->priv->loop)
124 g_cond_wait (&ice->priv->cond, &ice->priv->lock);
125 g_mutex_unlock (&ice->priv->lock);
129 _stop_thread (GstWebRTCNice * ice)
131 g_mutex_lock (&ice->priv->lock);
132 g_main_loop_quit (ice->priv->loop);
133 while (ice->priv->loop)
134 g_cond_wait (&ice->priv->cond, &ice->priv->lock);
135 g_mutex_unlock (&ice->priv->lock);
137 g_thread_unref (ice->priv->thread);
140 struct NiceStreamItem
143 guint nice_stream_id;
144 GstWebRTCICEStream *stream;
147 /* TRUE to continue, FALSE to stop */
148 typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item,
152 _nice_stream_item_foreach (GstWebRTCNice * ice, NiceStreamItemForeachFunc func,
157 len = ice->priv->nice_stream_map->len;
158 for (i = 0; i < len; i++) {
159 struct NiceStreamItem *item =
160 &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem,
163 if (!func (item, data))
168 /* TRUE for match, FALSE otherwise */
169 typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item,
174 NiceStreamItemFindFunc func;
176 struct NiceStreamItem *ret;
180 _find_nice_item (struct NiceStreamItem *item, gpointer user_data)
182 struct nice_find *f = user_data;
183 if (f->func (item, f->data)) {
190 static struct NiceStreamItem *
191 _nice_stream_item_find (GstWebRTCNice * ice, NiceStreamItemFindFunc func,
200 _nice_stream_item_foreach (ice, _find_nice_item, &f);
205 #define NICE_MATCH_INIT { -1, -1, NULL }
208 _match (struct NiceStreamItem *item, struct NiceStreamItem *m)
210 if (m->session_id != -1 && m->session_id != item->session_id)
212 if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id)
214 if (m->stream != NULL && m->stream != item->stream)
220 static struct NiceStreamItem *
221 _find_item (GstWebRTCNice * ice, guint session_id, guint nice_stream_id,
222 GstWebRTCICEStream * stream)
224 struct NiceStreamItem m = NICE_MATCH_INIT;
226 m.session_id = session_id;
227 m.nice_stream_id = nice_stream_id;
230 return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
233 static struct NiceStreamItem *
234 _create_nice_stream_item (GstWebRTCNice * ice, guint session_id)
236 struct NiceStreamItem item;
238 item.session_id = session_id;
239 item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 1);
241 GST_WEBRTC_ICE_STREAM (gst_webrtc_nice_stream_new (GST_WEBRTC_ICE (ice),
245 g_array_append_val (ice->priv->nice_stream_map, item);
247 return _find_item (ice, item.session_id, item.nice_stream_id, item.stream);
251 _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
261 colon = g_strstr_len (userinfo, -1, ":");
263 *user = g_uri_unescape_string (userinfo, NULL);
268 /* Check that the first occurence is also the last occurence */
269 if (colon != g_strrstr (userinfo, ":"))
270 GST_WARNING ("userinfo %s contains more than one ':', will assume that the "
271 "first ':' delineates user:pass. You should escape the user and pass "
272 "before adding to the URI.", userinfo);
274 *user = g_uri_unescape_segment (userinfo, colon, NULL);
275 *pass = g_uri_unescape_string (&colon[1], NULL);
278 struct resolve_host_data
282 gboolean main_context_handled;
284 GDestroyNotify notify;
288 on_resolve_host (GResolver * resolver, GAsyncResult * res, gpointer user_data)
290 GTask *task = user_data;
291 struct resolve_host_data *rh;
292 GError *error = NULL;
295 rh = g_task_get_task_data (task);
297 if (!(addresses = g_resolver_lookup_by_name_finish (resolver, res, &error))) {
298 GST_ERROR ("failed to resolve: %s", error->message);
299 g_task_return_error (task, error);
300 g_object_unref (task);
304 GST_DEBUG_OBJECT (rh->ice, "Resolved %d addresses for host %s with data %p",
305 g_list_length (addresses), rh->host, rh);
307 g_task_return_pointer (task, addresses,
308 (GDestroyNotify) g_resolver_free_addresses);
309 g_object_unref (task);
313 free_resolve_host_data (struct resolve_host_data *rh)
315 GST_TRACE_OBJECT (rh->ice, "Freeing data %p for resolving host %s", rh,
319 rh->notify (rh->user_data);
325 static struct resolve_host_data *
326 resolve_host_data_new (GstWebRTCNice * ice, const char *host)
328 struct resolve_host_data *rh = g_new0 (struct resolve_host_data, 1);
331 rh->host = g_strdup (host);
337 resolve_host_main_cb (gpointer user_data)
339 GResolver *resolver = g_resolver_get_default ();
340 GTask *task = user_data;
341 struct resolve_host_data *rh;
343 rh = g_task_get_task_data (task);
344 /* no need to error anymore if the main context disappears and this task is
346 rh->main_context_handled = TRUE;
348 GST_DEBUG_OBJECT (rh->ice, "Resolving host %s", rh->host);
349 g_resolver_lookup_by_name_async (resolver, rh->host, NULL,
350 (GAsyncReadyCallback) on_resolve_host, g_object_ref (task));
352 return G_SOURCE_REMOVE;
356 error_task_if_unhandled (GTask * task)
358 struct resolve_host_data *rh;
360 rh = g_task_get_task_data (task);
362 if (!rh->main_context_handled) {
363 GST_DEBUG_OBJECT (rh->ice, "host resolve for %s with data %p was never "
364 "executed, main context quit?", rh->host, rh);
365 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "%s",
369 g_object_unref (task);
373 resolve_host_async (GstWebRTCNice * ice, const gchar * host,
374 GAsyncReadyCallback cb, gpointer user_data, GDestroyNotify notify)
376 struct resolve_host_data *rh = resolve_host_data_new (ice, host);
379 rh->user_data = user_data;
381 task = g_task_new (rh->ice, NULL, cb, user_data);
383 g_task_set_task_data (task, rh, (GDestroyNotify) free_resolve_host_data);
385 GST_TRACE_OBJECT (rh->ice, "invoking main context for resolving host %s "
386 "with data %p", host, rh);
387 g_main_context_invoke_full (ice->priv->main_context, G_PRIORITY_DEFAULT,
388 resolve_host_main_cb, task, (GDestroyNotify) error_task_if_unhandled);
392 resolve_host_finish (GstWebRTCNice * ice, GAsyncResult * res, GError ** error)
394 g_return_val_if_fail (g_task_is_valid (res, ice), NULL);
396 return g_task_propagate_pointer (G_TASK (res), error);
399 struct turn_server_data
402 guint nice_stream_id;
406 turn_server_data_free (struct turn_server_data *data)
408 gst_uri_unref (data->uri);
414 on_turn_server_resolved (GstWebRTCICE * ice, GAsyncResult * res,
415 struct turn_server_data *user_data)
418 GError *error = NULL;
419 GstUri *turn_server = user_data->uri;
422 const gchar *userinfo, *transport, *scheme;
423 NiceRelayType relays[4] = { 0, };
426 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
428 if (!(addresses = resolve_host_finish (nice, res, &error))) {
429 GST_WARNING_OBJECT (ice, "failed to resolve turn address: %s",
431 g_clear_error (&error);
435 /* XXX: only the first IP is used */
436 ip = g_inet_address_to_string (addresses->data);
438 /* Set the resolved IP as the host since that's what libnice wants */
439 gst_uri_set_host (turn_server, ip);
441 scheme = gst_uri_get_scheme (turn_server);
442 transport = gst_uri_get_query_value (turn_server, "transport");
443 userinfo = gst_uri_get_userinfo (turn_server);
444 _parse_userinfo (userinfo, &user, &pass);
446 if (g_strcmp0 (scheme, "turns") == 0) {
447 relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS;
448 } else if (g_strcmp0 (scheme, "turn") == 0) {
449 if (!transport || g_strcmp0 (transport, "udp") == 0)
450 relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP;
451 if (!transport || g_strcmp0 (transport, "tcp") == 0)
452 relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP;
454 g_assert (relay_n < G_N_ELEMENTS (relays));
456 for (i = 0; i < relay_n; i++) {
457 ret = nice_agent_set_relay_info (nice->priv->nice_agent,
458 user_data->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
459 gst_uri_get_host (turn_server), gst_uri_get_port (turn_server),
460 user, pass, relays[i]);
462 gchar *uri = gst_uri_to_string (turn_server);
463 GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
475 _add_turn_server (GstWebRTCNice * ice, struct NiceStreamItem *item,
476 GstUri * turn_server)
478 struct turn_server_data *data;
481 host = gst_uri_get_host (turn_server);
483 GST_ERROR_OBJECT (ice, "Turn server has no host");
487 data = g_new0 (struct turn_server_data, 1);
488 data->nice_stream_id = item->nice_stream_id;
489 data->uri = gst_uri_copy (turn_server);
491 resolve_host_async (ice, host, (GAsyncReadyCallback) on_turn_server_resolved,
492 data, (GDestroyNotify) turn_server_data_free);
498 struct NiceStreamItem *item;
502 _add_turn_server_func (const gchar * uri, GstUri * turn_server,
503 AddTurnServerData * data)
505 _add_turn_server (data->ice, data->item, turn_server);
509 on_stun_server_resolved (GstWebRTCNice * ice, GAsyncResult * res,
513 GError *error = NULL;
514 guint port = GPOINTER_TO_UINT (user_data);
517 if (!(addresses = resolve_host_finish (ice, res, &error))) {
518 GST_WARNING_OBJECT (ice, "Failed to resolve stun server: %s",
520 g_clear_error (&error);
524 /* XXX: only the first IP is used */
525 ip = g_inet_address_to_string (addresses->data);
527 g_object_set (ice->priv->nice_agent, "stun-server", ip,
528 "stun-server-port", port, NULL);
534 _add_stun_server (GstWebRTCNice * ice, GstUri * stun_server)
536 const gchar *msg = "must be of the form stun://<host>:<port>";
541 s = gst_uri_to_string (stun_server);
542 GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
544 host = gst_uri_get_host (stun_server);
546 GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg);
550 port = gst_uri_get_port (stun_server);
551 if (port == GST_URI_NO_PORT) {
552 GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s);
554 gst_uri_set_port (stun_server, port);
557 resolve_host_async (ice, host, (GAsyncReadyCallback) on_stun_server_resolved,
558 GUINT_TO_POINTER (port), NULL);
564 static GstWebRTCICEStream *
565 gst_webrtc_nice_add_stream (GstWebRTCICE * ice, guint session_id)
567 struct NiceStreamItem m = NICE_MATCH_INIT;
568 struct NiceStreamItem *item;
569 AddTurnServerData add_data;
570 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
572 m.session_id = session_id;
573 item = _nice_stream_item_find (nice, (NiceStreamItemFindFunc) _match, &m);
575 GST_ERROR_OBJECT (nice, "stream already added with session_id=%u",
580 if (nice->priv->stun_server) {
581 _add_stun_server (nice, nice->priv->stun_server);
584 item = _create_nice_stream_item (nice, session_id);
586 if (nice->priv->turn_server) {
587 _add_turn_server (nice, item, nice->priv->turn_server);
591 add_data.item = item;
593 g_hash_table_foreach (nice->priv->turn_servers,
594 (GHFunc) _add_turn_server_func, &add_data);
600 _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
603 struct NiceStreamItem *item;
606 item = _find_item (ice, -1, candidate->stream_id, NULL);
608 GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u",
609 candidate->stream_id);
613 if (!candidate->username || !candidate->password) {
614 gboolean got_credentials;
615 gchar *ufrag, *password;
617 got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent,
618 candidate->stream_id, &ufrag, &password);
619 g_warn_if_fail (got_credentials);
621 if (!candidate->username)
622 candidate->username = ufrag;
626 if (!candidate->password)
627 candidate->password = password;
632 attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
634 if (ice->priv->on_candidate)
635 ice->priv->on_candidate (GST_WEBRTC_ICE (ice), item->session_id, attr,
636 ice->priv->on_candidate_data);
641 static GstWebRTCICETransport *
642 gst_webrtc_nice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
643 GstWebRTCICEComponent component)
645 struct NiceStreamItem *item;
646 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
648 item = _find_item (nice, -1, -1, stream);
649 g_return_val_if_fail (item != NULL, NULL);
651 return gst_webrtc_ice_stream_find_transport (item->stream, component);
655 /* TODO don't rely on libnice to (de)serialize candidates */
656 static NiceCandidateType
657 _candidate_type_from_string (const gchar * s)
659 if (g_strcmp0 (s, "host") == 0) {
660 return NICE_CANDIDATE_TYPE_HOST;
661 } else if (g_strcmp0 (s, "srflx") == 0) {
662 return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
663 } else if (g_strcmp0 (s, "prflx") == 0) { /* FIXME: is the right string? */
664 return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
665 } else if (g_strcmp0 (s, "relay") == 0) {
666 return NICE_CANDIDATE_TYPE_RELAY;
668 g_assert_not_reached ();
674 _candidate_type_to_string (NiceCandidateType type)
677 case NICE_CANDIDATE_TYPE_HOST:
679 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
681 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
683 case NICE_CANDIDATE_TYPE_RELAY:
686 g_assert_not_reached ();
691 static NiceCandidateTransport
692 _candidate_transport_from_string (const gchar * s)
694 if (g_strcmp0 (s, "UDP") == 0) {
695 return NICE_CANDIDATE_TRANSPORT_UDP;
696 } else if (g_strcmp0 (s, "TCP tcptype") == 0) {
697 return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
698 } else if (g_strcmp0 (s, "tcp-passive") == 0) { /* FIXME: is the right string? */
699 return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
700 } else if (g_strcmp0 (s, "tcp-so") == 0) {
701 return NICE_CANDIDATE_TRANSPORT_TCP_SO;
703 g_assert_not_reached ();
709 _candidate_type_to_string (NiceCandidateType type)
712 case NICE_CANDIDATE_TYPE_HOST:
714 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
716 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
718 case NICE_CANDIDATE_TYPE_RELAY:
721 g_assert_not_reached ();
727 /* parse the address for possible resolution */
729 get_candidate_address (const gchar * candidate, gchar ** prefix,
730 gchar ** address, gchar ** postfix)
732 char **tokens = NULL;
734 if (!g_str_has_prefix (candidate, "a=candidate:")) {
735 GST_ERROR ("candidate \"%s\" does not start with \"a=candidate:\"",
740 if (!(tokens = g_strsplit (candidate, " ", 6))) {
741 GST_ERROR ("candidate \"%s\" could not be tokenized", candidate);
745 if (g_strv_length (tokens) < 6) {
746 GST_ERROR ("candidate \"%s\" tokenization resulted in not enough tokens",
752 *address = g_strdup (tokens[4]);
755 *prefix = g_strjoinv (" ", tokens);
757 *postfix = g_strdup (tokens[5]);
768 struct resolve_candidate_data
770 guint nice_stream_id;
776 free_resolve_candidate_data (struct resolve_candidate_data *rc)
779 g_free (rc->postfix);
784 add_ice_candidate_to_libnice (GstWebRTCICE * ice, guint nice_stream_id,
785 NiceCandidate * cand)
787 GSList *candidates = NULL;
788 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
790 if (cand->component_id == 2) {
791 /* we only support rtcp-mux so rtcp candidates are useless for us */
792 GST_INFO_OBJECT (ice, "Dropping RTCP candidate");
796 candidates = g_slist_append (candidates, cand);
798 nice_agent_set_remote_candidates (nice->priv->nice_agent, nice_stream_id,
799 cand->component_id, candidates);
801 g_slist_free (candidates);
805 on_candidate_resolved (GstWebRTCICE * ice, GAsyncResult * res,
808 struct resolve_candidate_data *rc = user_data;
809 GError *error = NULL;
811 char *new_candv[4] = { NULL, };
812 char *new_addr, *new_candidate;
814 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
816 if (!(addresses = resolve_host_finish (nice, res, &error))) {
817 GST_WARNING_OBJECT (ice, "Could not resolve candidate address: %s",
819 g_clear_error (&error);
823 new_addr = g_inet_address_to_string (addresses->data);
825 new_candv[0] = rc->prefix;
826 new_candv[1] = new_addr;
827 new_candv[2] = rc->postfix;
829 new_candidate = g_strjoinv (" ", new_candv);
831 GST_DEBUG_OBJECT (ice, "resolved to candidate %s", new_candidate);
834 nice_agent_parse_remote_candidate_sdp (nice->priv->nice_agent,
835 rc->nice_stream_id, new_candidate);
836 g_free (new_candidate);
838 GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'", new_candidate);
844 add_ice_candidate_to_libnice (ice, rc->nice_stream_id, cand);
845 nice_candidate_free (cand);
848 /* candidate must start with "a=candidate:" or be NULL*/
850 gst_webrtc_nice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
851 const gchar * candidate)
853 struct NiceStreamItem *item;
855 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
857 item = _find_item (nice, -1, -1, stream);
858 g_return_if_fail (item != NULL);
860 if (candidate == NULL) {
861 nice_agent_peer_candidate_gathering_done (nice->priv->nice_agent,
862 item->nice_stream_id);
867 nice_agent_parse_remote_candidate_sdp (nice->priv->nice_agent,
868 item->nice_stream_id, candidate);
870 /* might be a .local candidate */
871 char *prefix = NULL, *address = NULL, *postfix = NULL;
872 struct resolve_candidate_data *rc;
874 if (!get_candidate_address (candidate, &prefix, &address, &postfix)) {
875 GST_WARNING_OBJECT (nice, "Failed to retrieve address from candidate %s",
880 if (!g_str_has_suffix (address, ".local")) {
881 GST_WARNING_OBJECT (nice, "candidate address \'%s\' does not end "
882 "with \'.local\'", address);
886 rc = g_new0 (struct resolve_candidate_data, 1);
887 rc->nice_stream_id = item->nice_stream_id;
889 rc->postfix = postfix;
890 resolve_host_async (nice, address,
891 (GAsyncReadyCallback) on_candidate_resolved, rc,
892 (GDestroyNotify) free_resolve_candidate_data);
898 g_clear_pointer (&address, g_free);
899 g_clear_pointer (&prefix, g_free);
900 g_clear_pointer (&postfix, g_free);
905 add_ice_candidate_to_libnice (ice, item->nice_stream_id, cand);
906 nice_candidate_free (cand);
910 gst_webrtc_nice_set_remote_credentials (GstWebRTCICE * ice,
911 GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
913 struct NiceStreamItem *item;
914 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
916 g_return_val_if_fail (ufrag != NULL, FALSE);
917 g_return_val_if_fail (pwd != NULL, FALSE);
918 item = _find_item (nice, -1, -1, stream);
919 g_return_val_if_fail (item != NULL, FALSE);
921 GST_DEBUG_OBJECT (nice, "Setting remote ICE credentials on "
922 "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
924 nice_agent_set_remote_credentials (nice->priv->nice_agent,
925 item->nice_stream_id, ufrag, pwd);
931 gst_webrtc_nice_add_turn_server (GstWebRTCICE * ice, const gchar * uri)
933 gboolean ret = FALSE;
935 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
937 if (!(valid_uri = _validate_turn_server (nice, uri)))
940 g_hash_table_insert (nice->priv->turn_servers, g_strdup (uri), valid_uri);
949 gst_webrtc_nice_add_local_ip_address (GstWebRTCNice * ice,
950 const gchar * address)
952 gboolean ret = FALSE;
953 NiceAddress nice_addr;
955 nice_address_init (&nice_addr);
957 ret = nice_address_set_from_string (&nice_addr, address);
960 ret = nice_agent_add_local_address (ice->priv->nice_agent, &nice_addr);
962 GST_ERROR_OBJECT (ice, "Failed to add local address to NiceAgent");
965 GST_ERROR_OBJECT (ice, "Failed to initialize NiceAddress [%s]", address);
972 gst_webrtc_nice_set_local_credentials (GstWebRTCICE * ice,
973 GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
975 struct NiceStreamItem *item;
976 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
978 g_return_val_if_fail (ufrag != NULL, FALSE);
979 g_return_val_if_fail (pwd != NULL, FALSE);
980 item = _find_item (nice, -1, -1, stream);
981 g_return_val_if_fail (item != NULL, FALSE);
983 GST_DEBUG_OBJECT (nice, "Setting local ICE credentials on "
984 "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
986 nice_agent_set_local_credentials (nice->priv->nice_agent,
987 item->nice_stream_id, ufrag, pwd);
993 gst_webrtc_nice_gather_candidates (GstWebRTCICE * ice,
994 GstWebRTCICEStream * stream)
996 struct NiceStreamItem *item;
997 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
999 item = _find_item (nice, -1, -1, stream);
1000 g_return_val_if_fail (item != NULL, FALSE);
1002 GST_DEBUG_OBJECT (nice, "gather candidates for stream %u",
1003 item->nice_stream_id);
1005 return gst_webrtc_ice_stream_gather_candidates (stream);
1009 gst_webrtc_nice_set_is_controller (GstWebRTCICE * ice, gboolean controller)
1011 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1012 g_object_set (G_OBJECT (nice->priv->nice_agent), "controlling-mode",
1017 gst_webrtc_nice_get_is_controller (GstWebRTCICE * ice)
1020 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1021 g_object_get (G_OBJECT (nice->priv->nice_agent), "controlling-mode",
1027 gst_webrtc_nice_set_force_relay (GstWebRTCICE * ice, gboolean force_relay)
1029 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1030 g_object_set (G_OBJECT (nice->priv->nice_agent), "force-relay", force_relay,
1035 gst_webrtc_nice_set_on_ice_candidate (GstWebRTCICE * ice,
1036 GstWebRTCICEOnCandidateFunc func, gpointer user_data, GDestroyNotify notify)
1038 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1039 if (nice->priv->on_candidate_notify)
1040 nice->priv->on_candidate_notify (nice->priv->on_candidate_data);
1041 nice->priv->on_candidate = NULL;
1043 nice->priv->on_candidate = func;
1044 nice->priv->on_candidate_data = user_data;
1045 nice->priv->on_candidate_notify = notify;
1049 gst_webrtc_nice_set_tos (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
1052 struct NiceStreamItem *item;
1053 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1055 item = _find_item (nice, -1, -1, stream);
1056 g_return_if_fail (item != NULL);
1058 nice_agent_set_stream_tos (nice->priv->nice_agent, item->nice_stream_id, tos);
1061 static const gchar *
1062 _relay_type_to_string (GstUri * turn_server)
1064 const gchar *scheme;
1065 const gchar *transport;
1070 scheme = gst_uri_get_scheme (turn_server);
1071 transport = gst_uri_get_query_value (turn_server, "transport");
1073 if (g_strcmp0 (scheme, "turns") == 0) {
1075 } else if (g_strcmp0 (scheme, "turn") == 0) {
1076 if (!transport || g_strcmp0 (transport, "udp") == 0)
1078 if (!transport || g_strcmp0 (transport, "tcp") == 0)
1086 _get_server_url (GstWebRTCNice * ice, NiceCandidate * cand)
1088 switch (cand->type) {
1089 case NICE_CANDIDATE_TYPE_RELAYED:{
1090 #if NICE_CHECK_VERSION(0, 1, 19)
1092 gchar ipaddr[NICE_ADDRESS_STRING_LEN];
1093 nice_candidate_relay_address (cand, &addr);
1094 nice_address_to_string (&addr, ipaddr);
1095 return g_strdup (ipaddr);
1097 static gboolean warned = FALSE;
1100 ("libnice version < 0.1.19 detected, relayed candidate server address might be wrong.");
1103 return g_strdup (gst_uri_get_host (ice->priv->turn_server));
1106 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:{
1107 #if NICE_CHECK_VERSION(0, 1, 20)
1109 gchar ipaddr[NICE_ADDRESS_STRING_LEN];
1110 if (nice_candidate_stun_server_address (cand, &addr)) {
1111 nice_address_to_string (&addr, ipaddr);
1112 return g_strdup (ipaddr);
1114 return g_strdup (gst_uri_get_host (ice->priv->stun_server));
1117 static gboolean warned = FALSE;
1120 ("libnice version < 0.1.20 detected, server-reflexive candidate server "
1121 "address might be wrong.");
1125 return g_strdup (gst_uri_get_host (ice->priv->stun_server));
1128 return g_strdup ("");
1132 /* TODO: replace it with nice_candidate_type_to_string()
1133 * when it's ready for use
1134 * https://libnice.freedesktop.org/libnice/NiceCandidate.html#nice-candidate-type-to-string
1136 static const gchar *
1137 _candidate_type_to_string (NiceCandidateType type)
1140 case NICE_CANDIDATE_TYPE_HOST:
1142 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
1144 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
1146 case NICE_CANDIDATE_TYPE_RELAYED:
1149 g_assert_not_reached ();
1155 _populate_candidate_stats (GstWebRTCNice * ice, NiceCandidate * cand,
1156 GstWebRTCICEStream * stream, GstWebRTCICECandidateStats * stats,
1159 gchar ipaddr[INET6_ADDRSTRLEN];
1161 g_assert (cand != NULL);
1163 nice_address_to_string (&cand->addr, ipaddr);
1164 stats->port = nice_address_get_port (&cand->addr);
1165 stats->ipaddr = g_strdup (ipaddr);
1166 stats->stream_id = stream->stream_id;
1167 stats->type = _candidate_type_to_string (cand->type);
1168 stats->prio = cand->priority;
1170 cand->transport == NICE_CANDIDATE_TRANSPORT_UDP ? "udp" : "tcp";
1172 if (cand->type == NICE_CANDIDATE_TYPE_RELAYED)
1173 stats->relay_proto = _relay_type_to_string (ice->priv->turn_server);
1174 stats->url = _get_server_url (ice, cand);
1179 _populate_candidate_list_stats (GstWebRTCNice * ice, GSList * cands,
1180 GstWebRTCICEStream * stream, GArray * result, gboolean is_local)
1184 for (item = cands; item != NULL; item = item->next) {
1185 GstWebRTCICECandidateStats stats;
1186 NiceCandidate *c = item->data;
1187 _populate_candidate_stats (ice, c, stream, &stats, is_local);
1188 g_array_append_val (result, stats);
1193 gst_webrtc_nice_get_local_candidates (GstWebRTCICE * ice,
1194 GstWebRTCICEStream * stream)
1196 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1197 GSList *cands = NULL;
1200 g_array_new (FALSE, TRUE, sizeof (GstWebRTCICECandidateStats));
1202 cands = nice_agent_get_local_candidates (nice->priv->nice_agent,
1203 stream->stream_id, NICE_COMPONENT_TYPE_RTP);
1205 _populate_candidate_list_stats (nice, cands, stream, result, TRUE);
1206 g_slist_free_full (cands, (GDestroyNotify) nice_candidate_free);
1212 gst_webrtc_nice_get_remote_candidates (GstWebRTCICE * ice,
1213 GstWebRTCICEStream * stream)
1215 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1216 GSList *cands = NULL;
1219 g_array_new (FALSE, TRUE, sizeof (GstWebRTCICECandidateStats));
1221 cands = nice_agent_get_remote_candidates (nice->priv->nice_agent,
1222 stream->stream_id, NICE_COMPONENT_TYPE_RTP);
1224 _populate_candidate_list_stats (nice, cands, stream, result, FALSE);
1225 g_slist_free_full (cands, (GDestroyNotify) nice_candidate_free);
1231 gst_webrtc_nice_get_selected_pair (GstWebRTCICE * ice,
1232 GstWebRTCICEStream * stream, GstWebRTCICECandidateStats ** local_stats,
1233 GstWebRTCICECandidateStats ** remote_stats)
1235 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1236 NiceCandidate *local_cand = NULL;
1237 NiceCandidate *remote_cand = NULL;
1241 if (nice_agent_get_selected_pair (nice->priv->nice_agent, stream->stream_id,
1242 NICE_COMPONENT_TYPE_RTP, &local_cand, &remote_cand)) {
1243 *local_stats = g_new0 (GstWebRTCICECandidateStats, 1);
1244 _populate_candidate_stats (nice, local_cand, stream, *local_stats, TRUE);
1246 *remote_stats = g_new0 (GstWebRTCICECandidateStats, 1);
1247 _populate_candidate_stats (nice, remote_cand, stream, *remote_stats,
1258 _clear_ice_stream (struct NiceStreamItem *item)
1266 g_object_get (GST_WEBRTC_NICE_STREAM (item->stream), "ice", &ice_weak,
1268 ice = g_weak_ref_get (&ice_weak);
1271 g_signal_handlers_disconnect_by_data (ice->priv->nice_agent,
1273 gst_object_unref (ice);
1275 gst_object_unref (item->stream);
1280 _validate_turn_server (GstWebRTCNice * ice, const gchar * s)
1282 GstUri *uri = gst_uri_from_string_escaped (s);
1283 const gchar *userinfo, *scheme;
1284 GList *keys = NULL, *l;
1285 gchar *user = NULL, *pass = NULL;
1286 gboolean turn_tls = FALSE;
1289 GST_DEBUG_OBJECT (ice, "validating turn server, %s", s);
1292 GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s);
1296 scheme = gst_uri_get_scheme (uri);
1297 if (g_strcmp0 (scheme, "turn") == 0) {
1298 } else if (g_strcmp0 (scheme, "turns") == 0) {
1301 GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme);
1305 keys = gst_uri_get_query_keys (uri);
1306 for (l = keys; l; l = l->next) {
1307 gchar *key = l->data;
1309 if (g_strcmp0 (key, "transport") == 0) {
1310 const gchar *transport = gst_uri_get_query_value (uri, "transport");
1312 } else if (g_strcmp0 (transport, "udp") == 0) {
1313 } else if (g_strcmp0 (transport, "tcp") == 0) {
1315 GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport);
1319 GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key);
1324 /* TODO: Implement error checking similar to the stun server below */
1325 userinfo = gst_uri_get_userinfo (uri);
1326 _parse_userinfo (userinfo, &user, &pass);
1328 GST_ERROR_OBJECT (ice, "No username specified in '%s'", s);
1332 GST_ERROR_OBJECT (ice, "No password specified in '%s'", s);
1336 port = gst_uri_get_port (uri);
1338 if (port == GST_URI_NO_PORT) {
1340 gst_uri_set_port (uri, 5349);
1342 gst_uri_set_port (uri, 3478);
1355 gst_webrtc_nice_set_stun_server (GstWebRTCICE * ice, const gchar * uri_s)
1357 GstUri *uri = gst_uri_from_string_escaped (uri_s);
1358 const gchar *msg = "must be of the form stun://<host>:<port>";
1359 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1361 GST_DEBUG_OBJECT (nice, "setting stun server, %s", uri_s);
1364 GST_ERROR_OBJECT (nice, "Couldn't parse stun server '%s', %s", uri_s, msg);
1368 if (nice->priv->stun_server)
1369 gst_uri_unref (nice->priv->stun_server);
1370 nice->priv->stun_server = uri;
1374 gst_webrtc_nice_get_stun_server (GstWebRTCICE * ice)
1376 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1377 if (nice->priv->stun_server)
1378 return gst_uri_to_string (nice->priv->stun_server);
1384 gst_webrtc_nice_set_turn_server (GstWebRTCICE * ice, const gchar * uri_s)
1387 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1388 uri = _validate_turn_server (nice, uri_s);
1391 if (nice->priv->turn_server)
1392 gst_uri_unref (nice->priv->turn_server);
1393 nice->priv->turn_server = uri;
1398 gst_webrtc_nice_get_turn_server (GstWebRTCICE * ice)
1400 GstWebRTCNice *nice = GST_WEBRTC_NICE (ice);
1401 if (nice->priv->turn_server)
1402 return gst_uri_to_string (nice->priv->turn_server);
1408 gst_webrtc_nice_set_property (GObject * object, guint prop_id,
1409 const GValue * value, GParamSpec * pspec)
1411 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1412 GstWebRTCNice *nice = GST_WEBRTC_NICE (object);
1416 g_object_set_property (G_OBJECT (nice->priv->nice_agent),
1420 g_object_set_property (G_OBJECT (nice->priv->nice_agent),
1423 case PROP_MIN_RTP_PORT:
1424 ice->min_rtp_port = g_value_get_uint (value);
1425 if (ice->min_rtp_port > ice->max_rtp_port)
1426 g_warning ("Set min-rtp-port to %u which is larger than"
1427 " max-rtp-port %u", ice->min_rtp_port, ice->max_rtp_port);
1429 case PROP_MAX_RTP_PORT:
1430 ice->max_rtp_port = g_value_get_uint (value);
1431 if (ice->min_rtp_port > ice->max_rtp_port)
1432 g_warning ("Set max-rtp-port to %u which is smaller than"
1433 " min-rtp-port %u", ice->max_rtp_port, ice->min_rtp_port);
1436 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1442 gst_webrtc_nice_get_property (GObject * object, guint prop_id,
1443 GValue * value, GParamSpec * pspec)
1445 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1446 GstWebRTCNice *nice = GST_WEBRTC_NICE (object);
1450 g_value_set_object (value, nice->priv->nice_agent);
1453 g_object_get_property (G_OBJECT (nice->priv->nice_agent),
1457 g_object_get_property (G_OBJECT (nice->priv->nice_agent),
1460 case PROP_MIN_RTP_PORT:
1461 g_value_set_uint (value, ice->min_rtp_port);
1463 case PROP_MAX_RTP_PORT:
1464 g_value_set_uint (value, ice->max_rtp_port);
1467 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1473 gst_webrtc_nice_finalize (GObject * object)
1475 GstWebRTCNice *ice = GST_WEBRTC_NICE (object);
1477 g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice);
1481 if (ice->priv->on_candidate_notify)
1482 ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
1483 ice->priv->on_candidate = NULL;
1484 ice->priv->on_candidate_notify = NULL;
1486 if (ice->priv->turn_server)
1487 gst_uri_unref (ice->priv->turn_server);
1488 if (ice->priv->stun_server)
1489 gst_uri_unref (ice->priv->stun_server);
1491 g_mutex_clear (&ice->priv->lock);
1492 g_cond_clear (&ice->priv->cond);
1494 g_array_free (ice->priv->nice_stream_map, TRUE);
1496 g_object_unref (ice->priv->nice_agent);
1498 g_hash_table_unref (ice->priv->turn_servers);
1500 G_OBJECT_CLASS (parent_class)->finalize (object);
1504 gst_webrtc_nice_constructed (GObject * object)
1506 GstWebRTCNice *ice = GST_WEBRTC_NICE (object);
1507 NiceAgentOption options = 0;
1509 _start_thread (ice);
1511 options |= NICE_AGENT_OPTION_ICE_TRICKLE;
1512 options |= NICE_AGENT_OPTION_REGULAR_NOMINATION;
1514 ice->priv->nice_agent = nice_agent_new_full (ice->priv->main_context,
1515 NICE_COMPATIBILITY_RFC5245, options);
1516 g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
1517 G_CALLBACK (_on_new_candidate), ice);
1519 G_OBJECT_CLASS (parent_class)->constructed (object);
1523 gst_webrtc_nice_class_init (GstWebRTCNiceClass * klass)
1525 GstWebRTCICEClass *gst_webrtc_ice_class = GST_WEBRTC_ICE_CLASS (klass);
1526 GObjectClass *gobject_class = (GObjectClass *) klass;
1528 // override virtual functions
1529 gst_webrtc_ice_class->add_candidate = gst_webrtc_nice_add_candidate;
1530 gst_webrtc_ice_class->add_stream = gst_webrtc_nice_add_stream;
1531 gst_webrtc_ice_class->add_turn_server = gst_webrtc_nice_add_turn_server;
1532 gst_webrtc_ice_class->find_transport = gst_webrtc_nice_find_transport;
1533 gst_webrtc_ice_class->gather_candidates = gst_webrtc_nice_gather_candidates;
1534 gst_webrtc_ice_class->get_is_controller = gst_webrtc_nice_get_is_controller;
1535 gst_webrtc_ice_class->get_stun_server = gst_webrtc_nice_get_stun_server;
1536 gst_webrtc_ice_class->get_turn_server = gst_webrtc_nice_get_turn_server;
1537 gst_webrtc_ice_class->set_force_relay = gst_webrtc_nice_set_force_relay;
1538 gst_webrtc_ice_class->set_is_controller = gst_webrtc_nice_set_is_controller;
1539 gst_webrtc_ice_class->set_local_credentials =
1540 gst_webrtc_nice_set_local_credentials;
1541 gst_webrtc_ice_class->set_remote_credentials =
1542 gst_webrtc_nice_set_remote_credentials;
1543 gst_webrtc_ice_class->set_stun_server = gst_webrtc_nice_set_stun_server;
1544 gst_webrtc_ice_class->set_tos = gst_webrtc_nice_set_tos;
1545 gst_webrtc_ice_class->set_turn_server = gst_webrtc_nice_set_turn_server;
1546 gst_webrtc_ice_class->set_on_ice_candidate =
1547 gst_webrtc_nice_set_on_ice_candidate;
1548 gst_webrtc_ice_class->get_local_candidates =
1549 gst_webrtc_nice_get_local_candidates;
1550 gst_webrtc_ice_class->get_remote_candidates =
1551 gst_webrtc_nice_get_remote_candidates;
1552 gst_webrtc_ice_class->get_selected_pair = gst_webrtc_nice_get_selected_pair;
1554 gobject_class->constructed = gst_webrtc_nice_constructed;
1555 gobject_class->get_property = gst_webrtc_nice_get_property;
1556 gobject_class->set_property = gst_webrtc_nice_set_property;
1557 gobject_class->finalize = gst_webrtc_nice_finalize;
1559 g_object_class_install_property (gobject_class,
1561 g_param_spec_object ("agent", "ICE agent",
1562 "ICE agent in use by this object. WARNING! Accessing this property "
1563 "may have disastrous consequences for the operation of webrtcbin. "
1564 "Other ICE implementations may not have the same interface.",
1565 NICE_TYPE_AGENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1567 g_object_class_install_property (gobject_class,
1569 g_param_spec_boolean ("ice-tcp", "ICE TCP",
1570 "Whether the agent should use ICE-TCP when gathering candidates",
1571 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1573 g_object_class_install_property (gobject_class,
1575 g_param_spec_boolean ("ice-udp", "ICE UDP",
1576 "Whether the agent should use ICE-UDP when gathering candidates",
1577 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1579 g_signal_override_class_handler ("add-local-ip-address",
1580 G_TYPE_FROM_CLASS (klass),
1581 G_CALLBACK (gst_webrtc_nice_add_local_ip_address));
1585 gst_webrtc_nice_init (GstWebRTCNice * ice)
1587 ice->priv = gst_webrtc_nice_get_instance_private (ice);
1589 g_mutex_init (&ice->priv->lock);
1590 g_cond_init (&ice->priv->cond);
1592 ice->priv->turn_servers =
1593 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1594 (GDestroyNotify) gst_uri_unref);
1596 ice->priv->nice_stream_map =
1597 g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
1598 g_array_set_clear_func (ice->priv->nice_stream_map,
1599 (GDestroyNotify) _clear_ice_stream);
1603 gst_webrtc_nice_new (const gchar * name)
1605 return g_object_new (GST_TYPE_WEBRTC_NICE, "name", name, NULL);