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 ADD_LOCAL_IP_ADDRESS_SIGNAL,
50 LAST_SIGNAL,
51 };
52
53 enum
54 {
55 PROP_0,
56 PROP_AGENT,
57 PROP_ICE_TCP,
58 PROP_ICE_UDP,
59 PROP_MIN_RTP_PORT,
60 PROP_MAX_RTP_PORT,
61 };
62
63 static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 };
64
65 struct _GstWebRTCICEPrivate
66 {
67 NiceAgent *nice_agent;
68
69 GArray *nice_stream_map;
70
71 GThread *thread;
72 GMainContext *main_context;
73 GMainLoop *loop;
74 GMutex lock;
75 GCond cond;
76
77 GstWebRTCIceOnCandidateFunc on_candidate;
78 gpointer on_candidate_data;
79 GDestroyNotify on_candidate_notify;
80 };
81
82 #define gst_webrtc_ice_parent_class parent_class
83 G_DEFINE_TYPE_WITH_CODE (GstWebRTCICE, gst_webrtc_ice,
84 GST_TYPE_OBJECT, G_ADD_PRIVATE (GstWebRTCICE)
85 GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_debug, "webrtcice", 0,
86 "webrtcice"););
87
88 static gboolean
_unlock_pc_thread(GMutex * lock)89 _unlock_pc_thread (GMutex * lock)
90 {
91 g_mutex_unlock (lock);
92 return G_SOURCE_REMOVE;
93 }
94
95 static gpointer
_gst_nice_thread(GstWebRTCICE * ice)96 _gst_nice_thread (GstWebRTCICE * ice)
97 {
98 g_mutex_lock (&ice->priv->lock);
99 ice->priv->main_context = g_main_context_new ();
100 ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE);
101
102 g_cond_broadcast (&ice->priv->cond);
103 g_main_context_invoke (ice->priv->main_context,
104 (GSourceFunc) _unlock_pc_thread, &ice->priv->lock);
105
106 g_main_loop_run (ice->priv->loop);
107
108 g_mutex_lock (&ice->priv->lock);
109 g_main_context_unref (ice->priv->main_context);
110 ice->priv->main_context = NULL;
111 g_main_loop_unref (ice->priv->loop);
112 ice->priv->loop = NULL;
113 g_cond_broadcast (&ice->priv->cond);
114 g_mutex_unlock (&ice->priv->lock);
115
116 return NULL;
117 }
118
119 static void
_start_thread(GstWebRTCICE * ice)120 _start_thread (GstWebRTCICE * ice)
121 {
122 g_mutex_lock (&ice->priv->lock);
123 ice->priv->thread = g_thread_new (GST_OBJECT_NAME (ice),
124 (GThreadFunc) _gst_nice_thread, ice);
125
126 while (!ice->priv->loop)
127 g_cond_wait (&ice->priv->cond, &ice->priv->lock);
128 g_mutex_unlock (&ice->priv->lock);
129 }
130
131 static void
_stop_thread(GstWebRTCICE * ice)132 _stop_thread (GstWebRTCICE * ice)
133 {
134 g_mutex_lock (&ice->priv->lock);
135 g_main_loop_quit (ice->priv->loop);
136 while (ice->priv->loop)
137 g_cond_wait (&ice->priv->cond, &ice->priv->lock);
138 g_mutex_unlock (&ice->priv->lock);
139
140 g_thread_unref (ice->priv->thread);
141 }
142
143 struct NiceStreamItem
144 {
145 guint session_id;
146 guint nice_stream_id;
147 GstWebRTCICEStream *stream;
148 };
149
150 /* TRUE to continue, FALSE to stop */
151 typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item,
152 gpointer user_data);
153
154 static void
_nice_stream_item_foreach(GstWebRTCICE * ice,NiceStreamItemForeachFunc func,gpointer data)155 _nice_stream_item_foreach (GstWebRTCICE * ice, NiceStreamItemForeachFunc func,
156 gpointer data)
157 {
158 int i, len;
159
160 len = ice->priv->nice_stream_map->len;
161 for (i = 0; i < len; i++) {
162 struct NiceStreamItem *item =
163 &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem,
164 i);
165
166 if (!func (item, data))
167 break;
168 }
169 }
170
171 /* TRUE for match, FALSE otherwise */
172 typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item,
173 gpointer user_data);
174
175 struct nice_find
176 {
177 NiceStreamItemFindFunc func;
178 gpointer data;
179 struct NiceStreamItem *ret;
180 };
181
182 static gboolean
_find_nice_item(struct NiceStreamItem * item,gpointer user_data)183 _find_nice_item (struct NiceStreamItem *item, gpointer user_data)
184 {
185 struct nice_find *f = user_data;
186 if (f->func (item, f->data)) {
187 f->ret = item;
188 return FALSE;
189 }
190 return TRUE;
191 }
192
193 static struct NiceStreamItem *
_nice_stream_item_find(GstWebRTCICE * ice,NiceStreamItemFindFunc func,gpointer data)194 _nice_stream_item_find (GstWebRTCICE * ice, NiceStreamItemFindFunc func,
195 gpointer data)
196 {
197 struct nice_find f;
198
199 f.func = func;
200 f.data = data;
201 f.ret = NULL;
202
203 _nice_stream_item_foreach (ice, _find_nice_item, &f);
204
205 return f.ret;
206 }
207
208 #define NICE_MATCH_INIT { -1, -1, NULL }
209
210 static gboolean
_match(struct NiceStreamItem * item,struct NiceStreamItem * m)211 _match (struct NiceStreamItem *item, struct NiceStreamItem *m)
212 {
213 if (m->session_id != -1 && m->session_id != item->session_id)
214 return FALSE;
215 if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id)
216 return FALSE;
217 if (m->stream != NULL && m->stream != item->stream)
218 return FALSE;
219
220 return TRUE;
221 }
222
223 static struct NiceStreamItem *
_find_item(GstWebRTCICE * ice,guint session_id,guint nice_stream_id,GstWebRTCICEStream * stream)224 _find_item (GstWebRTCICE * ice, guint session_id, guint nice_stream_id,
225 GstWebRTCICEStream * stream)
226 {
227 struct NiceStreamItem m = NICE_MATCH_INIT;
228
229 m.session_id = session_id;
230 m.nice_stream_id = nice_stream_id;
231 m.stream = stream;
232
233 return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
234 }
235
236 static struct NiceStreamItem *
_create_nice_stream_item(GstWebRTCICE * ice,guint session_id)237 _create_nice_stream_item (GstWebRTCICE * ice, guint session_id)
238 {
239 struct NiceStreamItem item;
240
241 item.session_id = session_id;
242 item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 1);
243 item.stream = gst_webrtc_ice_stream_new (ice, item.nice_stream_id);
244 g_array_append_val (ice->priv->nice_stream_map, item);
245
246 return _find_item (ice, item.session_id, item.nice_stream_id, item.stream);
247 }
248
249 static void
_parse_userinfo(const gchar * userinfo,gchar ** user,gchar ** pass)250 _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
251 {
252 const gchar *colon;
253
254 if (!userinfo) {
255 *user = NULL;
256 *pass = NULL;
257 return;
258 }
259
260 colon = g_strstr_len (userinfo, -1, ":");
261 if (!colon) {
262 *user = g_uri_unescape_string (userinfo, NULL);
263 *pass = NULL;
264 return;
265 }
266
267 /* Check that the first occurence is also the last occurence */
268 if (colon != g_strrstr (userinfo, ":"))
269 GST_WARNING ("userinfo %s contains more than one ':', will assume that the "
270 "first ':' delineates user:pass. You should escape the user and pass "
271 "before adding to the URI.", userinfo);
272
273 *user = g_uri_unescape_segment (userinfo, colon, NULL);
274 *pass = g_uri_unescape_string (&colon[1], NULL);
275 }
276
277 static gchar *
_resolve_host(GstWebRTCICE * ice,const gchar * host)278 _resolve_host (GstWebRTCICE * ice, const gchar * host)
279 {
280 GResolver *resolver = g_resolver_get_default ();
281 GError *error = NULL;
282 GInetAddress *addr;
283 GList *addresses;
284 gchar *address;
285
286 GST_DEBUG_OBJECT (ice, "Resolving host %s", host);
287
288 if (!(addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error))) {
289 GST_ERROR ("%s", error->message);
290 g_clear_error (&error);
291 return NULL;
292 }
293
294 GST_DEBUG_OBJECT (ice, "Resolved %d addresses for host %s",
295 g_list_length (addresses), host);
296
297 /* XXX: only the first address is used */
298 addr = addresses->data;
299 address = g_inet_address_to_string (addr);
300 g_resolver_free_addresses (addresses);
301
302 return address;
303 }
304
305 static void
_add_turn_server(GstWebRTCICE * ice,struct NiceStreamItem * item,GstUri * turn_server)306 _add_turn_server (GstWebRTCICE * ice, struct NiceStreamItem *item,
307 GstUri * turn_server)
308 {
309 gboolean ret;
310 gchar *user, *pass;
311 const gchar *host, *userinfo, *transport, *scheme;
312 NiceRelayType relays[4] = { 0, };
313 int i, relay_n = 0;
314 gchar *ip = NULL;
315
316 host = gst_uri_get_host (turn_server);
317 if (!host) {
318 GST_ERROR_OBJECT (ice, "Turn server has no host");
319 goto out;
320 }
321 ip = _resolve_host (ice, host);
322 if (!ip) {
323 GST_ERROR_OBJECT (ice, "Failed to resolve turn server '%s'", host);
324 goto out;
325 }
326
327 /* Set the resolved IP as the host since that's what libnice wants */
328 gst_uri_set_host (turn_server, ip);
329
330 scheme = gst_uri_get_scheme (turn_server);
331 transport = gst_uri_get_query_value (turn_server, "transport");
332 userinfo = gst_uri_get_userinfo (turn_server);
333 _parse_userinfo (userinfo, &user, &pass);
334
335 if (g_strcmp0 (scheme, "turns") == 0) {
336 relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS;
337 } else if (g_strcmp0 (scheme, "turn") == 0) {
338 if (!transport || g_strcmp0 (transport, "udp") == 0)
339 relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP;
340 if (!transport || g_strcmp0 (transport, "tcp") == 0)
341 relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP;
342 }
343 g_assert (relay_n < G_N_ELEMENTS (relays));
344
345 for (i = 0; i < relay_n; i++) {
346 ret = nice_agent_set_relay_info (ice->priv->nice_agent,
347 item->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
348 gst_uri_get_host (turn_server), gst_uri_get_port (turn_server),
349 user, pass, relays[i]);
350 if (!ret) {
351 gchar *uri = gst_uri_to_string (turn_server);
352 GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
353 g_free (uri);
354 break;
355 }
356 }
357 g_free (user);
358 g_free (pass);
359
360 out:
361 g_free (ip);
362 }
363
364 typedef struct
365 {
366 GstWebRTCICE *ice;
367 struct NiceStreamItem *item;
368 } AddTurnServerData;
369
370 static void
_add_turn_server_func(const gchar * uri,GstUri * turn_server,AddTurnServerData * data)371 _add_turn_server_func (const gchar * uri, GstUri * turn_server,
372 AddTurnServerData * data)
373 {
374 _add_turn_server (data->ice, data->item, turn_server);
375 }
376
377 static void
_add_stun_server(GstWebRTCICE * ice,GstUri * stun_server)378 _add_stun_server (GstWebRTCICE * ice, GstUri * stun_server)
379 {
380 const gchar *msg = "must be of the form stun://<host>:<port>";
381 const gchar *host;
382 gchar *s = NULL;
383 gchar *ip = NULL;
384 guint port;
385
386 s = gst_uri_to_string (stun_server);
387 GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
388
389 host = gst_uri_get_host (stun_server);
390 if (!host) {
391 GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg);
392 goto out;
393 }
394
395 port = gst_uri_get_port (stun_server);
396 if (port == GST_URI_NO_PORT) {
397 GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s);
398 port = 3478;
399 gst_uri_set_port (stun_server, port);
400 }
401
402 ip = _resolve_host (ice, host);
403 if (!ip) {
404 GST_ERROR_OBJECT (ice, "Failed to resolve stun server '%s'", host);
405 goto out;
406 }
407
408 g_object_set (ice->priv->nice_agent, "stun-server", ip,
409 "stun-server-port", port, NULL);
410
411 out:
412 g_free (s);
413 g_free (ip);
414 }
415
416 GstWebRTCICEStream *
gst_webrtc_ice_add_stream(GstWebRTCICE * ice,guint session_id)417 gst_webrtc_ice_add_stream (GstWebRTCICE * ice, guint session_id)
418 {
419 struct NiceStreamItem m = NICE_MATCH_INIT;
420 struct NiceStreamItem *item;
421 AddTurnServerData add_data;
422
423 m.session_id = session_id;
424 item = _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
425 if (item) {
426 GST_ERROR_OBJECT (ice, "stream already added with session_id=%u",
427 session_id);
428 return 0;
429 }
430
431 if (ice->stun_server) {
432 _add_stun_server (ice, ice->stun_server);
433 }
434
435 item = _create_nice_stream_item (ice, session_id);
436
437 if (ice->turn_server) {
438 _add_turn_server (ice, item, ice->turn_server);
439 }
440
441 add_data.ice = ice;
442 add_data.item = item;
443
444 g_hash_table_foreach (ice->turn_servers, (GHFunc) _add_turn_server_func,
445 &add_data);
446
447 return item->stream;
448 }
449
450 static void
_on_new_candidate(NiceAgent * agent,NiceCandidate * candidate,GstWebRTCICE * ice)451 _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
452 GstWebRTCICE * ice)
453 {
454 struct NiceStreamItem *item;
455 gchar *attr;
456
457 item = _find_item (ice, -1, candidate->stream_id, NULL);
458 if (!item) {
459 GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u",
460 candidate->stream_id);
461 return;
462 }
463
464 if (!candidate->username || !candidate->password) {
465 gboolean got_credentials;
466 gchar *ufrag, *password;
467
468 got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent,
469 candidate->stream_id, &ufrag, &password);
470 g_warn_if_fail (got_credentials);
471
472 if (!candidate->username)
473 candidate->username = ufrag;
474 else
475 g_free (ufrag);
476
477 if (!candidate->password)
478 candidate->password = password;
479 else
480 g_free (password);
481 }
482
483 attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
484
485 if (ice->priv->on_candidate)
486 ice->priv->on_candidate (ice, item->session_id, attr,
487 ice->priv->on_candidate_data);
488
489 g_free (attr);
490 }
491
492 GstWebRTCICETransport *
gst_webrtc_ice_find_transport(GstWebRTCICE * ice,GstWebRTCICEStream * stream,GstWebRTCICEComponent component)493 gst_webrtc_ice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
494 GstWebRTCICEComponent component)
495 {
496 struct NiceStreamItem *item;
497
498 item = _find_item (ice, -1, -1, stream);
499 g_return_val_if_fail (item != NULL, NULL);
500
501 return gst_webrtc_ice_stream_find_transport (item->stream, component);
502 }
503
504 #if 0
505 /* TODO don't rely on libnice to (de)serialize candidates */
506 static NiceCandidateType
507 _candidate_type_from_string (const gchar * s)
508 {
509 if (g_strcmp0 (s, "host") == 0) {
510 return NICE_CANDIDATE_TYPE_HOST;
511 } else if (g_strcmp0 (s, "srflx") == 0) {
512 return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
513 } else if (g_strcmp0 (s, "prflx") == 0) { /* FIXME: is the right string? */
514 return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
515 } else if (g_strcmp0 (s, "relay") == 0) {
516 return NICE_CANDIDATE_TYPE_RELAY;
517 } else {
518 g_assert_not_reached ();
519 return 0;
520 }
521 }
522
523 static const gchar *
524 _candidate_type_to_string (NiceCandidateType type)
525 {
526 switch (type) {
527 case NICE_CANDIDATE_TYPE_HOST:
528 return "host";
529 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
530 return "srflx";
531 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
532 return "prflx";
533 case NICE_CANDIDATE_TYPE_RELAY:
534 return "relay";
535 default:
536 g_assert_not_reached ();
537 return NULL;
538 }
539 }
540
541 static NiceCandidateTransport
542 _candidate_transport_from_string (const gchar * s)
543 {
544 if (g_strcmp0 (s, "UDP") == 0) {
545 return NICE_CANDIDATE_TRANSPORT_UDP;
546 } else if (g_strcmp0 (s, "TCP tcptype") == 0) {
547 return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
548 } else if (g_strcmp0 (s, "tcp-passive") == 0) { /* FIXME: is the right string? */
549 return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
550 } else if (g_strcmp0 (s, "tcp-so") == 0) {
551 return NICE_CANDIDATE_TRANSPORT_TCP_SO;
552 } else {
553 g_assert_not_reached ();
554 return 0;
555 }
556 }
557
558 static const gchar *
559 _candidate_type_to_string (NiceCandidateType type)
560 {
561 switch (type) {
562 case NICE_CANDIDATE_TYPE_HOST:
563 return "host";
564 case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
565 return "srflx";
566 case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
567 return "prflx";
568 case NICE_CANDIDATE_TYPE_RELAY:
569 return "relay";
570 default:
571 g_assert_not_reached ();
572 return NULL;
573 }
574 }
575 #endif
576
577 /* parse the address for possible resolution */
578 static gboolean
get_candidate_address(const gchar * candidate,gchar ** prefix,gchar ** address,gchar ** postfix)579 get_candidate_address (const gchar * candidate, gchar ** prefix,
580 gchar ** address, gchar ** postfix)
581 {
582 char **tokens = NULL;
583
584 if (!g_str_has_prefix (candidate, "a=candidate:")) {
585 GST_ERROR ("candidate \"%s\" does not start with \"a=candidate:\"",
586 candidate);
587 goto failure;
588 }
589
590 if (!(tokens = g_strsplit (candidate, " ", 6))) {
591 GST_ERROR ("candidate \"%s\" could not be tokenized", candidate);
592 goto failure;
593 }
594
595 if (g_strv_length (tokens) < 6) {
596 GST_ERROR ("candidate \"%s\" tokenization resulted in not enough tokens",
597 candidate);
598 goto failure;
599 }
600
601 if (address)
602 *address = g_strdup (tokens[4]);
603 tokens[4] = NULL;
604 if (prefix)
605 *prefix = g_strjoinv (" ", tokens);
606 if (postfix)
607 *postfix = g_strdup (tokens[5]);
608
609 g_strfreev (tokens);
610 return TRUE;
611
612 failure:
613 if (tokens)
614 g_strfreev (tokens);
615 return FALSE;
616 }
617
618 /* candidate must start with "a=candidate:" or be NULL*/
619 void
gst_webrtc_ice_add_candidate(GstWebRTCICE * ice,GstWebRTCICEStream * stream,const gchar * candidate)620 gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
621 const gchar * candidate)
622 {
623 struct NiceStreamItem *item;
624 NiceCandidate *cand;
625 GSList *candidates = NULL;
626
627 item = _find_item (ice, -1, -1, stream);
628 g_return_if_fail (item != NULL);
629
630 if (candidate == NULL) {
631 nice_agent_peer_candidate_gathering_done (ice->priv->nice_agent,
632 item->nice_stream_id);
633 return;
634 }
635
636 cand =
637 nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
638 item->nice_stream_id, candidate);
639 if (!cand) {
640 /* might be a .local candidate */
641 char *prefix = NULL, *address = NULL, *postfix = NULL;
642 char *new_addr = NULL, *new_candidate = NULL;
643 char *new_candv[4] = { NULL, };
644 gboolean failure = TRUE;
645
646 if (!get_candidate_address (candidate, &prefix, &address, &postfix)) {
647 GST_WARNING_OBJECT (ice, "Failed to retrieve address from candidate %s",
648 candidate);
649 goto done;
650 }
651
652 if (!g_str_has_suffix (address, ".local")) {
653 GST_WARNING_OBJECT (ice, "candidate address \'%s\' does not end "
654 "with \'.local\'", address);
655 goto done;
656 }
657
658 /* FIXME: async */
659 if (!(new_addr = _resolve_host (ice, address))) {
660 GST_WARNING_OBJECT (ice, "Failed to resolve %s", address);
661 goto done;
662 }
663
664 new_candv[0] = prefix;
665 new_candv[1] = new_addr;
666 new_candv[2] = postfix;
667 new_candv[3] = NULL;
668 new_candidate = g_strjoinv (" ", new_candv);
669
670 GST_DEBUG_OBJECT (ice, "resolved to candidate %s", new_candidate);
671
672 cand =
673 nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
674 item->nice_stream_id, new_candidate);
675 if (!cand) {
676 GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'",
677 new_candidate);
678 goto done;
679 }
680
681 failure = FALSE;
682
683 done:
684 g_free (prefix);
685 g_free (address);
686 g_free (postfix);
687 g_free (new_addr);
688 g_free (new_candidate);
689 if (failure)
690 return;
691 }
692
693 if (cand->component_id == 2) {
694 /* we only support rtcp-mux so rtcp candidates are useless for us */
695 GST_INFO_OBJECT (ice, "Dropping RTCP candidate %s", candidate);
696 nice_candidate_free (cand);
697 return;
698 }
699
700 candidates = g_slist_append (candidates, cand);
701
702 nice_agent_set_remote_candidates (ice->priv->nice_agent, item->nice_stream_id,
703 cand->component_id, candidates);
704
705 g_slist_free (candidates);
706 nice_candidate_free (cand);
707 }
708
709 gboolean
gst_webrtc_ice_set_remote_credentials(GstWebRTCICE * ice,GstWebRTCICEStream * stream,gchar * ufrag,gchar * pwd)710 gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice,
711 GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
712 {
713 struct NiceStreamItem *item;
714
715 g_return_val_if_fail (ufrag != NULL, FALSE);
716 g_return_val_if_fail (pwd != NULL, FALSE);
717 item = _find_item (ice, -1, -1, stream);
718 g_return_val_if_fail (item != NULL, FALSE);
719
720 GST_DEBUG_OBJECT (ice, "Setting remote ICE credentials on "
721 "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
722
723 nice_agent_set_remote_credentials (ice->priv->nice_agent,
724 item->nice_stream_id, ufrag, pwd);
725
726 return TRUE;
727 }
728
729 gboolean
gst_webrtc_ice_add_turn_server(GstWebRTCICE * ice,const gchar * uri)730 gst_webrtc_ice_add_turn_server (GstWebRTCICE * ice, const gchar * uri)
731 {
732 gboolean ret = FALSE;
733 GstUri *valid_uri;
734
735 if (!(valid_uri = _validate_turn_server (ice, uri)))
736 goto done;
737
738 g_hash_table_insert (ice->turn_servers, g_strdup (uri), valid_uri);
739
740 ret = TRUE;
741
742 done:
743 return ret;
744 }
745
746 static gboolean
gst_webrtc_ice_add_local_ip_address(GstWebRTCICE * ice,const gchar * address)747 gst_webrtc_ice_add_local_ip_address (GstWebRTCICE * ice, const gchar * address)
748 {
749 gboolean ret = FALSE;
750 NiceAddress nice_addr;
751
752 nice_address_init (&nice_addr);
753
754 ret = nice_address_set_from_string (&nice_addr, address);
755
756 if (ret) {
757 ret = nice_agent_add_local_address (ice->priv->nice_agent, &nice_addr);
758 if (!ret) {
759 GST_ERROR_OBJECT (ice, "Failed to add local address to NiceAgent");
760 }
761 } else {
762 GST_ERROR_OBJECT (ice, "Failed to initialize NiceAddress [%s]", address);
763 }
764
765 return ret;
766 }
767
768 gboolean
gst_webrtc_ice_set_local_credentials(GstWebRTCICE * ice,GstWebRTCICEStream * stream,gchar * ufrag,gchar * pwd)769 gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice,
770 GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
771 {
772 struct NiceStreamItem *item;
773
774 g_return_val_if_fail (ufrag != NULL, FALSE);
775 g_return_val_if_fail (pwd != NULL, FALSE);
776 item = _find_item (ice, -1, -1, stream);
777 g_return_val_if_fail (item != NULL, FALSE);
778
779 GST_DEBUG_OBJECT (ice, "Setting local ICE credentials on "
780 "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
781
782 nice_agent_set_local_credentials (ice->priv->nice_agent, item->nice_stream_id,
783 ufrag, pwd);
784
785 return TRUE;
786 }
787
788 gboolean
gst_webrtc_ice_gather_candidates(GstWebRTCICE * ice,GstWebRTCICEStream * stream)789 gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice,
790 GstWebRTCICEStream * stream)
791 {
792 struct NiceStreamItem *item;
793
794 item = _find_item (ice, -1, -1, stream);
795 g_return_val_if_fail (item != NULL, FALSE);
796
797 GST_DEBUG_OBJECT (ice, "gather candidates for stream %u",
798 item->nice_stream_id);
799
800 return gst_webrtc_ice_stream_gather_candidates (stream);
801 }
802
803 void
gst_webrtc_ice_set_is_controller(GstWebRTCICE * ice,gboolean controller)804 gst_webrtc_ice_set_is_controller (GstWebRTCICE * ice, gboolean controller)
805 {
806 g_object_set (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
807 controller, NULL);
808 }
809
810 gboolean
gst_webrtc_ice_get_is_controller(GstWebRTCICE * ice)811 gst_webrtc_ice_get_is_controller (GstWebRTCICE * ice)
812 {
813 gboolean ret;
814 g_object_get (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
815 &ret, NULL);
816 return ret;
817 }
818
819 void
gst_webrtc_ice_set_force_relay(GstWebRTCICE * ice,gboolean force_relay)820 gst_webrtc_ice_set_force_relay (GstWebRTCICE * ice, gboolean force_relay)
821 {
822 g_object_set (G_OBJECT (ice->priv->nice_agent), "force-relay", force_relay,
823 NULL);
824 }
825
826 void
gst_webrtc_ice_set_on_ice_candidate(GstWebRTCICE * ice,GstWebRTCIceOnCandidateFunc func,gpointer user_data,GDestroyNotify notify)827 gst_webrtc_ice_set_on_ice_candidate (GstWebRTCICE * ice,
828 GstWebRTCIceOnCandidateFunc func, gpointer user_data, GDestroyNotify notify)
829 {
830 if (ice->priv->on_candidate_notify)
831 ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
832 ice->priv->on_candidate = NULL;
833
834 ice->priv->on_candidate = func;
835 ice->priv->on_candidate_data = user_data;
836 ice->priv->on_candidate_notify = notify;
837 }
838
839 void
gst_webrtc_ice_set_tos(GstWebRTCICE * ice,GstWebRTCICEStream * stream,guint tos)840 gst_webrtc_ice_set_tos (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
841 guint tos)
842 {
843 struct NiceStreamItem *item;
844
845 item = _find_item (ice, -1, -1, stream);
846 g_return_if_fail (item != NULL);
847
848 nice_agent_set_stream_tos (ice->priv->nice_agent, item->nice_stream_id, tos);
849 }
850
851 static void
_clear_ice_stream(struct NiceStreamItem * item)852 _clear_ice_stream (struct NiceStreamItem *item)
853 {
854 if (!item)
855 return;
856
857 if (item->stream) {
858 g_signal_handlers_disconnect_by_data (item->stream->ice->priv->nice_agent,
859 item->stream);
860 gst_object_unref (item->stream);
861 }
862 }
863
864 static GstUri *
_validate_turn_server(GstWebRTCICE * ice,const gchar * s)865 _validate_turn_server (GstWebRTCICE * ice, const gchar * s)
866 {
867 GstUri *uri = gst_uri_from_string_escaped (s);
868 const gchar *userinfo, *scheme;
869 GList *keys = NULL, *l;
870 gchar *user = NULL, *pass = NULL;
871 gboolean turn_tls = FALSE;
872 guint port;
873
874 GST_DEBUG_OBJECT (ice, "validating turn server, %s", s);
875
876 if (!uri) {
877 GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s);
878 return NULL;
879 }
880
881 scheme = gst_uri_get_scheme (uri);
882 if (g_strcmp0 (scheme, "turn") == 0) {
883 } else if (g_strcmp0 (scheme, "turns") == 0) {
884 turn_tls = TRUE;
885 } else {
886 GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme);
887 goto out;
888 }
889
890 keys = gst_uri_get_query_keys (uri);
891 for (l = keys; l; l = l->next) {
892 gchar *key = l->data;
893
894 if (g_strcmp0 (key, "transport") == 0) {
895 const gchar *transport = gst_uri_get_query_value (uri, "transport");
896 if (!transport) {
897 } else if (g_strcmp0 (transport, "udp") == 0) {
898 } else if (g_strcmp0 (transport, "tcp") == 0) {
899 } else {
900 GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport);
901 goto out;
902 }
903 } else {
904 GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key);
905 goto out;
906 }
907 }
908
909 /* TODO: Implement error checking similar to the stun server below */
910 userinfo = gst_uri_get_userinfo (uri);
911 _parse_userinfo (userinfo, &user, &pass);
912 if (!user) {
913 GST_ERROR_OBJECT (ice, "No username specified in '%s'", s);
914 goto out;
915 }
916 if (!pass) {
917 GST_ERROR_OBJECT (ice, "No password specified in '%s'", s);
918 goto out;
919 }
920
921 port = gst_uri_get_port (uri);
922
923 if (port == GST_URI_NO_PORT) {
924 if (turn_tls) {
925 gst_uri_set_port (uri, 5349);
926 } else {
927 gst_uri_set_port (uri, 3478);
928 }
929 }
930
931 out:
932 g_list_free (keys);
933 g_free (user);
934 g_free (pass);
935
936 return uri;
937 }
938
939 void
gst_webrtc_ice_set_stun_server(GstWebRTCICE * ice,const gchar * uri_s)940 gst_webrtc_ice_set_stun_server (GstWebRTCICE * ice, const gchar * uri_s)
941 {
942 GstUri *uri = gst_uri_from_string_escaped (uri_s);
943 const gchar *msg = "must be of the form stun://<host>:<port>";
944
945 GST_DEBUG_OBJECT (ice, "setting stun server, %s", uri_s);
946
947 if (!uri) {
948 GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", uri_s, msg);
949 return;
950 }
951
952 if (ice->stun_server)
953 gst_uri_unref (ice->stun_server);
954 ice->stun_server = uri;
955 }
956
957 gchar *
gst_webrtc_ice_get_stun_server(GstWebRTCICE * ice)958 gst_webrtc_ice_get_stun_server (GstWebRTCICE * ice)
959 {
960 if (ice->stun_server)
961 return gst_uri_to_string (ice->stun_server);
962 else
963 return NULL;
964 }
965
966 void
gst_webrtc_ice_set_turn_server(GstWebRTCICE * ice,const gchar * uri_s)967 gst_webrtc_ice_set_turn_server (GstWebRTCICE * ice, const gchar * uri_s)
968 {
969 GstUri *uri = _validate_turn_server (ice, uri_s);
970
971 if (uri) {
972 if (ice->turn_server)
973 gst_uri_unref (ice->turn_server);
974 ice->turn_server = uri;
975 }
976 }
977
978 gchar *
gst_webrtc_ice_get_turn_server(GstWebRTCICE * ice)979 gst_webrtc_ice_get_turn_server (GstWebRTCICE * ice)
980 {
981 if (ice->turn_server)
982 return gst_uri_to_string (ice->turn_server);
983 else
984 return NULL;
985 }
986
987 static void
gst_webrtc_ice_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)988 gst_webrtc_ice_set_property (GObject * object, guint prop_id,
989 const GValue * value, GParamSpec * pspec)
990 {
991 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
992
993 switch (prop_id) {
994 case PROP_ICE_TCP:
995 g_object_set_property (G_OBJECT (ice->priv->nice_agent),
996 "ice-tcp", value);
997 break;
998 case PROP_ICE_UDP:
999 g_object_set_property (G_OBJECT (ice->priv->nice_agent),
1000 "ice-udp", value);
1001 break;
1002
1003 case PROP_MIN_RTP_PORT:
1004 ice->min_rtp_port = g_value_get_uint (value);
1005 if (ice->min_rtp_port > ice->max_rtp_port)
1006 g_warning ("Set min-rtp-port to %u which is larger than"
1007 " max-rtp-port %u", ice->min_rtp_port, ice->max_rtp_port);
1008 break;
1009
1010 case PROP_MAX_RTP_PORT:
1011 ice->max_rtp_port = g_value_get_uint (value);
1012 if (ice->min_rtp_port > ice->max_rtp_port)
1013 g_warning ("Set max-rtp-port to %u which is smaller than"
1014 " min-rtp-port %u", ice->max_rtp_port, ice->min_rtp_port);
1015 break;
1016
1017 default:
1018 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1019 break;
1020 }
1021 }
1022
1023 static void
gst_webrtc_ice_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1024 gst_webrtc_ice_get_property (GObject * object, guint prop_id,
1025 GValue * value, GParamSpec * pspec)
1026 {
1027 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1028
1029 switch (prop_id) {
1030 case PROP_AGENT:
1031 g_value_set_object (value, ice->priv->nice_agent);
1032 break;
1033 case PROP_ICE_TCP:
1034 g_object_get_property (G_OBJECT (ice->priv->nice_agent),
1035 "ice-tcp", value);
1036 break;
1037 case PROP_ICE_UDP:
1038 g_object_get_property (G_OBJECT (ice->priv->nice_agent),
1039 "ice-udp", value);
1040 break;
1041
1042 case PROP_MIN_RTP_PORT:
1043 g_value_set_uint (value, ice->min_rtp_port);
1044 break;
1045
1046 case PROP_MAX_RTP_PORT:
1047 g_value_set_uint (value, ice->max_rtp_port);
1048 break;
1049
1050 default:
1051 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1052 break;
1053 }
1054 }
1055
1056 static void
gst_webrtc_ice_finalize(GObject * object)1057 gst_webrtc_ice_finalize (GObject * object)
1058 {
1059 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1060
1061 g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice);
1062
1063 _stop_thread (ice);
1064
1065 if (ice->priv->on_candidate_notify)
1066 ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
1067 ice->priv->on_candidate = NULL;
1068 ice->priv->on_candidate_notify = NULL;
1069
1070 if (ice->turn_server)
1071 gst_uri_unref (ice->turn_server);
1072 if (ice->stun_server)
1073 gst_uri_unref (ice->stun_server);
1074
1075 g_mutex_clear (&ice->priv->lock);
1076 g_cond_clear (&ice->priv->cond);
1077
1078 g_array_free (ice->priv->nice_stream_map, TRUE);
1079
1080 g_object_unref (ice->priv->nice_agent);
1081
1082 g_hash_table_unref (ice->turn_servers);
1083
1084 G_OBJECT_CLASS (parent_class)->finalize (object);
1085 }
1086
1087 static void
gst_webrtc_ice_constructed(GObject * object)1088 gst_webrtc_ice_constructed (GObject * object)
1089 {
1090 GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1091 NiceAgentOption options = 0;
1092
1093 _start_thread (ice);
1094
1095 options |= NICE_AGENT_OPTION_ICE_TRICKLE;
1096 options |= NICE_AGENT_OPTION_REGULAR_NOMINATION;
1097
1098 ice->priv->nice_agent = nice_agent_new_full (ice->priv->main_context,
1099 NICE_COMPATIBILITY_RFC5245, options);
1100 g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
1101 G_CALLBACK (_on_new_candidate), ice);
1102
1103 G_OBJECT_CLASS (parent_class)->constructed (object);
1104 }
1105
1106 static void
gst_webrtc_ice_class_init(GstWebRTCICEClass * klass)1107 gst_webrtc_ice_class_init (GstWebRTCICEClass * klass)
1108 {
1109 GObjectClass *gobject_class = (GObjectClass *) klass;
1110
1111 gobject_class->constructed = gst_webrtc_ice_constructed;
1112 gobject_class->get_property = gst_webrtc_ice_get_property;
1113 gobject_class->set_property = gst_webrtc_ice_set_property;
1114 gobject_class->finalize = gst_webrtc_ice_finalize;
1115
1116 g_object_class_install_property (gobject_class,
1117 PROP_AGENT,
1118 g_param_spec_object ("agent", "ICE agent",
1119 "ICE agent in use by this object. WARNING! Accessing this property "
1120 "may have disastrous consequences for the operation of webrtcbin. "
1121 "Other ICE implementations may not have the same interface.",
1122 NICE_TYPE_AGENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1123
1124 g_object_class_install_property (gobject_class,
1125 PROP_ICE_TCP,
1126 g_param_spec_boolean ("ice-tcp", "ICE TCP",
1127 "Whether the agent should use ICE-TCP when gathering candidates",
1128 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1129
1130 g_object_class_install_property (gobject_class,
1131 PROP_ICE_UDP,
1132 g_param_spec_boolean ("ice-udp", "ICE UDP",
1133 "Whether the agent should use ICE-UDP when gathering candidates",
1134 TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1135
1136 /**
1137 * GstWebRTCICE:min-rtp-port:
1138 *
1139 * Minimum port for local rtp port range.
1140 * min-rtp-port must be <= max-rtp-port
1141 *
1142 * Since: 1.20
1143 */
1144 g_object_class_install_property (gobject_class,
1145 PROP_MIN_RTP_PORT,
1146 g_param_spec_uint ("min-rtp-port", "ICE RTP candidate min port",
1147 "Minimum port for local rtp port range. "
1148 "min-rtp-port must be <= max-rtp-port",
1149 0, 65535, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1150
1151 /**
1152 * GstWebRTCICE:max-rtp-port:
1153 *
1154 * Maximum port for local rtp port range.
1155 * min-rtp-port must be <= max-rtp-port
1156 *
1157 * Since: 1.20
1158 */
1159 g_object_class_install_property (gobject_class,
1160 PROP_MAX_RTP_PORT,
1161 g_param_spec_uint ("max-rtp-port", "ICE RTP candidate max port",
1162 "Maximum port for local rtp port range. "
1163 "max-rtp-port must be >= min-rtp-port",
1164 0, 65535, 65535,
1165 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
1166
1167 /**
1168 * GstWebRTCICE::add-local-ip-address:
1169 * @object: the #GstWebRTCICE
1170 * @address: The local IP address
1171 *
1172 * Add a local IP address to use for ICE candidate gathering. If none
1173 * are supplied, they will be discovered automatically. Calling this signal
1174 * stops automatic ICE gathering.
1175 *
1176 * Returns: whether the address could be added.
1177 */
1178 gst_webrtc_ice_signals[ADD_LOCAL_IP_ADDRESS_SIGNAL] =
1179 g_signal_new_class_handler ("add-local-ip-address",
1180 G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1181 G_CALLBACK (gst_webrtc_ice_add_local_ip_address), NULL, NULL,
1182 g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
1183 }
1184
1185 static void
gst_webrtc_ice_init(GstWebRTCICE * ice)1186 gst_webrtc_ice_init (GstWebRTCICE * ice)
1187 {
1188 ice->priv = gst_webrtc_ice_get_instance_private (ice);
1189
1190 g_mutex_init (&ice->priv->lock);
1191 g_cond_init (&ice->priv->cond);
1192
1193 ice->turn_servers =
1194 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1195 (GDestroyNotify) gst_uri_unref);
1196
1197 ice->priv->nice_stream_map =
1198 g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
1199 g_array_set_clear_func (ice->priv->nice_stream_map,
1200 (GDestroyNotify) _clear_ice_stream);
1201 }
1202
1203 GstWebRTCICE *
gst_webrtc_ice_new(const gchar * name)1204 gst_webrtc_ice_new (const gchar * name)
1205 {
1206 return g_object_new (GST_TYPE_WEBRTC_ICE, "name", name, NULL);
1207 }
1208