1 /**
2 * @file icem.c ICE Media stream
3 *
4 * Copyright (C) 2010 Creytiv.com
5 */
6 #include <re_types.h>
7 #include <re_fmt.h>
8 #include <re_mem.h>
9 #include <re_mbuf.h>
10 #include <re_list.h>
11 #include <re_tmr.h>
12 #include <re_sa.h>
13 #include <re_stun.h>
14 #include <re_turn.h>
15 #include <re_ice.h>
16 #include "ice.h"
17
18
19 #define DEBUG_MODULE "icem"
20 #define DEBUG_LEVEL 5
21 #include <re_dbg.h>
22
23
24 /*
25 * ICE Implementation as of RFC 5245
26 */
27
28
29 static const struct ice_conf conf_default = {
30 ICE_NOMINATION_REGULAR,
31 ICE_DEFAULT_RTO_RTP,
32 ICE_DEFAULT_RC,
33 false
34 };
35
36
37 /** Determining Role */
ice_determine_role(struct icem * icem,enum ice_role role)38 static void ice_determine_role(struct icem *icem, enum ice_role role)
39 {
40 if (!icem)
41 return;
42
43 if (icem->lmode == icem->rmode)
44 icem->lrole = role;
45 else if (icem->lmode == ICE_MODE_FULL)
46 icem->lrole = ICE_ROLE_CONTROLLING;
47 else
48 icem->lrole = ICE_ROLE_CONTROLLED;
49 }
50
51
icem_destructor(void * data)52 static void icem_destructor(void *data)
53 {
54 struct icem *icem = data;
55
56 tmr_cancel(&icem->tmr_pace);
57 list_flush(&icem->compl);
58 list_flush(&icem->validl);
59 list_flush(&icem->checkl);
60 list_flush(&icem->lcandl);
61 list_flush(&icem->rcandl);
62 mem_deref(icem->lufrag);
63 mem_deref(icem->lpwd);
64 mem_deref(icem->rufrag);
65 mem_deref(icem->rpwd);
66 mem_deref(icem->stun);
67 }
68
69
70 /**
71 * Add a new ICE Media object to the ICE Session
72 *
73 * @param icemp Pointer to allocated ICE Media object
74 * @param mode ICE mode
75 * @param role Local ICE role
76 * @param proto Transport protocol
77 * @param layer Protocol stack layer
78 * @param tiebrk Tie-breaker value, must be same for all media streams
79 * @param lufrag Local username fragment
80 * @param lpwd Local password
81 * @param chkh Connectivity check handler
82 * @param arg Handler argument
83 *
84 * @return 0 if success, otherwise errorcode
85 */
icem_alloc(struct icem ** icemp,enum ice_mode mode,enum ice_role role,int proto,int layer,uint64_t tiebrk,const char * lufrag,const char * lpwd,ice_connchk_h * chkh,void * arg)86 int icem_alloc(struct icem **icemp,
87 enum ice_mode mode, enum ice_role role,
88 int proto, int layer,
89 uint64_t tiebrk, const char *lufrag, const char *lpwd,
90 ice_connchk_h *chkh, void *arg)
91 {
92 struct icem *icem;
93 int err = 0;
94
95 if (!icemp || !tiebrk || !lufrag || !lpwd)
96 return EINVAL;
97
98 if (str_len(lufrag) < 4 || str_len(lpwd) < 22) {
99 DEBUG_WARNING("alloc: lufrag/lpwd is too short\n");
100 return EINVAL;
101 }
102
103 if (proto != IPPROTO_UDP)
104 return EPROTONOSUPPORT;
105
106 icem = mem_zalloc(sizeof(*icem), icem_destructor);
107 if (!icem)
108 return ENOMEM;
109
110 icem->conf = conf_default;
111
112 tmr_init(&icem->tmr_pace);
113 list_init(&icem->lcandl);
114 list_init(&icem->rcandl);
115 list_init(&icem->checkl);
116 list_init(&icem->validl);
117
118 icem->layer = layer;
119 icem->proto = proto;
120 icem->state = ICE_CHECKLIST_NULL;
121 icem->chkh = chkh;
122 icem->arg = arg;
123
124 if (err)
125 goto out;
126
127 icem->lmode = mode;
128 icem->tiebrk = tiebrk;
129
130 err |= str_dup(&icem->lufrag, lufrag);
131 err |= str_dup(&icem->lpwd, lpwd);
132 if (err)
133 goto out;
134
135 ice_determine_role(icem, role);
136
137 if (ICE_MODE_FULL == icem->lmode) {
138
139 err = stun_alloc(&icem->stun, NULL, NULL, NULL);
140 if (err)
141 goto out;
142
143 /* Update STUN Transport */
144 stun_conf(icem->stun)->rto = icem->conf.rto;
145 stun_conf(icem->stun)->rc = icem->conf.rc;
146 }
147
148 out:
149 if (err)
150 mem_deref(icem);
151 else if (icemp)
152 *icemp = icem;
153
154 return err;
155 }
156
157
158 /**
159 * Get the ICE Configuration
160 *
161 * @param icem ICE Media object
162 *
163 * @return ICE Configuration
164 */
icem_conf(struct icem * icem)165 struct ice_conf *icem_conf(struct icem *icem)
166 {
167 return icem ? &icem->conf : NULL;
168 }
169
170
icem_local_role(const struct icem * icem)171 enum ice_role icem_local_role(const struct icem *icem)
172 {
173 return icem ? icem->lrole : ICE_ROLE_UNKNOWN;
174 }
175
176
icem_set_conf(struct icem * icem,const struct ice_conf * conf)177 void icem_set_conf(struct icem *icem, const struct ice_conf *conf)
178 {
179 if (!icem || !conf)
180 return;
181
182 icem->conf = *conf;
183
184 if (icem->stun) {
185
186 /* Update STUN Transport */
187 stun_conf(icem->stun)->rto = icem->conf.rto;
188 stun_conf(icem->stun)->rc = icem->conf.rc;
189 }
190 }
191
192
193 /**
194 * Set the local role on the ICE Session
195 *
196 * @param icem ICE Media object
197 * @param role Local ICE role
198 */
icem_set_role(struct icem * icem,enum ice_role role)199 void icem_set_role(struct icem *icem, enum ice_role role)
200 {
201 if (!icem)
202 return;
203
204 ice_determine_role(icem, role);
205 }
206
207
208 /**
209 * Set the name of the ICE Media object, used for debugging
210 *
211 * @param icem ICE Media object
212 * @param name Media name
213 */
icem_set_name(struct icem * icem,const char * name)214 void icem_set_name(struct icem *icem, const char *name)
215 {
216 if (!icem)
217 return;
218
219 str_ncpy(icem->name, name, sizeof(icem->name));
220 }
221
222
223 /**
224 * Add a new component to the ICE Media object
225 *
226 * @param icem ICE Media object
227 * @param compid Component ID
228 * @param sock Application protocol socket
229 *
230 * @return 0 if success, otherwise errorcode
231 */
icem_comp_add(struct icem * icem,unsigned compid,void * sock)232 int icem_comp_add(struct icem *icem, unsigned compid, void *sock)
233 {
234 struct icem_comp *comp;
235 int err;
236
237 if (!icem)
238 return EINVAL;
239
240 if (icem_comp_find(icem, compid))
241 return EALREADY;
242
243 err = icem_comp_alloc(&comp, icem, compid, sock);
244 if (err)
245 return err;
246
247 list_append(&icem->compl, &comp->le, comp);
248
249 return 0;
250 }
251
252
253 /**
254 * Add a new candidate to the ICE Media object
255 *
256 * @param icem ICE Media object
257 * @param compid Component ID
258 * @param lprio Local priority
259 * @param ifname Name of the network interface
260 * @param addr Local network address
261 *
262 * @return 0 if success, otherwise errorcode
263 */
icem_cand_add(struct icem * icem,unsigned compid,uint16_t lprio,const char * ifname,const struct sa * addr)264 int icem_cand_add(struct icem *icem, unsigned compid, uint16_t lprio,
265 const char *ifname, const struct sa *addr)
266 {
267 if (!icem_comp_find(icem, compid))
268 return ENOENT;
269
270 return icem_lcand_add_base(icem, compid, lprio, ifname,
271 ICE_TRANSP_UDP, addr);
272 }
273
274
unique_handler(struct le * le1,struct le * le2)275 static void *unique_handler(struct le *le1, struct le *le2)
276 {
277 struct ice_cand *c1 = le1->data, *c2 = le2->data;
278
279 if (c1->base != c2->base || !sa_cmp(&c1->addr, &c2->addr, SA_ALL))
280 return NULL;
281
282 /* remove candidate with lower priority */
283 return c1->prio < c2->prio ? c1 : c2;
284 }
285
286
287 /**
288 * Eliminating Redundant Candidates
289 *
290 * @param icem ICE Media object
291 */
icem_cand_redund_elim(struct icem * icem)292 void icem_cand_redund_elim(struct icem *icem)
293 {
294 uint32_t n;
295
296 n = ice_list_unique(&icem->lcandl, unique_handler);
297 if (n > 0) {
298 icem_printf(icem, "redundant candidates eliminated: %u\n", n);
299 }
300 }
301
302
303 /**
304 * Get the Default Local Candidate
305 *
306 * @param icem ICE Media object
307 * @param compid Component ID
308 *
309 * @return Default Local Candidate address if set, otherwise NULL
310 */
icem_cand_default(struct icem * icem,unsigned compid)311 const struct sa *icem_cand_default(struct icem *icem, unsigned compid)
312 {
313 const struct icem_comp *comp = icem_comp_find(icem, compid);
314
315 if (!comp || !comp->def_lcand)
316 return NULL;
317
318 return &comp->def_lcand->addr;
319 }
320
321
322 /**
323 * Verifying ICE Support and set default remote candidate
324 *
325 * @param icem ICE Media
326 * @param compid Component ID
327 * @param raddr Address of default remote candidate
328 *
329 * @return True if ICE is supported, otherwise false
330 */
icem_verify_support(struct icem * icem,unsigned compid,const struct sa * raddr)331 bool icem_verify_support(struct icem *icem, unsigned compid,
332 const struct sa *raddr)
333 {
334 struct ice_cand *rcand;
335 bool match;
336
337 if (!icem)
338 return false;
339
340 rcand = icem_cand_find(&icem->rcandl, compid, raddr);
341 match = rcand != NULL;
342
343 if (!match)
344 icem->mismatch = true;
345
346 if (rcand) {
347 icem_comp_set_default_rcand(icem_comp_find(icem, compid),
348 rcand);
349 }
350
351 return match;
352 }
353
354
355 /**
356 * Add a TURN Channel for the selected remote address
357 *
358 * @param icem ICE Media object
359 * @param compid Component ID
360 * @param raddr Remote network address
361 *
362 * @return 0 if success, otherwise errorcode
363 */
icem_add_chan(struct icem * icem,unsigned compid,const struct sa * raddr)364 int icem_add_chan(struct icem *icem, unsigned compid, const struct sa *raddr)
365 {
366 struct icem_comp *comp;
367
368 if (!icem)
369 return EINVAL;
370
371 comp = icem_comp_find(icem, compid);
372 if (!comp)
373 return ENOENT;
374
375 if (comp->turnc) {
376 DEBUG_NOTICE("{%s.%u} Add TURN Channel to peer %J\n",
377 comp->icem->name, comp->id, raddr);
378
379 return turnc_add_chan(comp->turnc, raddr, NULL, NULL);
380 }
381
382 return 0;
383 }
384
385
purge_relayed(struct icem * icem,struct icem_comp * comp)386 static void purge_relayed(struct icem *icem, struct icem_comp *comp)
387 {
388 if (comp->turnc) {
389 DEBUG_NOTICE("{%s.%u} purge local RELAY candidates\n",
390 icem->name, comp->id);
391 }
392
393 /*
394 * Purge all Candidate-Pairs where the Local candidate
395 * is of type "Relay"
396 */
397 icem_candpairs_flush(&icem->checkl, ICE_CAND_TYPE_RELAY, comp->id);
398 icem_candpairs_flush(&icem->validl, ICE_CAND_TYPE_RELAY, comp->id);
399
400 comp->turnc = mem_deref(comp->turnc);
401 }
402
403
404 /**
405 * Update the ICE Media object
406 *
407 * @param icem ICE Media object
408 */
icem_update(struct icem * icem)409 void icem_update(struct icem *icem)
410 {
411 struct le *le;
412
413 if (!icem)
414 return;
415
416 for (le = icem->compl.head; le; le = le->next) {
417
418 struct icem_comp *comp = le->data;
419
420 /* remove TURN client if not used by local "Selected" */
421 if (comp->cp_sel) {
422
423 if (comp->cp_sel->lcand->type != ICE_CAND_TYPE_RELAY)
424 purge_relayed(icem, comp);
425 }
426 }
427 }
428
429
430 /**
431 * Get the ICE Mismatch flag of the ICE Media object
432 *
433 * @param icem ICE Media object
434 *
435 * @return True if ICE mismatch, otherwise false
436 */
icem_mismatch(const struct icem * icem)437 bool icem_mismatch(const struct icem *icem)
438 {
439 return icem ? icem->mismatch : true;
440 }
441
442
443 /**
444 * Print debug information for the ICE Media
445 *
446 * @param pf Print function for debug output
447 * @param icem ICE Media object
448 *
449 * @return 0 if success, otherwise errorcode
450 */
icem_debug(struct re_printf * pf,const struct icem * icem)451 int icem_debug(struct re_printf *pf, const struct icem *icem)
452 {
453 struct le *le;
454 int err = 0;
455
456 if (!icem)
457 return 0;
458
459 err |= re_hprintf(pf, "----- ICE Media <%s> -----\n", icem->name);
460
461 err |= re_hprintf(pf, " local_mode=%s, remote_mode=%s",
462 ice_mode2name(icem->lmode),
463 ice_mode2name(icem->rmode));
464 err |= re_hprintf(pf, ", local_role=%s\n", ice_role2name(icem->lrole));
465 err |= re_hprintf(pf, " local_ufrag=\"%s\" local_pwd=\"%s\"\n",
466 icem->lufrag, icem->lpwd);
467
468 err |= re_hprintf(pf, " Components: (%u)\n", list_count(&icem->compl));
469 for (le = icem->compl.head; le; le = le->next) {
470 struct icem_comp *comp = le->data;
471
472 err |= re_hprintf(pf, " %H\n", icecomp_debug, comp);
473 }
474
475 err |= re_hprintf(pf, " Local Candidates: %H",
476 icem_cands_debug, &icem->lcandl);
477 err |= re_hprintf(pf, " Remote Candidates: %H",
478 icem_cands_debug, &icem->rcandl);
479 err |= re_hprintf(pf, " Check list: [state=%s]%H",
480 ice_checkl_state2name(icem->state),
481 icem_candpairs_debug, &icem->checkl);
482 err |= re_hprintf(pf, " Valid list: %H",
483 icem_candpairs_debug, &icem->validl);
484
485 err |= stun_debug(pf, icem->stun);
486
487 return err;
488 }
489
490
491 /**
492 * Get the list of Local Candidates (struct cand)
493 *
494 * @param icem ICE Media object
495 *
496 * @return List of Local Candidates
497 */
icem_lcandl(const struct icem * icem)498 struct list *icem_lcandl(const struct icem *icem)
499 {
500 return icem ? (struct list *)&icem->lcandl : NULL;
501 }
502
503
504 /**
505 * Get the list of Remote Candidates (struct cand)
506 *
507 * @param icem ICE Media object
508 *
509 * @return List of Remote Candidates
510 */
icem_rcandl(const struct icem * icem)511 struct list *icem_rcandl(const struct icem *icem)
512 {
513 return icem ? (struct list *)&icem->rcandl : NULL;
514 }
515
516
517 /**
518 * Get the checklist of Candidate Pairs
519 *
520 * @param icem ICE Media object
521 *
522 * @return Checklist (struct ice_candpair)
523 */
icem_checkl(const struct icem * icem)524 struct list *icem_checkl(const struct icem *icem)
525 {
526 return icem ? (struct list *)&icem->checkl : NULL;
527 }
528
529
530 /**
531 * Get the list of valid Candidate Pairs
532 *
533 * @param icem ICE Media object
534 *
535 * @return Validlist (struct ice_candpair)
536 */
icem_validl(const struct icem * icem)537 struct list *icem_validl(const struct icem *icem)
538 {
539 return icem ? (struct list *)&icem->validl : NULL;
540 }
541
542
543 /**
544 * Set the default local candidates, for ICE-lite mode only
545 *
546 * @param icem ICE Media object
547 *
548 * @return 0 if success, otherwise errorcode
549 */
icem_lite_set_default_candidates(struct icem * icem)550 int icem_lite_set_default_candidates(struct icem *icem)
551 {
552 struct le *le;
553 int err = 0;
554
555 if (icem->lmode != ICE_MODE_LITE)
556 return EINVAL;
557
558 for (le = icem->compl.head; le; le = le->next) {
559
560 struct icem_comp *comp = le->data;
561
562 err |= icem_comp_set_default_cand(comp);
563 }
564
565 return err;
566 }
567
568
icem_comps_set_default_cand(struct icem * icem)569 int icem_comps_set_default_cand(struct icem *icem)
570 {
571 struct le *le;
572 int err = 0;
573
574 if (!icem)
575 return EINVAL;
576
577 for (le = icem->compl.head; le; le = le->next) {
578
579 struct icem_comp *comp = le->data;
580
581 err |= icem_comp_set_default_cand(comp);
582 }
583
584 return err;
585 }
586
587
icem_stun(struct icem * icem)588 struct stun *icem_stun(struct icem *icem)
589 {
590 return icem ? icem->stun : NULL;
591 }
592
593
icem_printf(struct icem * icem,const char * fmt,...)594 void icem_printf(struct icem *icem, const char *fmt, ...)
595 {
596 va_list ap;
597
598 if (!icem || !icem->conf.debug)
599 return;
600
601 va_start(ap, fmt);
602 (void)re_printf("{%11s. } %v", icem->name, fmt, &ap);
603 va_end(ap);
604 }
605