1 /* GStreamer
2  * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
3  *
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.
8  *
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.
13  *
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.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include "gstwebrtcice.h"
25 /* libnice */
26 #include <agent.h>
27 #include "icestream.h"
28 #include "nicetransport.h"
29 
30 /* XXX:
31  *
32  * - are locally generated remote candidates meant to be readded to libnice?
33  */
34 
35 static GstUri *_validate_turn_server (GstWebRTCICE * ice, const gchar * s);
36 
37 #define GST_CAT_DEFAULT gst_webrtc_ice_debug
38 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
39 
40 GQuark
gst_webrtc_ice_error_quark(void)41 gst_webrtc_ice_error_quark (void)
42 {
43   return g_quark_from_static_string ("gst-webrtc-ice-error-quark");
44 }
45 
46 enum
47 {
48   SIGNAL_0,
49   ON_ICE_CANDIDATE_SIGNAL,
50   ON_ICE_GATHERING_STATE_CHANGE_SIGNAL,
51   LAST_SIGNAL,
52 };
53 
54 enum
55 {
56   PROP_0,
57   PROP_ICE_GATHERING_STATE,
58   PROP_STUN_SERVER,
59   PROP_TURN_SERVER,
60   PROP_CONTROLLER,
61   PROP_AGENT,
62   PROP_FORCE_RELAY,
63 };
64 
65 static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 };
66 
67 struct _GstWebRTCICEPrivate
68 {
69   NiceAgent *nice_agent;
70 
71   GArray *nice_stream_map;
72 
73   GThread *thread;
74   GMainContext *main_context;
75   GMainLoop *loop;
76   GMutex lock;
77   GCond cond;
78 };
79 
80 #define gst_webrtc_ice_parent_class parent_class
81 G_DEFINE_TYPE_WITH_CODE (GstWebRTCICE, gst_webrtc_ice,
82     GST_TYPE_OBJECT, G_ADD_PRIVATE (GstWebRTCICE)
83     GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_debug, "webrtcice", 0,
84         "webrtcice"););
85 
86 static gboolean
_unlock_pc_thread(GMutex * lock)87 _unlock_pc_thread (GMutex * lock)
88 {
89   g_mutex_unlock (lock);
90   return G_SOURCE_REMOVE;
91 }
92 
93 static gpointer
_gst_nice_thread(GstWebRTCICE * ice)94 _gst_nice_thread (GstWebRTCICE * ice)
95 {
96   g_mutex_lock (&ice->priv->lock);
97   ice->priv->main_context = g_main_context_new ();
98   ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE);
99 
100   g_cond_broadcast (&ice->priv->cond);
101   g_main_context_invoke (ice->priv->main_context,
102       (GSourceFunc) _unlock_pc_thread, &ice->priv->lock);
103 
104   g_main_loop_run (ice->priv->loop);
105 
106   g_mutex_lock (&ice->priv->lock);
107   g_main_context_unref (ice->priv->main_context);
108   ice->priv->main_context = NULL;
109   g_main_loop_unref (ice->priv->loop);
110   ice->priv->loop = NULL;
111   g_cond_broadcast (&ice->priv->cond);
112   g_mutex_unlock (&ice->priv->lock);
113 
114   return NULL;
115 }
116 
117 static void
_start_thread(GstWebRTCICE * ice)118 _start_thread (GstWebRTCICE * ice)
119 {
120   g_mutex_lock (&ice->priv->lock);
121   ice->priv->thread = g_thread_new ("gst-nice-ops",
122       (GThreadFunc) _gst_nice_thread, ice);
123 
124   while (!ice->priv->loop)
125     g_cond_wait (&ice->priv->cond, &ice->priv->lock);
126   g_mutex_unlock (&ice->priv->lock);
127 }
128 
129 static void
_stop_thread(GstWebRTCICE * ice)130 _stop_thread (GstWebRTCICE * ice)
131 {
132   g_mutex_lock (&ice->priv->lock);
133   g_main_loop_quit (ice->priv->loop);
134   while (ice->priv->loop)
135     g_cond_wait (&ice->priv->cond, &ice->priv->lock);
136   g_mutex_unlock (&ice->priv->lock);
137 
138   g_thread_unref (ice->priv->thread);
139 }
140 
141 #if 0
142 static NiceComponentType
143 _webrtc_component_to_nice (GstWebRTCICEComponent comp)
144 {
145   switch (comp) {
146     case GST_WEBRTC_ICE_COMPONENT_RTP:
147       return NICE_COMPONENT_TYPE_RTP;
148     case GST_WEBRTC_ICE_COMPONENT_RTCP:
149       return NICE_COMPONENT_TYPE_RTCP;
150     default:
151       g_assert_not_reached ();
152       return 0;
153   }
154 }
155 
156 static GstWebRTCICEComponent
157 _nice_component_to_webrtc (NiceComponentType comp)
158 {
159   switch (comp) {
160     case NICE_COMPONENT_TYPE_RTP:
161       return GST_WEBRTC_ICE_COMPONENT_RTP;
162     case NICE_COMPONENT_TYPE_RTCP:
163       return GST_WEBRTC_ICE_COMPONENT_RTCP;
164     default:
165       g_assert_not_reached ();
166       return 0;
167   }
168 }
169 #endif
170 struct NiceStreamItem
171 {
172   guint session_id;
173   guint nice_stream_id;
174   GstWebRTCICEStream *stream;
175 };
176 
177 /* TRUE to continue, FALSE to stop */
178 typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item,
179     gpointer user_data);
180 
181 static void
_nice_stream_item_foreach(GstWebRTCICE * ice,NiceStreamItemForeachFunc func,gpointer data)182 _nice_stream_item_foreach (GstWebRTCICE * ice, NiceStreamItemForeachFunc func,
183     gpointer data)
184 {
185   int i, len;
186 
187   len = ice->priv->nice_stream_map->len;
188   for (i = 0; i < len; i++) {
189     struct NiceStreamItem *item =
190         &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem,
191         i);
192 
193     if (!func (item, data))
194       break;
195   }
196 }
197 
198 /* TRUE for match, FALSE otherwise */
199 typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item,
200     gpointer user_data);
201 
202 struct nice_find
203 {
204   NiceStreamItemFindFunc func;
205   gpointer data;
206   struct NiceStreamItem *ret;
207 };
208 
209 static gboolean
_find_nice_item(struct NiceStreamItem * item,gpointer user_data)210 _find_nice_item (struct NiceStreamItem *item, gpointer user_data)
211 {
212   struct nice_find *f = user_data;
213   if (f->func (item, f->data)) {
214     f->ret = item;
215     return FALSE;
216   }
217   return TRUE;
218 }
219 
220 static struct NiceStreamItem *
_nice_stream_item_find(GstWebRTCICE * ice,NiceStreamItemFindFunc func,gpointer data)221 _nice_stream_item_find (GstWebRTCICE * ice, NiceStreamItemFindFunc func,
222     gpointer data)
223 {
224   struct nice_find f;
225 
226   f.func = func;
227   f.data = data;
228   f.ret = NULL;
229 
230   _nice_stream_item_foreach (ice, _find_nice_item, &f);
231 
232   return f.ret;
233 }
234 
235 #define NICE_MATCH_INIT { -1, -1, NULL }
236 
237 static gboolean
_match(struct NiceStreamItem * item,struct NiceStreamItem * m)238 _match (struct NiceStreamItem *item, struct NiceStreamItem *m)
239 {
240   if (m->session_id != -1 && m->session_id != item->session_id)
241     return FALSE;
242   if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id)
243     return FALSE;
244   if (m->stream != NULL && m->stream != item->stream)
245     return FALSE;
246 
247   return TRUE;
248 }
249 
250 static struct NiceStreamItem *
_find_item(GstWebRTCICE * ice,guint session_id,guint nice_stream_id,GstWebRTCICEStream * stream)251 _find_item (GstWebRTCICE * ice, guint session_id, guint nice_stream_id,
252     GstWebRTCICEStream * stream)
253 {
254   struct NiceStreamItem m = NICE_MATCH_INIT;
255 
256   m.session_id = session_id;
257   m.nice_stream_id = nice_stream_id;
258   m.stream = stream;
259 
260   return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
261 }
262 
263 static struct NiceStreamItem *
_create_nice_stream_item(GstWebRTCICE * ice,guint session_id)264 _create_nice_stream_item (GstWebRTCICE * ice, guint session_id)
265 {
266   struct NiceStreamItem item;
267 
268   item.session_id = session_id;
269   item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 2);
270   item.stream = gst_webrtc_ice_stream_new (ice, item.nice_stream_id);
271   g_array_append_val (ice->priv->nice_stream_map, item);
272 
273   return _find_item (ice, item.session_id, item.nice_stream_id, item.stream);
274 }
275 
276 static void
_parse_userinfo(const gchar * userinfo,gchar ** user,gchar ** pass)277 _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
278 {
279   const gchar *colon;
280 
281   if (!userinfo) {
282     *user = NULL;
283     *pass = NULL;
284     return;
285   }
286 
287   colon = g_strstr_len (userinfo, -1, ":");
288   if (!colon) {
289     *user = g_strdup (userinfo);
290     *pass = NULL;
291     return;
292   }
293 
294   *user = g_strndup (userinfo, colon - userinfo);
295   *pass = g_strdup (&colon[1]);
296 }
297 
298 static gchar *
_resolve_host(GstWebRTCICE * ice,const gchar * host)299 _resolve_host (GstWebRTCICE * ice, const gchar * host)
300 {
301   GResolver *resolver = g_resolver_get_default ();
302   GError *error = NULL;
303   GInetAddress *addr;
304   GList *addresses;
305   gchar *address;
306 
307   GST_DEBUG_OBJECT (ice, "Resolving host %s", host);
308 
309   if (!(addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error))) {
310     GST_ERROR ("%s", error->message);
311     g_clear_error (&error);
312     return NULL;
313   }
314 
315   GST_DEBUG_OBJECT (ice, "Resolved %d addresses for host %s",
316       g_list_length (addresses), host);
317 
318   /* XXX: only the first address is used */
319   addr = addresses->data;
320   address = g_inet_address_to_string (addr);
321   g_resolver_free_addresses (addresses);
322 
323   return address;
324 }
325 
326 static void
_add_turn_server(GstWebRTCICE * ice,struct NiceStreamItem * item,GstUri * turn_server)327 _add_turn_server (GstWebRTCICE * ice, struct NiceStreamItem *item,
328     GstUri * turn_server)
329 {
330   gboolean ret;
331   gchar *user, *pass;
332   const gchar *host, *userinfo, *transport, *scheme;
333   NiceRelayType relays[4] = { 0, };
334   int i, relay_n = 0;
335   gchar *ip = NULL;
336 
337   host = gst_uri_get_host (turn_server);
338   if (!host) {
339     GST_ERROR_OBJECT (ice, "Turn server has no host");
340     goto out;
341   }
342   ip = _resolve_host (ice, host);
343   if (!ip) {
344     GST_ERROR_OBJECT (ice, "Failed to resolve turn server '%s'", host);
345     goto out;
346   }
347 
348   /* Set the resolved IP as the host since that's what libnice wants */
349   gst_uri_set_host (turn_server, ip);
350 
351   scheme = gst_uri_get_scheme (turn_server);
352   transport = gst_uri_get_query_value (turn_server, "transport");
353   userinfo = gst_uri_get_userinfo (turn_server);
354   _parse_userinfo (userinfo, &user, &pass);
355 
356   if (g_strcmp0 (scheme, "turns") == 0) {
357     relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS;
358   } else if (g_strcmp0 (scheme, "turn") == 0) {
359     if (!transport || g_strcmp0 (transport, "udp") == 0)
360       relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP;
361     if (!transport || g_strcmp0 (transport, "tcp") == 0)
362       relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP;
363   }
364   g_assert (relay_n < G_N_ELEMENTS (relays));
365 
366   for (i = 0; i < relay_n; i++) {
367     ret = nice_agent_set_relay_info (ice->priv->nice_agent,
368         item->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
369         gst_uri_get_host (turn_server), gst_uri_get_port (turn_server), user,
370         pass, relays[i]);
371     if (!ret) {
372       gchar *uri = gst_uri_to_string (turn_server);
373       GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
374       g_free (uri);
375       break;
376     }
377     ret = nice_agent_set_relay_info (ice->priv->nice_agent,
378         item->nice_stream_id, NICE_COMPONENT_TYPE_RTCP,
379         gst_uri_get_host (turn_server), gst_uri_get_port (turn_server), user,
380         pass, relays[i]);
381     if (!ret) {
382       gchar *uri = gst_uri_to_string (turn_server);
383       GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
384       g_free (uri);
385       break;
386     }
387   }
388   g_free (user);
389   g_free (pass);
390 
391 out:
392   g_free (ip);
393 }
394 
395 typedef struct
396 {
397   GstWebRTCICE *ice;
398   struct NiceStreamItem *item;
399 } AddTurnServerData;
400 
401 static void
_add_turn_server_func(const gchar * uri,GstUri * turn_server,AddTurnServerData * data)402 _add_turn_server_func (const gchar * uri, GstUri * turn_server,
403     AddTurnServerData * data)
404 {
405   _add_turn_server (data->ice, data->item, turn_server);
406 }
407 
408 static void
_add_stun_server(GstWebRTCICE * ice,GstUri * stun_server)409 _add_stun_server (GstWebRTCICE * ice, GstUri * stun_server)
410 {
411   const gchar *msg = "must be of the form stun://<host>:<port>";
412   const gchar *host;
413   gchar *s = NULL;
414   gchar *ip = NULL;
415   guint port;
416 
417   GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
418 
419   s = gst_uri_to_string (stun_server);
420 
421   host = gst_uri_get_host (stun_server);
422   if (!host) {
423     GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg);
424     goto out;
425   }
426 
427   port = gst_uri_get_port (stun_server);
428   if (port == GST_URI_NO_PORT) {
429     GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s);
430     port = 3478;
431     gst_uri_set_port (stun_server, port);
432   }
433 
434   ip = _resolve_host (ice, host);
435   if (!ip) {
436     GST_ERROR_OBJECT (ice, "Failed to resolve stun server '%s'", host);
437     goto out;
438   }
439 
440   g_object_set (ice->priv->nice_agent, "stun-server", ip,
441       "stun-server-port", port, NULL);
442 
443 out:
444   g_free (s);
445   g_free (ip);
446 }
447 
448 GstWebRTCICEStream *
gst_webrtc_ice_add_stream(GstWebRTCICE * ice,guint session_id)449 gst_webrtc_ice_add_stream (GstWebRTCICE * ice, guint session_id)
450 {
451   struct NiceStreamItem m = NICE_MATCH_INIT;
452   struct NiceStreamItem *item;
453   AddTurnServerData add_data;
454 
455   m.session_id = session_id;
456   item = _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
457   if (item) {
458     GST_ERROR_OBJECT (ice, "stream already added with session_id=%u",
459         session_id);
460     return 0;
461   }
462 
463   if (ice->stun_server) {
464     _add_stun_server (ice, ice->stun_server);
465   }
466 
467   item = _create_nice_stream_item (ice, session_id);
468 
469   if (ice->turn_server) {
470     _add_turn_server (ice, item, ice->turn_server);
471   }
472 
473   add_data.ice = ice;
474   add_data.item = item;
475 
476   g_hash_table_foreach (ice->turn_servers, (GHFunc) _add_turn_server_func,
477       &add_data);
478 
479   return item->stream;
480 }
481 
482 static void
_on_new_candidate(NiceAgent * agent,NiceCandidate * candidate,GstWebRTCICE * ice)483 _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
484     GstWebRTCICE * ice)
485 {
486   struct NiceStreamItem *item;
487   gchar *attr;
488 
489   item = _find_item (ice, -1, candidate->stream_id, NULL);
490   if (!item) {
491     GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u",
492         candidate->stream_id);
493     return;
494   }
495 
496   if (!candidate->username || !candidate->password) {
497     gboolean got_credentials;
498     gchar *ufrag, *password;
499 
500     got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent,
501         candidate->stream_id, &ufrag, &password);
502     g_warn_if_fail (got_credentials);
503 
504     if (!candidate->username)
505       candidate->username = ufrag;
506     else
507       g_free (ufrag);
508 
509     if (!candidate->password)
510       candidate->password = password;
511     else
512       g_free (password);
513   }
514 
515   attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
516   g_signal_emit (ice, gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL],
517       0, item->session_id, attr);
518   g_free (attr);
519 }
520 
521 GstWebRTCICETransport *
gst_webrtc_ice_find_transport(GstWebRTCICE * ice,GstWebRTCICEStream * stream,GstWebRTCICEComponent component)522 gst_webrtc_ice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
523     GstWebRTCICEComponent component)
524 {
525   struct NiceStreamItem *item;
526 
527   item = _find_item (ice, -1, -1, stream);
528   g_return_val_if_fail (item != NULL, NULL);
529 
530   return gst_webrtc_ice_stream_find_transport (item->stream, component);
531 }
532 
533 #if 0
534 /* TODO don't rely on libnice to (de)serialize candidates */
535 static NiceCandidateType
536 _candidate_type_from_string (const gchar * s)
537 {
538   if (g_strcmp0 (s, "host") == 0) {
539     return NICE_CANDIDATE_TYPE_HOST;
540   } else if (g_strcmp0 (s, "srflx") == 0) {
541     return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
542   } else if (g_strcmp0 (s, "prflx") == 0) {     /* FIXME: is the right string? */
543     return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
544   } else if (g_strcmp0 (s, "relay") == 0) {
545     return NICE_CANDIDATE_TYPE_RELAY;
546   } else {
547     g_assert_not_reached ();
548     return 0;
549   }
550 }
551 
552 static const gchar *
553 _candidate_type_to_string (NiceCandidateType type)
554 {
555   switch (type) {
556     case NICE_CANDIDATE_TYPE_HOST:
557       return "host";
558     case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
559       return "srflx";
560     case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
561       return "prflx";
562     case NICE_CANDIDATE_TYPE_RELAY:
563       return "relay";
564     default:
565       g_assert_not_reached ();
566       return NULL;
567   }
568 }
569 
570 static NiceCandidateTransport
571 _candidate_transport_from_string (const gchar * s)
572 {
573   if (g_strcmp0 (s, "UDP") == 0) {
574     return NICE_CANDIDATE_TRANSPORT_UDP;
575   } else if (g_strcmp0 (s, "TCP tcptype") == 0) {
576     return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
577   } else if (g_strcmp0 (s, "tcp-passive") == 0) {       /* FIXME: is the right string? */
578     return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
579   } else if (g_strcmp0 (s, "tcp-so") == 0) {
580     return NICE_CANDIDATE_TRANSPORT_TCP_SO;
581   } else {
582     g_assert_not_reached ();
583     return 0;
584   }
585 }
586 
587 static const gchar *
588 _candidate_type_to_string (NiceCandidateType type)
589 {
590   switch (type) {
591     case NICE_CANDIDATE_TYPE_HOST:
592       return "host";
593     case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
594       return "srflx";
595     case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
596       return "prflx";
597     case NICE_CANDIDATE_TYPE_RELAY:
598       return "relay";
599     default:
600       g_assert_not_reached ();
601       return NULL;
602   }
603 }
604 #endif
605 
606 /* must start with "a=candidate:" */
607 void
gst_webrtc_ice_add_candidate(GstWebRTCICE * ice,GstWebRTCICEStream * stream,const gchar * candidate)608 gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
609     const gchar * candidate)
610 {
611   struct NiceStreamItem *item;
612   NiceCandidate *cand;
613   GSList *candidates = NULL;
614 
615   item = _find_item (ice, -1, -1, stream);
616   g_return_if_fail (item != NULL);
617 
618   cand =
619       nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
620       item->nice_stream_id, candidate);
621   if (!cand) {
622     GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'", candidate);
623     return;
624   }
625 
626   candidates = g_slist_append (candidates, cand);
627 
628   nice_agent_set_remote_candidates (ice->priv->nice_agent, item->nice_stream_id,
629       cand->component_id, candidates);
630 
631   g_slist_free (candidates);
632   nice_candidate_free (cand);
633 }
634 
635 gboolean
gst_webrtc_ice_set_remote_credentials(GstWebRTCICE * ice,GstWebRTCICEStream * stream,gchar * ufrag,gchar * pwd)636 gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice,
637     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
638 {
639   struct NiceStreamItem *item;
640 
641   g_return_val_if_fail (ufrag != NULL, FALSE);
642   g_return_val_if_fail (pwd != NULL, FALSE);
643   item = _find_item (ice, -1, -1, stream);
644   g_return_val_if_fail (item != NULL, FALSE);
645 
646   GST_DEBUG_OBJECT (ice, "Setting remote ICE credentials on "
647       "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
648 
649   nice_agent_set_remote_credentials (ice->priv->nice_agent,
650       item->nice_stream_id, ufrag, pwd);
651 
652   return TRUE;
653 }
654 
655 gboolean
gst_webrtc_ice_add_turn_server(GstWebRTCICE * ice,const gchar * uri)656 gst_webrtc_ice_add_turn_server (GstWebRTCICE * ice, const gchar * uri)
657 {
658   gboolean ret = FALSE;
659   GstUri *valid_uri;
660 
661   if (!(valid_uri = _validate_turn_server (ice, uri)))
662     goto done;
663 
664   g_hash_table_insert (ice->turn_servers, g_strdup (uri), valid_uri);
665 
666   ret = TRUE;
667 
668 done:
669   return ret;
670 }
671 
672 gboolean
gst_webrtc_ice_set_local_credentials(GstWebRTCICE * ice,GstWebRTCICEStream * stream,gchar * ufrag,gchar * pwd)673 gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice,
674     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
675 {
676   struct NiceStreamItem *item;
677 
678   g_return_val_if_fail (ufrag != NULL, FALSE);
679   g_return_val_if_fail (pwd != NULL, FALSE);
680   item = _find_item (ice, -1, -1, stream);
681   g_return_val_if_fail (item != NULL, FALSE);
682 
683   GST_DEBUG_OBJECT (ice, "Setting local ICE credentials on "
684       "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
685 
686   nice_agent_set_local_credentials (ice->priv->nice_agent, item->nice_stream_id,
687       ufrag, pwd);
688 
689   return TRUE;
690 }
691 
692 gboolean
gst_webrtc_ice_gather_candidates(GstWebRTCICE * ice,GstWebRTCICEStream * stream)693 gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice,
694     GstWebRTCICEStream * stream)
695 {
696   struct NiceStreamItem *item;
697 
698   item = _find_item (ice, -1, -1, stream);
699   g_return_val_if_fail (item != NULL, FALSE);
700 
701   GST_DEBUG_OBJECT (ice, "gather candidates for stream %u",
702       item->nice_stream_id);
703 
704   return gst_webrtc_ice_stream_gather_candidates (stream);
705 }
706 
707 static void
_clear_ice_stream(struct NiceStreamItem * item)708 _clear_ice_stream (struct NiceStreamItem *item)
709 {
710   if (!item)
711     return;
712 
713   if (item->stream) {
714     g_signal_handlers_disconnect_by_data (item->stream->ice->priv->nice_agent,
715         item->stream);
716     gst_object_unref (item->stream);
717   }
718 }
719 
720 static GstUri *
_validate_turn_server(GstWebRTCICE * ice,const gchar * s)721 _validate_turn_server (GstWebRTCICE * ice, const gchar * s)
722 {
723   GstUri *uri = gst_uri_from_string (s);
724   const gchar *userinfo, *scheme;
725   GList *keys = NULL, *l;
726   gchar *user = NULL, *pass = NULL;
727   gboolean turn_tls = FALSE;
728   guint port;
729 
730   GST_DEBUG_OBJECT (ice, "validating turn server, %s", s);
731 
732   if (!uri) {
733     GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s);
734     return NULL;
735   }
736 
737   scheme = gst_uri_get_scheme (uri);
738   if (g_strcmp0 (scheme, "turn") == 0) {
739   } else if (g_strcmp0 (scheme, "turns") == 0) {
740     turn_tls = TRUE;
741   } else {
742     GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme);
743     goto out;
744   }
745 
746   keys = gst_uri_get_query_keys (uri);
747   for (l = keys; l; l = l->next) {
748     gchar *key = l->data;
749 
750     if (g_strcmp0 (key, "transport") == 0) {
751       const gchar *transport = gst_uri_get_query_value (uri, "transport");
752       if (!transport) {
753       } else if (g_strcmp0 (transport, "udp") == 0) {
754       } else if (g_strcmp0 (transport, "tcp") == 0) {
755       } else {
756         GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport);
757         goto out;
758       }
759     } else {
760       GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key);
761       goto out;
762     }
763   }
764 
765   /* TODO: Implement error checking similar to the stun server below */
766   userinfo = gst_uri_get_userinfo (uri);
767   _parse_userinfo (userinfo, &user, &pass);
768   if (!user) {
769     GST_ERROR_OBJECT (ice, "No username specified in '%s'", s);
770     goto out;
771   }
772   if (!pass) {
773     GST_ERROR_OBJECT (ice, "No password specified in '%s'", s);
774     goto out;
775   }
776 
777   port = gst_uri_get_port (uri);
778 
779   if (port == GST_URI_NO_PORT) {
780     if (turn_tls) {
781       gst_uri_set_port (uri, 5349);
782     } else {
783       gst_uri_set_port (uri, 3478);
784     }
785   }
786 
787 out:
788   g_list_free (keys);
789   g_free (user);
790   g_free (pass);
791 
792   return uri;
793 }
794 
795 static void
gst_webrtc_ice_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)796 gst_webrtc_ice_set_property (GObject * object, guint prop_id,
797     const GValue * value, GParamSpec * pspec)
798 {
799   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
800 
801   switch (prop_id) {
802     case PROP_STUN_SERVER:{
803       const gchar *s = g_value_get_string (value);
804       GstUri *uri = gst_uri_from_string (s);
805       const gchar *msg = "must be of the form stun://<host>:<port>";
806 
807       GST_DEBUG_OBJECT (ice, "setting stun server, %s", s);
808 
809       if (!uri) {
810         GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", s, msg);
811         return;
812       }
813 
814       if (ice->stun_server)
815         gst_uri_unref (ice->stun_server);
816       ice->stun_server = uri;
817       break;
818     }
819     case PROP_TURN_SERVER:{
820       GstUri *uri = _validate_turn_server (ice, g_value_get_string (value));
821 
822       if (uri) {
823         if (ice->turn_server)
824           gst_uri_unref (ice->turn_server);
825         ice->turn_server = uri;
826       }
827       break;
828     }
829     case PROP_CONTROLLER:
830       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
831           "controlling-mode", value);
832       break;
833     case PROP_FORCE_RELAY:
834       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
835           "force-relay", value);
836       break;
837     default:
838       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
839       break;
840   }
841 }
842 
843 static void
gst_webrtc_ice_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)844 gst_webrtc_ice_get_property (GObject * object, guint prop_id,
845     GValue * value, GParamSpec * pspec)
846 {
847   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
848 
849   switch (prop_id) {
850     case PROP_STUN_SERVER:
851       if (ice->stun_server)
852         g_value_take_string (value, gst_uri_to_string (ice->stun_server));
853       else
854         g_value_take_string (value, NULL);
855       break;
856     case PROP_TURN_SERVER:
857       if (ice->turn_server)
858         g_value_take_string (value, gst_uri_to_string (ice->turn_server));
859       else
860         g_value_take_string (value, NULL);
861       break;
862     case PROP_CONTROLLER:
863       g_object_get_property (G_OBJECT (ice->priv->nice_agent),
864           "controlling-mode", value);
865       break;
866     case PROP_AGENT:
867       g_value_set_object (value, ice->priv->nice_agent);
868       break;
869     case PROP_FORCE_RELAY:
870       g_object_get_property (G_OBJECT (ice->priv->nice_agent),
871           "force-relay", value);
872       break;
873     default:
874       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
875       break;
876   }
877 }
878 
879 static void
gst_webrtc_ice_finalize(GObject * object)880 gst_webrtc_ice_finalize (GObject * object)
881 {
882   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
883 
884   g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice);
885 
886   _stop_thread (ice);
887 
888   if (ice->turn_server)
889     gst_uri_unref (ice->turn_server);
890   if (ice->stun_server)
891     gst_uri_unref (ice->stun_server);
892 
893   g_mutex_clear (&ice->priv->lock);
894   g_cond_clear (&ice->priv->cond);
895 
896   g_array_free (ice->priv->nice_stream_map, TRUE);
897 
898   g_object_unref (ice->priv->nice_agent);
899 
900   g_hash_table_unref (ice->turn_servers);
901 
902   G_OBJECT_CLASS (parent_class)->finalize (object);
903 }
904 
905 static void
gst_webrtc_ice_class_init(GstWebRTCICEClass * klass)906 gst_webrtc_ice_class_init (GstWebRTCICEClass * klass)
907 {
908   GObjectClass *gobject_class = (GObjectClass *) klass;
909 
910   gobject_class->get_property = gst_webrtc_ice_get_property;
911   gobject_class->set_property = gst_webrtc_ice_set_property;
912   gobject_class->finalize = gst_webrtc_ice_finalize;
913 
914   g_object_class_install_property (gobject_class,
915       PROP_STUN_SERVER,
916       g_param_spec_string ("stun-server", "STUN Server",
917           "The STUN server of the form stun://hostname:port",
918           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
919 
920   g_object_class_install_property (gobject_class,
921       PROP_TURN_SERVER,
922       g_param_spec_string ("turn-server", "TURN Server",
923           "The TURN server of the form turn(s)://username:password@host:port",
924           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
925 
926   g_object_class_install_property (gobject_class,
927       PROP_CONTROLLER,
928       g_param_spec_boolean ("controller", "ICE controller",
929           "Whether the ICE agent is the controller or controlled. "
930           "In WebRTC, the initial offerrer is the ICE controller.", FALSE,
931           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
932 
933   g_object_class_install_property (gobject_class,
934       PROP_AGENT,
935       g_param_spec_object ("agent", "ICE agent",
936           "ICE agent in use by this object", NICE_TYPE_AGENT,
937           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
938 
939   g_object_class_install_property (gobject_class,
940       PROP_FORCE_RELAY,
941       g_param_spec_boolean ("force-relay", "Force Relay",
942           "Force all traffic to go through a relay.", FALSE,
943           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
944 
945   /**
946    * GstWebRTCICE::on-ice-candidate:
947    * @object: the #GstWebRtcBin
948    * @candidate: the ICE candidate
949    */
950   gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL] =
951       g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass),
952       G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
953       G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
954 }
955 
956 static void
gst_webrtc_ice_init(GstWebRTCICE * ice)957 gst_webrtc_ice_init (GstWebRTCICE * ice)
958 {
959   ice->priv = gst_webrtc_ice_get_instance_private (ice);
960 
961   g_mutex_init (&ice->priv->lock);
962   g_cond_init (&ice->priv->cond);
963 
964   ice->turn_servers =
965       g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
966       (GDestroyNotify) gst_uri_unref);
967 
968   _start_thread (ice);
969 
970   ice->priv->nice_agent = nice_agent_new (ice->priv->main_context,
971       NICE_COMPATIBILITY_RFC5245);
972   g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
973       G_CALLBACK (_on_new_candidate), ice);
974 
975   ice->priv->nice_stream_map =
976       g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
977   g_array_set_clear_func (ice->priv->nice_stream_map,
978       (GDestroyNotify) _clear_ice_stream);
979 }
980 
981 GstWebRTCICE *
gst_webrtc_ice_new(void)982 gst_webrtc_ice_new (void)
983 {
984   return g_object_new (GST_TYPE_WEBRTC_ICE, NULL);
985 }
986