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