blob: d101d5a7d0dfe6a379cd3e608fe32f599670cb0a [file] [log] [blame]
James Kuszmaul82f6c042021-01-17 11:30:16 -08001/**
2 * @file connchk.c ICE Connectivity Checks
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 "connchk"
20#define DEBUG_LEVEL 5
21#include <re_dbg.h>
22
23
24static void pace_next(struct icem *icem)
25{
26 if (icem->state != ICE_CHECKLIST_RUNNING)
27 return;
28
29 icem_conncheck_schedule_check(icem);
30
31 if (icem->state == ICE_CHECKLIST_FAILED)
32 return;
33
34 icem_checklist_update(icem);
35}
36
37
38/**
39 * Constructing a Valid Pair
40 *
41 * @return The valid pair
42 */
43static struct ice_candpair *construct_valid_pair(struct icem *icem,
44 struct ice_candpair *cp,
45 const struct sa *mapped,
46 const struct sa *dest)
47{
48 struct ice_cand *lcand, *rcand;
49 struct ice_candpair *cp2;
50 int err;
51
52 lcand = icem_cand_find(&icem->lcandl, cp->lcand->compid, mapped);
53 rcand = icem_cand_find(&icem->rcandl, cp->rcand->compid, dest);
54
55 if (!lcand) {
56 DEBUG_WARNING("no such local candidate: %J\n", mapped);
57 return NULL;
58 }
59 if (!rcand) {
60 DEBUG_WARNING("no such remote candidate: %J\n", dest);
61 return NULL;
62 }
63
64 /* New candidate? -- implicit success */
65 if (lcand != cp->lcand || rcand != cp->rcand) {
66
67 if (lcand != cp->lcand) {
68 icecomp_printf(cp->comp,
69 "New local candidate for mapped %J\n",
70 mapped);
71 }
72 if (rcand != cp->rcand) {
73 icecomp_printf(cp->comp,
74 "New remote candidate for dest %J\n",
75 dest);
76 }
77
78 /* The original candidate pair is set to 'Failed' because
79 * the implicitly discovered pair is 'better'.
80 * This happens for UAs behind NAT where the original
81 * pair is of type 'host' and the implicit pair is 'srflx'
82 */
83
84 icem_candpair_make_valid(cp);
85
86 cp2 = icem_candpair_find(&icem->validl, lcand, rcand);
87 if (cp2)
88 return cp2;
89
90 err = icem_candpair_clone(&cp2, cp, lcand, rcand);
91 if (err)
92 return NULL;
93
94 icem_candpair_make_valid(cp2);
95 /*icem_candpair_failed(cp, EINTR, 0);*/
96
97 return cp2;
98 }
99 else {
100 /* Add to VALID LIST, the pair that generated the check */
101 icem_candpair_make_valid(cp);
102
103 return cp;
104 }
105}
106
107
108static void handle_success(struct icem *icem, struct ice_candpair *cp,
109 const struct sa *laddr)
110{
111 if (!icem_cand_find(&icem->lcandl, cp->lcand->compid, laddr)) {
112
113 int err;
114
115 icecomp_printf(cp->comp, "adding local PRFLX Candidate: %J\n",
116 laddr);
117
118 err = icem_lcand_add(icem, cp->lcand,
119 ICE_CAND_TYPE_PRFLX, laddr);
120 if (err) {
121 DEBUG_WARNING("failed to add PRFLX: %m\n", err);
122 }
123 }
124
125 cp = construct_valid_pair(icem, cp, laddr, &cp->rcand->addr);
126 if (!cp) {
127 DEBUG_WARNING("{%s} no valid candidate pair for %J\n",
128 icem->name, laddr);
129 return;
130 }
131
132 icem_candpair_make_valid(cp);
133 icem_comp_set_selected(cp->comp, cp);
134
135 cp->nominated = true;
136
137#if 0
138 /* stop conncheck now -- conclude */
139 icem_conncheck_stop(icem, 0);
140#endif
141}
142
143
144#if ICE_TRACE
145static int print_err(struct re_printf *pf, const int *err)
146{
147 if (err && *err)
148 return re_hprintf(pf, " (%m)", *err);
149
150 return 0;
151}
152#endif
153
154
155static void stunc_resp_handler(int err, uint16_t scode, const char *reason,
156 const struct stun_msg *msg, void *arg)
157{
158 struct ice_candpair *cp = arg;
159 struct icem *icem = cp->icem;
160 struct stun_attr *attr;
161
162 (void)reason;
163
164#if ICE_TRACE
165 icecomp_printf(cp->comp, "Rx %H <--- %H '%u %s'%H\n",
166 icem_cand_print, cp->lcand,
167 icem_cand_print, cp->rcand,
168 scode, reason, print_err, &err);
169#endif
170
171 if (err) {
172 icem_candpair_failed(cp, err, scode);
173 goto out;
174 }
175
176 switch (scode) {
177
178 case 0: /* Success case */
179 attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
180 if (!attr) {
181 DEBUG_WARNING("no XOR-MAPPED-ADDR in response\n");
182 icem_candpair_failed(cp, EBADMSG, 0);
183 break;
184 }
185
186 handle_success(icem, cp, &attr->v.sa);
187 break;
188
189 case 487: /* Role Conflict */
190 ice_switch_local_role(icem);
191 (void)icem_conncheck_send(cp, false, true);
192 break;
193
194 default:
195 DEBUG_WARNING("{%s.%u} STUN Response: %u %s\n",
196 icem->name, cp->comp->id, scode, reason);
197 icem_candpair_failed(cp, err, scode);
198 break;
199 }
200
201 out:
202 pace_next(icem);
203}
204
205
206int icem_conncheck_send(struct ice_candpair *cp, bool use_cand, bool trigged)
207{
208 struct ice_cand *lcand = cp->lcand;
209 struct icem *icem = cp->icem;
210 char username_buf[64];
211 size_t presz = 0;
212 uint32_t prio_prflx;
213 uint16_t ctrl_attr;
214 int err = 0;
215
216 icem_candpair_set_state(cp, ICE_CANDPAIR_INPROGRESS);
217
218 (void)re_snprintf(username_buf, sizeof(username_buf),
219 "%s:%s", icem->rufrag, icem->lufrag);
220
221 /* PRIORITY and USE-CANDIDATE */
222 prio_prflx = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, lcand->compid);
223
224 switch (icem->lrole) {
225
226 case ICE_ROLE_CONTROLLING:
227 ctrl_attr = STUN_ATTR_CONTROLLING;
228
229 if (icem->conf.nom == ICE_NOMINATION_AGGRESSIVE)
230 use_cand = true;
231 break;
232
233 case ICE_ROLE_CONTROLLED:
234 ctrl_attr = STUN_ATTR_CONTROLLED;
235 break;
236
237 default:
238 return EINVAL;
239 }
240
241#if ICE_TRACE
242 icecomp_printf(cp->comp, "Tx %H ---> %H (%s) %s %s\n",
243 icem_cand_print, cp->lcand, icem_cand_print, cp->rcand,
244 ice_candpair_state2name(cp->state),
245 use_cand ? "[USE]" : "",
246 trigged ? "[Trigged]" : "");
247#else
248 (void)trigged;
249#endif
250
251 /* A connectivity check MUST utilize the STUN short term credential
252 mechanism. */
253
254 /* The password is equal to the password provided by the peer */
255 if (!icem->rpwd) {
256 DEBUG_WARNING("no remote password!\n");
257 }
258
259 if (cp->ct_conn) {
260 DEBUG_WARNING("send_req: CONNCHECK already Pending!\n");
261 return EBUSY;
262 }
263
264 switch (lcand->type) {
265
266 case ICE_CAND_TYPE_RELAY:
267 /* Creating Permissions for Relayed Candidates */
268 err = turnc_add_chan(cp->comp->turnc, &cp->rcand->addr,
269 NULL, NULL);
270 if (err) {
271 DEBUG_WARNING("add channel: %m\n", err);
272 break;
273 }
274 presz = 4;
275 /*@fallthrough@*/
276
277 case ICE_CAND_TYPE_HOST:
278 case ICE_CAND_TYPE_SRFLX:
279 case ICE_CAND_TYPE_PRFLX:
280 cp->ct_conn = mem_deref(cp->ct_conn);
281 err = stun_request(&cp->ct_conn, icem->stun, icem->proto,
282 cp->comp->sock, &cp->rcand->addr, presz,
283 STUN_METHOD_BINDING,
284 (uint8_t *)icem->rpwd, str_len(icem->rpwd),
285 true, stunc_resp_handler, cp,
286 4,
287 STUN_ATTR_USERNAME, username_buf,
288 STUN_ATTR_PRIORITY, &prio_prflx,
289 ctrl_attr, &icem->tiebrk,
290 STUN_ATTR_USE_CAND,
291 use_cand ? &use_cand : 0);
292 break;
293
294 default:
295 DEBUG_WARNING("unknown candidate type %d\n", lcand->type);
296 err = EINVAL;
297 break;
298 }
299
300 return err;
301}
302
303
304static void abort_ice(struct icem *icem, int err)
305{
306 icem->state = ICE_CHECKLIST_FAILED;
307 tmr_cancel(&icem->tmr_pace);
308
309 if (icem->chkh) {
310 icem->chkh(err, icem->lrole == ICE_ROLE_CONTROLLING,
311 icem->arg);
312 }
313
314 icem->chkh = NULL;
315}
316
317
318static void do_check(struct ice_candpair *cp)
319{
320 int err;
321
322 err = icem_conncheck_send(cp, false, false);
323 if (err) {
324 icem_candpair_failed(cp, err, 0);
325
326 if (err == ENOMEM) {
327 abort_ice(cp->icem, err);
328 }
329 else {
330 pace_next(cp->icem);
331 }
332 }
333}
334
335
336/**
337 * Scheduling Checks
338 *
339 * @param icem ICE Media object
340 */
341void icem_conncheck_schedule_check(struct icem *icem)
342{
343 struct ice_candpair *cp;
344
345 /* Find the highest priority pair in that check list that is in the
346 Waiting state. */
347 cp = icem_candpair_find_st(&icem->checkl, 0, ICE_CANDPAIR_WAITING);
348 if (cp) {
349 do_check(cp);
350 return;
351 }
352
353 /* If there is no such pair: */
354
355 /* Find the highest priority pair in that check list that is in
356 the Frozen state. */
357 cp = icem_candpair_find_st(&icem->checkl, 0, ICE_CANDPAIR_FROZEN);
358 if (cp) { /* If there is such a pair: */
359
360 /* Unfreeze the pair.
361 Perform a check for that pair, causing its state to
362 transition to In-Progress. */
363 do_check(cp);
364 return;
365 }
366
367 /* If there is no such pair: */
368
369 /* Terminate the timer for that check list. */
370
371#if 0
372 icem->state = ICE_CHECKLIST_COMPLETED;
373#endif
374}
375
376
377static void pace_timeout(void *arg)
378{
379 struct icem *icem = arg;
380
381 pace_next(icem);
382}
383
384
385/**
386 * Scheduling Checks
387 *
388 * @param icem ICE Media object
389 *
390 * @return 0 if success, otherwise errorcode
391 */
392int icem_conncheck_start(struct icem *icem)
393{
394 int err;
395
396 if (!icem)
397 return EINVAL;
398
399 if (ICE_MODE_FULL != icem->lmode)
400 return EINVAL;
401
402 err = icem_checklist_form(icem);
403 if (err)
404 return err;
405
406 icem->state = ICE_CHECKLIST_RUNNING;
407
408 icem_printf(icem, "starting connectivity checks"
409 " with %u candidate pairs\n",
410 list_count(&icem->checkl));
411
412 /* add some delay, to wait for call to be 'established' */
413 tmr_start(&icem->tmr_pace, 10, pace_timeout, icem);
414
415 return 0;
416}
417
418
419void icem_conncheck_continue(struct icem *icem)
420{
421 if (!tmr_isrunning(&icem->tmr_pace))
422 tmr_start(&icem->tmr_pace, 1, pace_timeout, icem);
423}
424
425
426/**
427 * Stop checklist, cancel all connectivity checks
428 *
429 * @param icem ICE Media object
430 * @param err Error code
431 */
432void icem_conncheck_stop(struct icem *icem, int err)
433{
434 struct le *le;
435
436 icem->state = err ? ICE_CHECKLIST_FAILED : ICE_CHECKLIST_COMPLETED;
437
438 tmr_cancel(&icem->tmr_pace);
439
440 for (le = icem->checkl.head; le; le = le->next) {
441 struct ice_candpair *cp = le->data;
442
443 if (!icem_candpair_iscompleted(cp)) {
444 icem_candpair_cancel(cp);
445 icem_candpair_failed(cp, EINTR, 0);
446 }
447 }
448
449 icem_checklist_update(icem);
450}