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.
24 #include "gstwebrtcice.h"
27 #include "icestream.h"
28 #include "nicetransport.h"
30 #ifndef NICE_CHECK_VERSION
31 #define NICE_CHECK_VERSION(major,minor,micro) \
32 (NICE_VERSION_MAJOR > (major) || \
33 (NICE_VERSION_MAJOR == (major) && NICE_VERSION_MINOR > (minor)) || \
34 (NICE_VERSION_MAJOR == (major) && NICE_VERSION_MINOR == (minor) && \
35 NICE_VERSION_MICRO >= (micro)))
40 * - are locally generated remote candidates meant to be readded to libnice?
43 static GstUri *_validate_turn_server (GstWebRTCICE * ice, const gchar * s);
45 #define GST_CAT_DEFAULT gst_webrtc_ice_debug
46 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
49 gst_webrtc_ice_error_quark (void)
51 return g_quark_from_static_string ("gst-webrtc-ice-error-quark");
57 ADD_LOCAL_IP_ADDRESS_SIGNAL,
71 static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 };
73 struct _GstWebRTCICEPrivate
75 NiceAgent *nice_agent;
77 GArray *nice_stream_map;
80 GMainContext *main_context;
85 GstWebRTCIceOnCandidateFunc on_candidate;
86 gpointer on_candidate_data;
87 GDestroyNotify on_candidate_notify;
90 #define gst_webrtc_ice_parent_class parent_class
91 G_DEFINE_TYPE_WITH_CODE (GstWebRTCICE, gst_webrtc_ice,
92 GST_TYPE_OBJECT, G_ADD_PRIVATE (GstWebRTCICE)
93 GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_debug, "webrtcice", 0,
97 _unlock_pc_thread (GMutex * lock)
99 g_mutex_unlock (lock);
100 return G_SOURCE_REMOVE;
104 _gst_nice_thread (GstWebRTCICE * ice)
106 g_mutex_lock (&ice->priv->lock);
107 ice->priv->main_context = g_main_context_new ();
108 ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE);
110 g_cond_broadcast (&ice->priv->cond);
111 g_main_context_invoke (ice->priv->main_context,
112 (GSourceFunc) _unlock_pc_thread, &ice->priv->lock);
114 g_main_loop_run (ice->priv->loop);
116 g_mutex_lock (&ice->priv->lock);
117 g_main_context_unref (ice->priv->main_context);
118 ice->priv->main_context = NULL;
119 g_main_loop_unref (ice->priv->loop);
120 ice->priv->loop = NULL;
121 g_cond_broadcast (&ice->priv->cond);
122 g_mutex_unlock (&ice->priv->lock);
128 _start_thread (GstWebRTCICE * ice)
130 g_mutex_lock (&ice->priv->lock);
131 ice->priv->thread = g_thread_new (GST_OBJECT_NAME (ice),
132 (GThreadFunc) _gst_nice_thread, ice);
134 while (!ice->priv->loop)
135 g_cond_wait (&ice->priv->cond, &ice->priv->lock);
136 g_mutex_unlock (&ice->priv->lock);
140 _stop_thread (GstWebRTCICE * ice)
142 g_mutex_lock (&ice->priv->lock);
143 g_main_loop_quit (ice->priv->loop);
144 while (ice->priv->loop)
145 g_cond_wait (&ice->priv->cond, &ice->priv->lock);
146 g_mutex_unlock (&ice->priv->lock);
148 g_thread_unref (ice->priv->thread);
151 struct NiceStreamItem
154 guint nice_stream_id;
155 GstWebRTCICEStream *stream;
158 /* TRUE to continue, FALSE to stop */
159 typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item,
163 _nice_stream_item_foreach (GstWebRTCICE * ice, NiceStreamItemForeachFunc func,
168 len = ice->priv->nice_stream_map->len;
169 for (i = 0; i < len; i++) {
170 struct NiceStreamItem *item =
171 &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem,
174 if (!func (item, data))
179 /* TRUE for match, FALSE otherwise */
180 typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item,
185 NiceStreamItemFindFunc func;
187 struct NiceStreamItem *ret;
191 _find_nice_item (struct NiceStreamItem *item, gpointer user_data)
193 struct nice_find *f = user_data;
194 if (f->func (item, f->data)) {
201 static struct NiceStreamItem *
202 _nice_stream_item_find (GstWebRTCICE * ice, NiceStreamItemFindFunc func,
211 _nice_stream_item_foreach (ice, _find_nice_item, &f);
216 #define NICE_MATCH_INIT { -1, -1, NULL }
219 _match (struct NiceStreamItem *item, struct NiceStreamItem *m)
221 if (m->session_id != -1 && m->session_id != item->session_id)
223 if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id)
225 if (m->stream != NULL && m->stream != item->stream)
231 static struct NiceStreamItem *
232 _find_item (GstWebRTCICE * ice, guint session_id, guint nice_stream_id,
233 GstWebRTCICEStream * stream)
235 struct NiceStreamItem m = NICE_MATCH_INIT;
237 m.session_id = session_id;
238 m.nice_stream_id = nice_stream_id;
241 return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
244 static struct NiceStreamItem *
245 _create_nice_stream_item (GstWebRTCICE * ice, guint session_id)
247 struct NiceStreamItem item;
249 item.session_id = session_id;
250 item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 1);
251 item.stream = gst_webrtc_ice_stream_new (ice, item.nice_stream_id);
252 g_array_append_val (ice->priv->nice_stream_map, item);
254 return _find_item (ice, item.session_id, item.nice_stream_id, item.stream);
258 _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
268 colon = g_strstr_len (userinfo, -1, ":");
270 *user = g_uri_unescape_string (userinfo, NULL);
275 /* Check that the first occurence is also the last occurence */
276 if (colon != g_strrstr (userinfo, ":"))
277 GST_WARNING ("userinfo %s contains more than one ':', will assume that the "
278 "first ':' delineates user:pass. You should escape the user and pass "
279 "before adding to the URI.", userinfo);
281 *user = g_uri_unescape_segment (userinfo, colon, NULL);
282 *pass = g_uri_unescape_string (&colon[1], NULL);
286 _resolve_host (GstWebRTCICE * ice, const gchar * host)
288 GResolver *resolver = g_resolver_get_default ();
289 GError *error = NULL;
294 GST_DEBUG_OBJECT (ice, "Resolving host %s", host);
296 if (!(addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error))) {
297 GST_ERROR ("%s", error->message);
298 g_clear_error (&error);
302 GST_DEBUG_OBJECT (ice, "Resolved %d addresses for host %s",
303 g_list_length (addresses), host);
305 /* XXX: only the first address is used */
306 addr = addresses->data;
307 address = g_inet_address_to_string (addr);
308 g_resolver_free_addresses (addresses);
314 _add_turn_server (GstWebRTCICE * ice, struct NiceStreamItem *item,
315 GstUri * turn_server)
319 const gchar *host, *userinfo, *transport, *scheme;
320 NiceRelayType relays[4] = { 0, };
324 host = gst_uri_get_host (turn_server);
326 GST_ERROR_OBJECT (ice, "Turn server has no host");
329 ip = _resolve_host (ice, host);
331 GST_ERROR_OBJECT (ice, "Failed to resolve turn server '%s'", host);
335 /* Set the resolved IP as the host since that's what libnice wants */
336 gst_uri_set_host (turn_server, ip);
338 scheme = gst_uri_get_scheme (turn_server);
339 transport = gst_uri_get_query_value (turn_server, "transport");
340 userinfo = gst_uri_get_userinfo (turn_server);
341 _parse_userinfo (userinfo, &user, &pass);
343 if (g_strcmp0 (scheme, "turns") == 0) {
344 relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS;
345 } else if (g_strcmp0 (scheme, "turn") == 0) {
346 if (!transport || g_strcmp0 (transport, "udp") == 0)
347 relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP;
348 if (!transport || g_strcmp0 (transport, "tcp") == 0)
349 relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP;
351 g_assert (relay_n < G_N_ELEMENTS (relays));
353 for (i = 0; i < relay_n; i++) {
354 ret = nice_agent_set_relay_info (ice->priv->nice_agent,
355 item->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
356 gst_uri_get_host (turn_server), gst_uri_get_port (turn_server),
357 user, pass, relays[i]);
359 gchar *uri = gst_uri_to_string (turn_server);
360 GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
375 struct NiceStreamItem *item;
379 _add_turn_server_func (const gchar * uri, GstUri * turn_server,
380 AddTurnServerData * data)
382 _add_turn_server (data->ice, data->item, turn_server);
386 _add_stun_server (GstWebRTCICE * ice, GstUri * stun_server)
388 const gchar *msg = "must be of the form stun://<host>:<port>";
394 s = gst_uri_to_string (stun_server);
395 GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
397 host = gst_uri_get_host (stun_server);
399 GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg);
403 port = gst_uri_get_port (stun_server);
404 if (port == GST_URI_NO_PORT) {
405 GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s);
407 gst_uri_set_port (stun_server, port);
410 ip = _resolve_host (ice, host);
412 GST_ERROR_OBJECT (ice, "Failed to resolve stun server '%s'", host);
416 g_object_set (ice->priv->nice_agent, "stun-server", ip,
417 "stun-server-port", port, NULL);
425 gst_webrtc_ice_add_stream (GstWebRTCICE * ice, guint session_id)
427 struct NiceStreamItem m = NICE_MATCH_INIT;
428 struct NiceStreamItem *item;
429 AddTurnServerData add_data;
431 m.session_id = session_id;
432 item = _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
434 GST_ERROR_OBJECT (ice, "stream already added with session_id=%u",
439 if (ice->stun_server) {
440 _add_stun_server (ice, ice->stun_server);
443 item = _create_nice_stream_item (ice, session_id);
445 if (ice->turn_server) {
446 _add_turn_server (ice, item, ice->turn_server);
450 add_data.item = item;
452 g_hash_table_foreach (ice->turn_servers, (GHFunc) _add_turn_server_func,
459 _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
462 struct NiceStreamItem *item;
465 item = _find_item (ice, -1, candidate->stream_id, NULL);
467 GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u",
468 candidate->stream_id);
472 if (!candidate->username || !candidate->password) {
473 gboolean got_credentials;
474 gchar *ufrag, *password;
476 got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent,
477 candidate->stream_id, &ufrag, &password);
478 g_warn_if_fail (got_credentials);
480 if (!candidate->username)
481 candidate->username = ufrag;
485 if (!candidate->password)
486 candidate->password = password;
491 attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
493 if (ice->priv->on_candidate)
494 ice->priv->on_candidate (ice, item->session_id, attr,
495 ice->priv->on_candidate_data);
500 GstWebRTCICETransport *
501 gst_webrtc_ice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
502 GstWebRTCICEComponent component)
504 struct NiceStreamItem *item;
506 item = _find_item (ice, -1, -1, stream);
507 g_return_val_if_fail (item != NULL, NULL);
509 return gst_webrtc_ice_stream_find_transport (item->stream, component);
513 /* TODO don't rely on libnice to (de)serialize candidates */
514 static NiceCandidateType
515 _candidate_type_from_string (const gchar * s)
517 if (g_strcmp0 (s, "host") == 0) {
518 return NICE_CANDIDATE_TYPE_HOST;
519 } else if (g_strcmp0 (s, "srflx") == 0) {
520 return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
521 } else if (g_strcmp0 (s, "prflx") == 0) { /* FIXME: is the right string? */
522 return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
523 } else if (g_strcmp0 (s, "relay") == 0) {
524 return NICE_CANDIDATE_TYPE_RELAY;
526 g_assert_not_reached ();
532 _candidate_type_to_string (NiceCandidateType type)
535 case NICE_CANDIDATE_TYPE_HOST:
537 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
539 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
541 case NICE_CANDIDATE_TYPE_RELAY:
544 g_assert_not_reached ();
549 static NiceCandidateTransport
550 _candidate_transport_from_string (const gchar * s)
552 if (g_strcmp0 (s, "UDP") == 0) {
553 return NICE_CANDIDATE_TRANSPORT_UDP;
554 } else if (g_strcmp0 (s, "TCP tcptype") == 0) {
555 return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
556 } else if (g_strcmp0 (s, "tcp-passive") == 0) { /* FIXME: is the right string? */
557 return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
558 } else if (g_strcmp0 (s, "tcp-so") == 0) {
559 return NICE_CANDIDATE_TRANSPORT_TCP_SO;
561 g_assert_not_reached ();
567 _candidate_type_to_string (NiceCandidateType type)
570 case NICE_CANDIDATE_TYPE_HOST:
572 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
574 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
576 case NICE_CANDIDATE_TYPE_RELAY:
579 g_assert_not_reached ();
585 /* parse the address for possible resolution */
587 get_candidate_address (const gchar * candidate, gchar ** prefix,
588 gchar ** address, gchar ** postfix)
590 char **tokens = NULL;
592 if (!g_str_has_prefix (candidate, "a=candidate:")) {
593 GST_ERROR ("candidate \"%s\" does not start with \"a=candidate:\"",
598 if (!(tokens = g_strsplit (candidate, " ", 6))) {
599 GST_ERROR ("candidate \"%s\" could not be tokenized", candidate);
603 if (g_strv_length (tokens) < 6) {
604 GST_ERROR ("candidate \"%s\" tokenization resulted in not enough tokens",
610 *address = g_strdup (tokens[4]);
613 *prefix = g_strjoinv (" ", tokens);
615 *postfix = g_strdup (tokens[5]);
626 /* candidate must start with "a=candidate:" or be NULL*/
628 gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
629 const gchar * candidate)
631 struct NiceStreamItem *item;
633 GSList *candidates = NULL;
635 item = _find_item (ice, -1, -1, stream);
636 g_return_if_fail (item != NULL);
638 if (candidate == NULL) {
639 nice_agent_peer_candidate_gathering_done (ice->priv->nice_agent,
640 item->nice_stream_id);
645 nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
646 item->nice_stream_id, candidate);
648 /* might be a .local candidate */
649 char *prefix = NULL, *address = NULL, *postfix = NULL;
650 char *new_addr = NULL, *new_candidate = NULL;
651 char *new_candv[4] = { NULL, };
652 gboolean failure = TRUE;
654 if (!get_candidate_address (candidate, &prefix, &address, &postfix)) {
655 GST_WARNING_OBJECT (ice, "Failed to retrieve address from candidate %s",
660 if (!g_str_has_suffix (address, ".local")) {
661 GST_WARNING_OBJECT (ice, "candidate address \'%s\' does not end "
662 "with \'.local\'", address);
667 if (!(new_addr = _resolve_host (ice, address))) {
668 GST_WARNING_OBJECT (ice, "Failed to resolve %s", address);
672 new_candv[0] = prefix;
673 new_candv[1] = new_addr;
674 new_candv[2] = postfix;
676 new_candidate = g_strjoinv (" ", new_candv);
678 GST_DEBUG_OBJECT (ice, "resolved to candidate %s", new_candidate);
681 nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
682 item->nice_stream_id, new_candidate);
684 GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'",
696 g_free (new_candidate);
701 if (cand->component_id == 2) {
702 /* we only support rtcp-mux so rtcp candidates are useless for us */
703 GST_INFO_OBJECT (ice, "Dropping RTCP candidate %s", candidate);
704 nice_candidate_free (cand);
708 candidates = g_slist_append (candidates, cand);
710 nice_agent_set_remote_candidates (ice->priv->nice_agent, item->nice_stream_id,
711 cand->component_id, candidates);
713 g_slist_free (candidates);
714 nice_candidate_free (cand);
718 gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice,
719 GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
721 struct NiceStreamItem *item;
723 g_return_val_if_fail (ufrag != NULL, FALSE);
724 g_return_val_if_fail (pwd != NULL, FALSE);
725 item = _find_item (ice, -1, -1, stream);
726 g_return_val_if_fail (item != NULL, FALSE);
728 GST_DEBUG_OBJECT (ice, "Setting remote ICE credentials on "
729 "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
731 nice_agent_set_remote_credentials (ice->priv->nice_agent,
732 item->nice_stream_id, ufrag, pwd);
738 gst_webrtc_ice_add_turn_server (GstWebRTCICE * ice, const gchar * uri)
740 gboolean ret = FALSE;
743 if (!(valid_uri = _validate_turn_server (ice, uri)))
746 g_hash_table_insert (ice->turn_servers, g_strdup (uri), valid_uri);
755 gst_webrtc_ice_add_local_ip_address (GstWebRTCICE * ice, const gchar * address)
757 gboolean ret = FALSE;
758 NiceAddress nice_addr;
760 nice_address_init (&nice_addr);
762 ret = nice_address_set_from_string (&nice_addr, address);
765 ret = nice_agent_add_local_address (ice->priv->nice_agent, &nice_addr);
767 GST_ERROR_OBJECT (ice, "Failed to add local address to NiceAgent");
770 GST_ERROR_OBJECT (ice, "Failed to initialize NiceAddress [%s]", address);
777 gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice,
778 GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
780 struct NiceStreamItem *item;
782 g_return_val_if_fail (ufrag != NULL, FALSE);
783 g_return_val_if_fail (pwd != NULL, FALSE);
784 item = _find_item (ice, -1, -1, stream);
785 g_return_val_if_fail (item != NULL, FALSE);
787 GST_DEBUG_OBJECT (ice, "Setting local ICE credentials on "
788 "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
790 nice_agent_set_local_credentials (ice->priv->nice_agent, item->nice_stream_id,
797 gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice,
798 GstWebRTCICEStream * stream)
800 struct NiceStreamItem *item;
802 item = _find_item (ice, -1, -1, stream);
803 g_return_val_if_fail (item != NULL, FALSE);
805 GST_DEBUG_OBJECT (ice, "gather candidates for stream %u",
806 item->nice_stream_id);
808 return gst_webrtc_ice_stream_gather_candidates (stream);
812 gst_webrtc_ice_set_is_controller (GstWebRTCICE * ice, gboolean controller)
814 g_object_set (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
819 gst_webrtc_ice_get_is_controller (GstWebRTCICE * ice)
822 g_object_get (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
828 gst_webrtc_ice_set_force_relay (GstWebRTCICE * ice, gboolean force_relay)
830 g_object_set (G_OBJECT (ice->priv->nice_agent), "force-relay", force_relay,
835 gst_webrtc_ice_set_on_ice_candidate (GstWebRTCICE * ice,
836 GstWebRTCIceOnCandidateFunc func, gpointer user_data, GDestroyNotify notify)
838 if (ice->priv->on_candidate_notify)
839 ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
840 ice->priv->on_candidate = NULL;
842 ice->priv->on_candidate = func;
843 ice->priv->on_candidate_data = user_data;
844 ice->priv->on_candidate_notify = notify;
848 gst_webrtc_ice_set_tos (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
851 struct NiceStreamItem *item;
853 item = _find_item (ice, -1, -1, stream);
854 g_return_if_fail (item != NULL);
856 nice_agent_set_stream_tos (ice->priv->nice_agent, item->nice_stream_id, tos);
860 _relay_type_to_string (GstUri * turn_server)
863 const gchar *transport;
868 scheme = gst_uri_get_scheme (turn_server);
869 transport = gst_uri_get_query_value (turn_server, "transport");
871 if (g_strcmp0 (scheme, "turns") == 0) {
873 } else if (g_strcmp0 (scheme, "turn") == 0) {
874 if (!transport || g_strcmp0 (transport, "udp") == 0)
876 if (!transport || g_strcmp0 (transport, "tcp") == 0)
884 _get_server_url (GstWebRTCICE * ice, NiceCandidate * cand)
886 switch (cand->type) {
887 case NICE_CANDIDATE_TYPE_RELAYED:{
888 #if NICE_CHECK_VERSION(0, 1, 19)
890 gchar ipaddr[NICE_ADDRESS_STRING_LEN];
891 nice_candidate_relay_address (cand, &addr);
892 nice_address_to_string (&addr, ipaddr);
893 return g_strdup (ipaddr);
895 static gboolean warned = FALSE;
898 ("libnice version < 0.1.19 detected, relayed candidate server address might be wrong.");
901 return g_strdup (gst_uri_get_host (ice->turn_server));
904 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:{
905 #if NICE_CHECK_VERSION(0, 1, 20)
907 gchar ipaddr[NICE_ADDRESS_STRING_LEN];
908 if (nice_candidate_stun_server_address (cand, &addr)) {
909 nice_address_to_string (&addr, ipaddr);
910 return g_strdup (ipaddr);
912 return g_strdup (gst_uri_get_host (ice->stun_server));
915 static gboolean warned = FALSE;
918 ("libnice version < 0.1.20 detected, server-reflexive candidate server "
919 "address might be wrong.");
923 return g_strdup (gst_uri_get_host (ice->stun_server));
926 return g_strdup ("");
930 /* TODO: replace it with nice_candidate_type_to_string()
931 * when it's ready for use
932 * https://libnice.freedesktop.org/libnice/NiceCandidate.html#nice-candidate-type-to-string
935 _candidate_type_to_string (NiceCandidateType type)
938 case NICE_CANDIDATE_TYPE_HOST:
940 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
942 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
944 case NICE_CANDIDATE_TYPE_RELAYED:
947 g_assert_not_reached ();
953 _populate_candidate_stats (GstWebRTCICE * ice, NiceCandidate * cand,
954 GstWebRTCICEStream * stream, GstWebRTCICECandidateStats * stats,
957 gchar ipaddr[INET6_ADDRSTRLEN];
959 g_assert (cand != NULL);
961 nice_address_to_string (&cand->addr, ipaddr);
962 stats->port = nice_address_get_port (&cand->addr);
963 stats->ipaddr = g_strdup (ipaddr);
964 stats->stream_id = stream->stream_id;
965 stats->type = _candidate_type_to_string (cand->type);
966 stats->prio = cand->priority;
968 cand->transport == NICE_CANDIDATE_TRANSPORT_UDP ? "udp" : "tcp";
970 if (cand->type == NICE_CANDIDATE_TYPE_RELAYED)
971 stats->relay_proto = _relay_type_to_string (ice->turn_server);
972 stats->url = _get_server_url (ice, cand);
977 _populate_candidate_list_stats (GstWebRTCICE * ice, GSList * cands,
978 GstWebRTCICEStream * stream, GArray * result, gboolean is_local)
982 for (item = cands; item != NULL; item = item->next) {
983 GstWebRTCICECandidateStats stats;
984 NiceCandidate *c = item->data;
985 _populate_candidate_stats (ice, c, stream, &stats, is_local);
986 g_array_append_val (result, stats);
991 gst_webrtc_ice_get_local_candidates (GstWebRTCICE * ice,
992 GstWebRTCICEStream * stream)
994 GSList *cands = NULL;
997 g_array_new (FALSE, TRUE, sizeof (GstWebRTCICECandidateStats));
999 cands = nice_agent_get_local_candidates (ice->priv->nice_agent,
1000 stream->stream_id, NICE_COMPONENT_TYPE_RTP);
1002 _populate_candidate_list_stats (ice, cands, stream, result, TRUE);
1003 g_slist_free_full (cands, (GDestroyNotify) nice_candidate_free);
1009 gst_webrtc_ice_get_remote_candidates (GstWebRTCICE * ice,
1010 GstWebRTCICEStream * stream)
1012 GSList *cands = NULL;
1015 g_array_new (FALSE, TRUE, sizeof (GstWebRTCICECandidateStats));
1017 cands = nice_agent_get_remote_candidates (ice->priv->nice_agent,
1018 stream->stream_id, NICE_COMPONENT_TYPE_RTP);
1020 _populate_candidate_list_stats (ice, cands, stream, result, FALSE);
1021 g_slist_free_full (cands, (GDestroyNotify) nice_candidate_free);
1027 gst_webrtc_ice_get_selected_pair (GstWebRTCICE * ice,
1028 GstWebRTCICEStream * stream, GstWebRTCICECandidateStats ** local_stats,
1029 GstWebRTCICECandidateStats ** remote_stats)
1031 NiceCandidate *local_cand = NULL;
1032 NiceCandidate *remote_cand = NULL;
1035 if (nice_agent_get_selected_pair (ice->priv->nice_agent, stream->stream_id,
1036 NICE_COMPONENT_TYPE_RTP, &local_cand, &remote_cand)) {
1037 *local_stats = g_new0 (GstWebRTCICECandidateStats, 1);
1038 _populate_candidate_stats (ice, local_cand, stream, *local_stats, TRUE);
1040 *remote_stats = g_new0 (GstWebRTCICECandidateStats, 1);
1041 _populate_candidate_stats (ice, remote_cand, stream, *remote_stats,
1052 gst_webrtc_ice_candidate_stats_free (GstWebRTCICECandidateStats * stats)
1055 g_free (stats->ipaddr);
1056 g_free (stats->url);
1063 _clear_ice_stream (struct NiceStreamItem *item)
1069 GstWebRTCICE *ice = g_weak_ref_get (&item->stream->ice_weak);
1071 g_signal_handlers_disconnect_by_data (ice->priv->nice_agent,
1073 gst_object_unref (ice);
1075 gst_object_unref (item->stream);
1080 _validate_turn_server (GstWebRTCICE * ice, const gchar * s)
1082 GstUri *uri = gst_uri_from_string_escaped (s);
1083 const gchar *userinfo, *scheme;
1084 GList *keys = NULL, *l;
1085 gchar *user = NULL, *pass = NULL;
1086 gboolean turn_tls = FALSE;
1089 GST_DEBUG_OBJECT (ice, "validating turn server, %s", s);
1092 GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s);
1096 scheme = gst_uri_get_scheme (uri);
1097 if (g_strcmp0 (scheme, "turn") == 0) {
1098 } else if (g_strcmp0 (scheme, "turns") == 0) {
1101 GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme);
1105 keys = gst_uri_get_query_keys (uri);
1106 for (l = keys; l; l = l->next) {
1107 gchar *key = l->data;
1109 if (g_strcmp0 (key, "transport") == 0) {
1110 const gchar *transport = gst_uri_get_query_value (uri, "transport");
1112 } else if (g_strcmp0 (transport, "udp") == 0) {
1113 } else if (g_strcmp0 (transport, "tcp") == 0) {
1115 GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport);
1119 GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key);
1124 /* TODO: Implement error checking similar to the stun server below */
1125 userinfo = gst_uri_get_userinfo (uri);
1126 _parse_userinfo (userinfo, &user, &pass);
1128 GST_ERROR_OBJECT (ice, "No username specified in '%s'", s);
1132 GST_ERROR_OBJECT (ice, "No password specified in '%s'", s);
1136 port = gst_uri_get_port (uri);
1138 if (port == GST_URI_NO_PORT) {
1140 gst_uri_set_port (uri, 5349);
1142 gst_uri_set_port (uri, 3478);
1155 gst_webrtc_ice_set_stun_server (GstWebRTCICE * ice, const gchar * uri_s)
1157 GstUri *uri = gst_uri_from_string_escaped (uri_s);
1158 const gchar *msg = "must be of the form stun://<host>:<port>";
1160 GST_DEBUG_OBJECT (ice, "setting stun server, %s", uri_s);
1163 GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", uri_s, msg);
1167 if (ice->stun_server)
1168 gst_uri_unref (ice->stun_server);
1169 ice->stun_server = uri;
1173 gst_webrtc_ice_get_stun_server (GstWebRTCICE * ice)
1175 if (ice->stun_server)
1176 return gst_uri_to_string (ice->stun_server);
1182 gst_webrtc_ice_set_turn_server (GstWebRTCICE * ice, const gchar * uri_s)
1184 GstUri *uri = _validate_turn_server (ice, uri_s);
1187 if (ice->turn_server)
1188 gst_uri_unref (ice->turn_server);
1189 ice->turn_server = uri;
1194 gst_webrtc_ice_get_turn_server (GstWebRTCICE * ice)
1196 if (ice->turn_server)
1197 return gst_uri_to_string (ice->turn_server);
1203 gst_webrtc_ice_set_property (GObject * object, guint prop_id,
1204 const GValue * value, GParamSpec * pspec)
1206 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1210 g_object_set_property (G_OBJECT (ice->priv->nice_agent),
1214 g_object_set_property (G_OBJECT (ice->priv->nice_agent),
1218 case PROP_MIN_RTP_PORT:
1219 ice->min_rtp_port = g_value_get_uint (value);
1220 if (ice->min_rtp_port > ice->max_rtp_port)
1221 g_warning ("Set min-rtp-port to %u which is larger than"
1222 " max-rtp-port %u", ice->min_rtp_port, ice->max_rtp_port);
1225 case PROP_MAX_RTP_PORT:
1226 ice->max_rtp_port = g_value_get_uint (value);
1227 if (ice->min_rtp_port > ice->max_rtp_port)
1228 g_warning ("Set max-rtp-port to %u which is smaller than"
1229 " min-rtp-port %u", ice->max_rtp_port, ice->min_rtp_port);
1233 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1239 gst_webrtc_ice_get_property (GObject * object, guint prop_id,
1240 GValue * value, GParamSpec * pspec)
1242 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1246 g_value_set_object (value, ice->priv->nice_agent);
1249 g_object_get_property (G_OBJECT (ice->priv->nice_agent),
1253 g_object_get_property (G_OBJECT (ice->priv->nice_agent),
1257 case PROP_MIN_RTP_PORT:
1258 g_value_set_uint (value, ice->min_rtp_port);
1261 case PROP_MAX_RTP_PORT:
1262 g_value_set_uint (value, ice->max_rtp_port);
1266 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1272 gst_webrtc_ice_finalize (GObject * object)
1274 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1276 g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice);
1280 if (ice->priv->on_candidate_notify)
1281 ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
1282 ice->priv->on_candidate = NULL;
1283 ice->priv->on_candidate_notify = NULL;
1285 if (ice->turn_server)
1286 gst_uri_unref (ice->turn_server);
1287 if (ice->stun_server)
1288 gst_uri_unref (ice->stun_server);
1290 g_mutex_clear (&ice->priv->lock);
1291 g_cond_clear (&ice->priv->cond);
1293 g_array_free (ice->priv->nice_stream_map, TRUE);
1295 g_object_unref (ice->priv->nice_agent);
1297 g_hash_table_unref (ice->turn_servers);
1299 G_OBJECT_CLASS (parent_class)->finalize (object);
1303 gst_webrtc_ice_constructed (GObject * object)
1305 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1306 NiceAgentOption options = 0;
1308 _start_thread (ice);
1310 options |= NICE_AGENT_OPTION_ICE_TRICKLE;
1311 options |= NICE_AGENT_OPTION_REGULAR_NOMINATION;
1313 ice->priv->nice_agent = nice_agent_new_full (ice->priv->main_context,
1314 NICE_COMPATIBILITY_RFC5245, options);
1315 g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
1316 G_CALLBACK (_on_new_candidate), ice);
1318 G_OBJECT_CLASS (parent_class)->constructed (object);
1322 gst_webrtc_ice_class_init (GstWebRTCICEClass * klass)
1324 GObjectClass *gobject_class = (GObjectClass *) klass;
1326 gobject_class->constructed = gst_webrtc_ice_constructed;
1327 gobject_class->get_property = gst_webrtc_ice_get_property;
1328 gobject_class->set_property = gst_webrtc_ice_set_property;
1329 gobject_class->finalize = gst_webrtc_ice_finalize;
1331 g_object_class_install_property (gobject_class,
1333 g_param_spec_object ("agent", "ICE agent",
1334 "ICE agent in use by this object. WARNING! Accessing this property "
1335 "may have disastrous consequences for the operation of webrtcbin. "
1336 "Other ICE implementations may not have the same interface.",
1337 NICE_TYPE_AGENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1339 g_object_class_install_property (gobject_class,
1341 g_param_spec_boolean ("ice-tcp", "ICE TCP",
1342 "Whether the agent should use ICE-TCP when gathering candidates",
1343 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1345 g_object_class_install_property (gobject_class,
1347 g_param_spec_boolean ("ice-udp", "ICE UDP",
1348 "Whether the agent should use ICE-UDP when gathering candidates",
1349 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1352 * GstWebRTCICE:min-rtp-port:
1354 * Minimum port for local rtp port range.
1355 * min-rtp-port must be <= max-rtp-port
1359 g_object_class_install_property (gobject_class,
1361 g_param_spec_uint ("min-rtp-port", "ICE RTP candidate min port",
1362 "Minimum port for local rtp port range. "
1363 "min-rtp-port must be <= max-rtp-port",
1364 0, 65535, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1367 * GstWebRTCICE:max-rtp-port:
1369 * Maximum port for local rtp port range.
1370 * min-rtp-port must be <= max-rtp-port
1374 g_object_class_install_property (gobject_class,
1376 g_param_spec_uint ("max-rtp-port", "ICE RTP candidate max port",
1377 "Maximum port for local rtp port range. "
1378 "max-rtp-port must be >= min-rtp-port",
1380 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
1383 * GstWebRTCICE::add-local-ip-address:
1384 * @object: the #GstWebRTCICE
1385 * @address: The local IP address
1387 * Add a local IP address to use for ICE candidate gathering. If none
1388 * are supplied, they will be discovered automatically. Calling this signal
1389 * stops automatic ICE gathering.
1391 * Returns: whether the address could be added.
1393 gst_webrtc_ice_signals[ADD_LOCAL_IP_ADDRESS_SIGNAL] =
1394 g_signal_new_class_handler ("add-local-ip-address",
1395 G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1396 G_CALLBACK (gst_webrtc_ice_add_local_ip_address), NULL, NULL,
1397 g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
1401 gst_webrtc_ice_init (GstWebRTCICE * ice)
1403 ice->priv = gst_webrtc_ice_get_instance_private (ice);
1405 g_mutex_init (&ice->priv->lock);
1406 g_cond_init (&ice->priv->cond);
1409 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1410 (GDestroyNotify) gst_uri_unref);
1412 ice->priv->nice_stream_map =
1413 g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
1414 g_array_set_clear_func (ice->priv->nice_stream_map,
1415 (GDestroyNotify) _clear_ice_stream);
1419 gst_webrtc_ice_new (const gchar * name)
1421 return g_object_new (GST_TYPE_WEBRTC_ICE, "name", name, NULL);