blob: 06eef5168b0ad124f8e1018291eb88595d3dc034 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjmedia/transport_ice.h>
21#include <pjnath/errno.h>
22#include <pj/assert.h>
23#include <pj/log.h>
24#include <pj/pool.h>
25#include <pj/rand.h>
26
27#define THIS_FILE "transport_ice.c"
28#if 0
29# define TRACE__(expr) PJ_LOG(5,expr)
30#else
31# define TRACE__(expr)
32#endif
33
34enum oa_role
35{
36 ROLE_NONE,
37 ROLE_OFFERER,
38 ROLE_ANSWERER
39};
40
41struct sdp_state
42{
43 unsigned match_comp_cnt; /* Matching number of components */
44 pj_bool_t ice_mismatch; /* Address doesn't match candidates */
45 pj_bool_t ice_restart; /* Offer to restart ICE */
46 pj_ice_sess_role local_role; /* Our role */
47};
48
49struct transport_ice
50{
51 pjmedia_transport base;
52 pj_pool_t *pool;
53 int af;
54 unsigned options; /**< Transport options. */
55
56 unsigned comp_cnt;
57 pj_ice_strans *ice_st;
58
59 pjmedia_ice_cb cb;
60 unsigned media_option;
61
62 pj_bool_t initial_sdp;
63 enum oa_role oa_role; /**< Last role in SDP offer/answer */
64 struct sdp_state rem_offer_state;/**< Describes the remote offer */
65
66 void *stream;
67 pj_sockaddr remote_rtp;
68 pj_sockaddr remote_rtcp;
69 unsigned addr_len; /**< Length of addresses. */
70
71 pj_bool_t use_ice;
72 pj_sockaddr rtp_src_addr; /**< Actual source RTP address. */
73 pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */
74 unsigned rtp_src_cnt; /**< How many pkt from this addr. */
75 unsigned rtcp_src_cnt; /**< How many pkt from this addr. */
76
77 unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */
78 unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */
79
80 void (*rtp_cb)(void*,
81 void*,
82 pj_ssize_t);
83 void (*rtcp_cb)(void*,
84 void*,
85 pj_ssize_t);
86};
87
88
89/*
90 * These are media transport operations.
91 */
92static pj_status_t transport_get_info (pjmedia_transport *tp,
93 pjmedia_transport_info *info);
94static pj_status_t transport_attach (pjmedia_transport *tp,
95 void *user_data,
96 const pj_sockaddr_t *rem_addr,
97 const pj_sockaddr_t *rem_rtcp,
98 unsigned addr_len,
99 void (*rtp_cb)(void*,
100 void*,
101 pj_ssize_t),
102 void (*rtcp_cb)(void*,
103 void*,
104 pj_ssize_t));
105static void transport_detach (pjmedia_transport *tp,
106 void *strm);
107static pj_status_t transport_send_rtp( pjmedia_transport *tp,
108 const void *pkt,
109 pj_size_t size);
110static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
111 const void *pkt,
112 pj_size_t size);
113static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
114 const pj_sockaddr_t *addr,
115 unsigned addr_len,
116 const void *pkt,
117 pj_size_t size);
118static pj_status_t transport_media_create(pjmedia_transport *tp,
119 pj_pool_t *pool,
120 unsigned options,
121 const pjmedia_sdp_session *rem_sdp,
122 unsigned media_index);
123static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
124 pj_pool_t *tmp_pool,
125 pjmedia_sdp_session *sdp_local,
126 const pjmedia_sdp_session *rem_sdp,
127 unsigned media_index);
128static pj_status_t transport_media_start(pjmedia_transport *tp,
129 pj_pool_t *pool,
130 const pjmedia_sdp_session *sdp_local,
131 const pjmedia_sdp_session *rem_sdp,
132 unsigned media_index);
133static pj_status_t transport_media_stop(pjmedia_transport *tp);
134static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
135 pjmedia_dir dir,
136 unsigned pct_lost);
137static pj_status_t transport_destroy (pjmedia_transport *tp);
138
139/*
140 * And these are ICE callbacks.
141 */
142static void ice_on_rx_data(pj_ice_strans *ice_st,
143 unsigned comp_id,
144 void *pkt, pj_size_t size,
145 const pj_sockaddr_t *src_addr,
146 unsigned src_addr_len);
147static void ice_on_ice_complete(pj_ice_strans *ice_st,
148 pj_ice_strans_op op,
149 pj_status_t status);
150
151
152static pjmedia_transport_op transport_ice_op =
153{
154 &transport_get_info,
155 &transport_attach,
156 &transport_detach,
157 &transport_send_rtp,
158 &transport_send_rtcp,
159 &transport_send_rtcp2,
160 &transport_media_create,
161 &transport_encode_sdp,
162 &transport_media_start,
163 &transport_media_stop,
164 &transport_simulate_lost,
165 &transport_destroy
166};
167
168static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
169static const pj_str_t STR_CANDIDATE = { "candidate", 9};
170static const pj_str_t STR_REM_CAND = { "remote-candidates", 17 };
171static const pj_str_t STR_ICE_LITE = { "ice-lite", 8};
172static const pj_str_t STR_ICE_MISMATCH = { "ice-mismatch", 12};
173static const pj_str_t STR_ICE_UFRAG = { "ice-ufrag", 9 };
174static const pj_str_t STR_ICE_PWD = { "ice-pwd", 7 };
175static const pj_str_t STR_IP4 = { "IP4", 3 };
176static const pj_str_t STR_IP6 = { "IP6", 3 };
177static const pj_str_t STR_RTCP = { "rtcp", 4 };
178static const pj_str_t STR_BANDW_RR = { "RR", 2 };
179static const pj_str_t STR_BANDW_RS = { "RS", 2 };
180
181enum {
182 COMP_RTP = 1,
183 COMP_RTCP = 2
184};
185
186/*
187 * Create ICE media transport.
188 */
189PJ_DEF(pj_status_t) pjmedia_ice_create(pjmedia_endpt *endpt,
190 const char *name,
191 unsigned comp_cnt,
192 const pj_ice_strans_cfg *cfg,
193 const pjmedia_ice_cb *cb,
194 pjmedia_transport **p_tp)
195{
196 return pjmedia_ice_create2(endpt, name, comp_cnt, cfg, cb, 0, p_tp);
197}
198
199/*
200 * Create ICE media transport.
201 */
202PJ_DEF(pj_status_t) pjmedia_ice_create2(pjmedia_endpt *endpt,
203 const char *name,
204 unsigned comp_cnt,
205 const pj_ice_strans_cfg *cfg,
206 const pjmedia_ice_cb *cb,
207 unsigned options,
208 pjmedia_transport **p_tp)
209{
210 return pjmedia_ice_create3(endpt, name, comp_cnt, cfg, cb,
211 options, NULL, p_tp);
212}
213
214/*
215 * Create ICE media transport.
216 */
217PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt,
218 const char *name,
219 unsigned comp_cnt,
220 const pj_ice_strans_cfg *cfg,
221 const pjmedia_ice_cb *cb,
222 unsigned options,
223 void *user_data,
224 pjmedia_transport **p_tp)
225{
226 pj_pool_t *pool;
227 pj_ice_strans_cb ice_st_cb;
228 pj_ice_strans_cfg ice_st_cfg;
229 struct transport_ice *tp_ice;
230 pj_status_t status;
231
232 PJ_ASSERT_RETURN(endpt && comp_cnt && cfg && p_tp, PJ_EINVAL);
233
234 /* Create transport instance */
235 pool = pjmedia_endpt_create_pool(endpt, name, 512, 512);
236 tp_ice = PJ_POOL_ZALLOC_T(pool, struct transport_ice);
237 tp_ice->pool = pool;
238 tp_ice->af = cfg->af;
239 tp_ice->options = options;
240 tp_ice->comp_cnt = comp_cnt;
241 pj_ansi_strcpy(tp_ice->base.name, pool->obj_name);
242 tp_ice->base.op = &transport_ice_op;
243 tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE;
244 tp_ice->base.user_data = user_data;
245 tp_ice->initial_sdp = PJ_TRUE;
246 tp_ice->oa_role = ROLE_NONE;
247 tp_ice->use_ice = PJ_FALSE;
248
249 pj_memcpy(&ice_st_cfg, cfg, sizeof(pj_ice_strans_cfg));
250 if (cb)
251 pj_memcpy(&tp_ice->cb, cb, sizeof(pjmedia_ice_cb));
252
253 /* Assign return value first because ICE might call callback
254 * in create()
255 */
256 *p_tp = &tp_ice->base;
257
258 /* Configure ICE callbacks */
259 pj_bzero(&ice_st_cb, sizeof(ice_st_cb));
260 ice_st_cb.on_ice_complete = &ice_on_ice_complete;
261 ice_st_cb.on_rx_data = &ice_on_rx_data;
262
263 /* Configure RTP socket buffer settings, if not set */
264 if (ice_st_cfg.comp[COMP_RTP-1].so_rcvbuf_size == 0) {
265 ice_st_cfg.comp[COMP_RTP-1].so_rcvbuf_size =
266 PJMEDIA_TRANSPORT_SO_RCVBUF_SIZE;
267 }
268 if (ice_st_cfg.comp[COMP_RTP-1].so_sndbuf_size == 0) {
269 ice_st_cfg.comp[COMP_RTP-1].so_sndbuf_size =
270 PJMEDIA_TRANSPORT_SO_SNDBUF_SIZE;
271 }
272
273 /* Create ICE */
274 status = pj_ice_strans_create(name, &ice_st_cfg, comp_cnt, tp_ice,
275 &ice_st_cb, &tp_ice->ice_st);
276 if (status != PJ_SUCCESS) {
277 pj_pool_release(pool);
278 *p_tp = NULL;
279 return status;
280 }
281
282 /* Done */
283 return PJ_SUCCESS;
284}
285
286/* Disable ICE when SDP from remote doesn't contain a=candidate line */
287static void set_no_ice(struct transport_ice *tp_ice, const char *reason,
288 pj_status_t err)
289{
290 if (err != PJ_SUCCESS) {
291 char errmsg[PJ_ERR_MSG_SIZE];
292 pj_strerror(err, errmsg, sizeof(errmsg));
293 PJ_LOG(4,(tp_ice->base.name,
294 "Stopping ICE, reason=%s:%s", reason, errmsg));
295 } else {
296 PJ_LOG(4,(tp_ice->base.name,
297 "Stopping ICE, reason=%s", reason));
298 }
299
300 if (tp_ice->ice_st) {
301 pj_ice_strans_stop_ice(tp_ice->ice_st);
302 }
303
304 tp_ice->use_ice = PJ_FALSE;
305}
306
307
308/* Create SDP candidate attribute */
309static int print_sdp_cand_attr(char *buffer, int max_len,
310 const pj_ice_sess_cand *cand)
311{
312 char ipaddr[PJ_INET6_ADDRSTRLEN+2];
313 int len, len2;
314
315 len = pj_ansi_snprintf( buffer, max_len,
316 "%.*s %u UDP %u %s %u typ ",
317 (int)cand->foundation.slen,
318 cand->foundation.ptr,
319 (unsigned)cand->comp_id,
320 cand->prio,
321 pj_sockaddr_print(&cand->addr, ipaddr,
322 sizeof(ipaddr), 0),
323 (unsigned)pj_sockaddr_get_port(&cand->addr));
324 if (len < 1 || len >= max_len)
325 return -1;
326
327 switch (cand->type) {
328 case PJ_ICE_CAND_TYPE_HOST:
329 len2 = pj_ansi_snprintf(buffer+len, max_len-len, "host");
330 break;
331 case PJ_ICE_CAND_TYPE_SRFLX:
332 case PJ_ICE_CAND_TYPE_RELAYED:
333 case PJ_ICE_CAND_TYPE_PRFLX:
334 len2 = pj_ansi_snprintf(buffer+len, max_len-len,
335 "%s raddr %s rport %d",
336 pj_ice_get_cand_type_name(cand->type),
337 pj_sockaddr_print(&cand->rel_addr, ipaddr,
338 sizeof(ipaddr), 0),
339 (int)pj_sockaddr_get_port(&cand->rel_addr));
340 break;
341 default:
342 pj_assert(!"Invalid candidate type");
343 len2 = -1;
344 break;
345 }
346 if (len2 < 1 || len2 >= max_len)
347 return -1;
348
349 return len+len2;
350}
351
352
353/* Get ice-ufrag and ice-pwd attribute */
354static void get_ice_attr(const pjmedia_sdp_session *rem_sdp,
355 const pjmedia_sdp_media *rem_m,
356 const pjmedia_sdp_attr **p_ice_ufrag,
357 const pjmedia_sdp_attr **p_ice_pwd)
358{
359 pjmedia_sdp_attr *attr;
360
361 /* Find ice-ufrag attribute in media descriptor */
362 attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
363 &STR_ICE_UFRAG, NULL);
364 if (attr == NULL) {
365 /* Find ice-ufrag attribute in session descriptor */
366 attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr,
367 &STR_ICE_UFRAG, NULL);
368 }
369 *p_ice_ufrag = attr;
370
371 /* Find ice-pwd attribute in media descriptor */
372 attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
373 &STR_ICE_PWD, NULL);
374 if (attr == NULL) {
375 /* Find ice-pwd attribute in session descriptor */
376 attr = pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr,
377 &STR_ICE_PWD, NULL);
378 }
379 *p_ice_pwd = attr;
380}
381
382
383/* Encode and add "a=ice-mismatch" attribute in the SDP */
384static void encode_ice_mismatch(pj_pool_t *sdp_pool,
385 pjmedia_sdp_session *sdp_local,
386 unsigned media_index)
387{
388 pjmedia_sdp_attr *attr;
389 pjmedia_sdp_media *m = sdp_local->media[media_index];
390
391 attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr);
392 attr->name = STR_ICE_MISMATCH;
393 attr->value.slen = 0;
394 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
395}
396
397
398/* Encode ICE information in SDP */
399static pj_status_t encode_session_in_sdp(struct transport_ice *tp_ice,
400 pj_pool_t *sdp_pool,
401 pjmedia_sdp_session *sdp_local,
402 unsigned media_index,
403 unsigned comp_cnt,
404 pj_bool_t restart_session)
405{
406 enum {
407 ATTR_BUF_LEN = 160, /* Max len of a=candidate attr */
408 RATTR_BUF_LEN= 160 /* Max len of a=remote-candidates attr */
409 };
410 pjmedia_sdp_media *m = sdp_local->media[media_index];
411 pj_str_t local_ufrag, local_pwd;
412 pjmedia_sdp_attr *attr;
413 pj_status_t status;
414
415 /* Must have a session */
416 PJ_ASSERT_RETURN(pj_ice_strans_has_sess(tp_ice->ice_st), PJ_EBUG);
417
418 /* Get ufrag and pwd from current session */
419 pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, &local_ufrag, &local_pwd,
420 NULL, NULL);
421
422 /* The listing of candidates depends on whether ICE has completed
423 * or not. When ICE has completed:
424 *
425 * 9.1.2.2: Existing Media Streams with ICE Completed
426 * The agent MUST include a candidate attributes for candidates
427 * matching the default destination for each component of the
428 * media stream, and MUST NOT include any other candidates.
429 *
430 * When ICE has not completed, we shall include all candidates.
431 *
432 * Except when we have detected that remote is offering to restart
433 * the session, in this case we will answer with full ICE SDP and
434 * new ufrag/pwd pair.
435 */
436 if (!restart_session && pj_ice_strans_sess_is_complete(tp_ice->ice_st) &&
437 pj_ice_strans_get_state(tp_ice->ice_st) != PJ_ICE_STRANS_STATE_FAILED)
438 {
439 const pj_ice_sess_check *check;
440 char *attr_buf;
441 pjmedia_sdp_conn *conn;
442 pjmedia_sdp_attr *a_rtcp;
443 pj_str_t rem_cand;
444 unsigned comp;
445
446 /* Encode ice-ufrag attribute */
447 attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr,
448 &local_ufrag);
449 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
450
451 /* Encode ice-pwd attribute */
452 attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr,
453 &local_pwd);
454 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
455
456 /* Prepare buffer */
457 attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN);
458 rem_cand.ptr = (char*) pj_pool_alloc(sdp_pool, RATTR_BUF_LEN);
459 rem_cand.slen = 0;
460
461 /* 9.1.2.2: Existing Media Streams with ICE Completed
462 * The default destination for media (i.e., the values of
463 * the IP addresses and ports in the m and c line used for
464 * that media stream) MUST be the local candidate from the
465 * highest priority nominated pair in the valid list for each
466 * component.
467 */
468 check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, 1);
469 if (check == NULL) {
470 pj_assert(!"Shouldn't happen");
471 return PJ_EBUG;
472 }
473
474 /* Override connection line address and media port number */
475 conn = m->conn;
476 if (conn == NULL)
477 conn = sdp_local->conn;
478
479 conn->addr.ptr = (char*) pj_pool_alloc(sdp_pool,
480 PJ_INET6_ADDRSTRLEN);
481 pj_sockaddr_print(&check->lcand->addr, conn->addr.ptr,
482 PJ_INET6_ADDRSTRLEN, 0);
483 conn->addr.slen = pj_ansi_strlen(conn->addr.ptr);
484 m->desc.port = pj_sockaddr_get_port(&check->lcand->addr);
485
486 /* Override address RTCP attribute if it's present */
487 if (comp_cnt == 2 &&
488 (check = pj_ice_strans_get_valid_pair(tp_ice->ice_st,
489 COMP_RTCP)) != NULL &&
490 (a_rtcp = pjmedia_sdp_attr_find(m->attr_count, m->attr,
491 &STR_RTCP, 0)) != NULL)
492 {
493 pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a_rtcp);
494
495 a_rtcp = pjmedia_sdp_attr_create_rtcp(sdp_pool,
496 &check->lcand->addr);
497 if (a_rtcp)
498 pjmedia_sdp_attr_add(&m->attr_count, m->attr, a_rtcp);
499 }
500
501 /* Encode only candidates matching the default destination
502 * for each component
503 */
504 for (comp=0; comp < comp_cnt; ++comp) {
505 int len;
506 pj_str_t value;
507
508 /* Get valid pair for this component */
509 check = pj_ice_strans_get_valid_pair(tp_ice->ice_st, comp+1);
510 if (check == NULL)
511 continue;
512
513 /* Print and add local candidate in the pair */
514 value.ptr = attr_buf;
515 value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN,
516 check->lcand);
517 if (value.slen < 0) {
518 pj_assert(!"Not enough attr_buf to print candidate");
519 return PJ_EBUG;
520 }
521
522 attr = pjmedia_sdp_attr_create(sdp_pool, STR_CANDIDATE.ptr,
523 &value);
524 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
525
526 /* Append to a=remote-candidates attribute */
527 if (pj_ice_strans_get_role(tp_ice->ice_st) ==
528 PJ_ICE_SESS_ROLE_CONTROLLING)
529 {
530 char rem_addr[PJ_INET6_ADDRSTRLEN];
531
532 pj_sockaddr_print(&check->rcand->addr, rem_addr,
533 sizeof(rem_addr), 0);
534 len = pj_ansi_snprintf(
535 rem_cand.ptr + rem_cand.slen,
536 RATTR_BUF_LEN - rem_cand.slen,
537 "%s%u %s %u",
538 (rem_cand.slen==0? "" : " "),
539 comp+1, rem_addr,
540 pj_sockaddr_get_port(&check->rcand->addr)
541 );
542 if (len < 1 || len >= RATTR_BUF_LEN) {
543 pj_assert(!"Not enough buffer to print "
544 "remote-candidates");
545 return PJ_EBUG;
546 }
547
548 rem_cand.slen += len;
549 }
550 }
551
552 /* 9.1.2.2: Existing Media Streams with ICE Completed
553 * In addition, if the agent is controlling, it MUST include
554 * the a=remote-candidates attribute for each media stream
555 * whose check list is in the Completed state. The attribute
556 * contains the remote candidates from the highest priority
557 * nominated pair in the valid list for each component of that
558 * media stream.
559 */
560 if (pj_ice_strans_get_role(tp_ice->ice_st) ==
561 PJ_ICE_SESS_ROLE_CONTROLLING)
562 {
563 attr = pjmedia_sdp_attr_create(sdp_pool, STR_REM_CAND.ptr,
564 &rem_cand);
565 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
566 }
567
568 } else if (pj_ice_strans_has_sess(tp_ice->ice_st) &&
569 pj_ice_strans_get_state(tp_ice->ice_st) !=
570 PJ_ICE_STRANS_STATE_FAILED)
571 {
572 /* Encode all candidates to SDP media */
573 char *attr_buf;
574 unsigned comp;
575
576 /* If ICE is not restarted, encode current ICE ufrag/pwd.
577 * Otherwise generate new one.
578 */
579 if (!restart_session) {
580 attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr,
581 &local_ufrag);
582 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
583
584 attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr,
585 &local_pwd);
586 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
587
588 } else {
589 pj_str_t str;
590
591 str.slen = PJ_ICE_UFRAG_LEN;
592 str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen);
593 pj_create_random_string(str.ptr, str.slen);
594 attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_UFRAG.ptr, &str);
595 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
596
597 str.ptr = (char*) pj_pool_alloc(sdp_pool, str.slen);
598 pj_create_random_string(str.ptr, str.slen);
599 attr = pjmedia_sdp_attr_create(sdp_pool, STR_ICE_PWD.ptr, &str);
600 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
601 }
602
603 /* Create buffer to encode candidates as SDP attribute */
604 attr_buf = (char*) pj_pool_alloc(sdp_pool, ATTR_BUF_LEN);
605
606 for (comp=0; comp < comp_cnt; ++comp) {
607 unsigned cand_cnt;
608 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
609 unsigned i;
610
611 cand_cnt = PJ_ARRAY_SIZE(cand);
612 status = pj_ice_strans_enum_cands(tp_ice->ice_st, comp+1,
613 &cand_cnt, cand);
614 if (status != PJ_SUCCESS)
615 return status;
616
617 for (i=0; i<cand_cnt; ++i) {
618 pj_str_t value;
619
620 value.slen = print_sdp_cand_attr(attr_buf, ATTR_BUF_LEN,
621 &cand[i]);
622 if (value.slen < 0) {
623 pj_assert(!"Not enough attr_buf to print candidate");
624 return PJ_EBUG;
625 }
626
627 value.ptr = attr_buf;
628 attr = pjmedia_sdp_attr_create(sdp_pool,
629 STR_CANDIDATE.ptr,
630 &value);
631 pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr);
632 }
633 }
634 } else {
635 /* ICE has failed, application should have terminated this call */
636 }
637
638 /* Removing a=rtcp line when there is only one component. */
639 if (comp_cnt == 1) {
640 attr = pjmedia_sdp_attr_find(m->attr_count, m->attr, &STR_RTCP, NULL);
641 if (attr)
642 pjmedia_sdp_attr_remove(&m->attr_count, m->attr, attr);
643 /* If RTCP is not in use, we MUST send b=RS:0 and b=RR:0. */
644 pj_assert(m->bandw_count + 2 <= PJ_ARRAY_SIZE(m->bandw));
645 if (m->bandw_count + 2 <= PJ_ARRAY_SIZE(m->bandw)) {
646 m->bandw[m->bandw_count] = PJ_POOL_ZALLOC_T(sdp_pool,
647 pjmedia_sdp_bandw);
648 pj_memcpy(&m->bandw[m->bandw_count]->modifier, &STR_BANDW_RS,
649 sizeof(pj_str_t));
650 m->bandw_count++;
651 m->bandw[m->bandw_count] = PJ_POOL_ZALLOC_T(sdp_pool,
652 pjmedia_sdp_bandw);
653 pj_memcpy(&m->bandw[m->bandw_count]->modifier, &STR_BANDW_RR,
654 sizeof(pj_str_t));
655 m->bandw_count++;
656 }
657 }
658
659
660 return PJ_SUCCESS;
661}
662
663
664/* Parse a=candidate line */
665static pj_status_t parse_cand(const char *obj_name,
666 pj_pool_t *pool,
667 const pj_str_t *orig_input,
668 pj_ice_sess_cand *cand)
669{
670 pj_str_t input;
671 char *token, *host;
672 int af;
673 pj_str_t s;
674 pj_status_t status = PJNATH_EICEINCANDSDP;
675
676 pj_bzero(cand, sizeof(*cand));
677 pj_strdup_with_null(pool, &input, orig_input);
678
679 PJ_UNUSED_ARG(obj_name);
680
681 /* Foundation */
682 token = strtok(input.ptr, " ");
683 if (!token) {
684 TRACE__((obj_name, "Expecting ICE foundation in candidate"));
685 goto on_return;
686 }
687 pj_strdup2(pool, &cand->foundation, token);
688
689 /* Component ID */
690 token = strtok(NULL, " ");
691 if (!token) {
692 TRACE__((obj_name, "Expecting ICE component ID in candidate"));
693 goto on_return;
694 }
695 cand->comp_id = (pj_uint8_t) atoi(token);
696
697 /* Transport */
698 token = strtok(NULL, " ");
699 if (!token) {
700 TRACE__((obj_name, "Expecting ICE transport in candidate"));
701 goto on_return;
702 }
703 if (pj_ansi_stricmp(token, "UDP") != 0) {
704 TRACE__((obj_name,
705 "Expecting ICE UDP transport only in candidate"));
706 goto on_return;
707 }
708
709 /* Priority */
710 token = strtok(NULL, " ");
711 if (!token) {
712 TRACE__((obj_name, "Expecting ICE priority in candidate"));
713 goto on_return;
714 }
715 cand->prio = atoi(token);
716
717 /* Host */
718 host = strtok(NULL, " ");
719 if (!host) {
720 TRACE__((obj_name, "Expecting ICE host in candidate"));
721 goto on_return;
722 }
723 /* Detect address family */
724 if (pj_ansi_strchr(host, ':'))
725 af = pj_AF_INET6();
726 else
727 af = pj_AF_INET();
728 /* Assign address */
729 if (pj_sockaddr_init(af, &cand->addr, pj_cstr(&s, host), 0)) {
730 TRACE__((obj_name, "Invalid ICE candidate address"));
731 goto on_return;
732 }
733
734 /* Port */
735 token = strtok(NULL, " ");
736 if (!token) {
737 TRACE__((obj_name, "Expecting ICE port number in candidate"));
738 goto on_return;
739 }
740 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)atoi(token));
741
742 /* typ */
743 token = strtok(NULL, " ");
744 if (!token) {
745 TRACE__((obj_name, "Expecting ICE \"typ\" in candidate"));
746 goto on_return;
747 }
748 if (pj_ansi_stricmp(token, "typ") != 0) {
749 TRACE__((obj_name, "Expecting ICE \"typ\" in candidate"));
750 goto on_return;
751 }
752
753 /* candidate type */
754 token = strtok(NULL, " ");
755 if (!token) {
756 TRACE__((obj_name, "Expecting ICE candidate type in candidate"));
757 goto on_return;
758 }
759
760 if (pj_ansi_stricmp(token, "host") == 0) {
761 cand->type = PJ_ICE_CAND_TYPE_HOST;
762
763 } else if (pj_ansi_stricmp(token, "srflx") == 0) {
764 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
765
766 } else if (pj_ansi_stricmp(token, "relay") == 0) {
767 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
768
769 } else if (pj_ansi_stricmp(token, "prflx") == 0) {
770 cand->type = PJ_ICE_CAND_TYPE_PRFLX;
771
772 } else {
773 PJ_LOG(5,(obj_name, "Invalid ICE candidate type %s in candidate",
774 token));
775 goto on_return;
776 }
777
778 status = PJ_SUCCESS;
779
780on_return:
781 return status;
782}
783
784
785/* Create initial SDP offer */
786static pj_status_t create_initial_offer(struct transport_ice *tp_ice,
787 pj_pool_t *sdp_pool,
788 pjmedia_sdp_session *loc_sdp,
789 unsigned media_index)
790{
791 pj_status_t status;
792
793 /* Encode ICE in SDP */
794 status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
795 tp_ice->comp_cnt, PJ_FALSE);
796 if (status != PJ_SUCCESS) {
797 set_no_ice(tp_ice, "Error encoding SDP answer", status);
798 return status;
799 }
800
801 return PJ_SUCCESS;
802}
803
804
805/* Verify incoming offer */
806static pj_status_t verify_ice_sdp(struct transport_ice *tp_ice,
807 pj_pool_t *tmp_pool,
808 const pjmedia_sdp_session *rem_sdp,
809 unsigned media_index,
810 pj_ice_sess_role current_ice_role,
811 struct sdp_state *sdp_state)
812{
813 const pjmedia_sdp_media *rem_m;
814 const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
815 const pjmedia_sdp_conn *rem_conn;
816 pj_bool_t comp1_found=PJ_FALSE, comp2_found=PJ_FALSE, has_rtcp=PJ_FALSE;
817 pj_sockaddr rem_conn_addr, rtcp_addr;
818 unsigned i;
819 pj_status_t status;
820
821 rem_m = rem_sdp->media[media_index];
822
823 /* Get the "ice-ufrag" and "ice-pwd" attributes */
824 get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr);
825
826 /* If "ice-ufrag" or "ice-pwd" are not found, disable ICE */
827 if (ufrag_attr==NULL || pwd_attr==NULL) {
828 sdp_state->match_comp_cnt = 0;
829 return PJ_SUCCESS;
830 }
831
832 /* Verify that default target for each component matches one of the
833 * candidate for the component. Otherwise stop ICE with ICE ice_mismatch
834 * error.
835 */
836
837 /* Component 1 is the c= line */
838 rem_conn = rem_m->conn;
839 if (rem_conn == NULL)
840 rem_conn = rem_sdp->conn;
841 if (!rem_conn)
842 return PJMEDIA_SDP_EMISSINGCONN;
843
844 /* Verify address family matches */
845 if ((tp_ice->af==pj_AF_INET() &&
846 pj_strcmp(&rem_conn->addr_type, &STR_IP4)!=0) ||
847 (tp_ice->af==pj_AF_INET6() &&
848 pj_strcmp(&rem_conn->addr_type, &STR_IP6)!=0))
849 {
850 return PJMEDIA_SDP_ETPORTNOTEQUAL;
851 }
852
853 /* Assign remote connection address */
854 status = pj_sockaddr_init(tp_ice->af, &rem_conn_addr, &rem_conn->addr,
855 (pj_uint16_t)rem_m->desc.port);
856 if (status != PJ_SUCCESS)
857 return status;
858
859 if (tp_ice->comp_cnt > 1) {
860 const pjmedia_sdp_attr *attr;
861
862 /* Get default RTCP candidate from a=rtcp line, if present, otherwise
863 * calculate default RTCP candidate from default RTP target.
864 */
865 attr = pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
866 &STR_RTCP, NULL);
867 has_rtcp = (attr != NULL);
868
869 if (attr) {
870 pjmedia_sdp_rtcp_attr rtcp_attr;
871
872 status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp_attr);
873 if (status != PJ_SUCCESS) {
874 /* Error parsing a=rtcp attribute */
875 return status;
876 }
877
878 if (rtcp_attr.addr.slen) {
879 /* Verify address family matches */
880 if ((tp_ice->af==pj_AF_INET() &&
881 pj_strcmp(&rtcp_attr.addr_type, &STR_IP4)!=0) ||
882 (tp_ice->af==pj_AF_INET6() &&
883 pj_strcmp(&rtcp_attr.addr_type, &STR_IP6)!=0))
884 {
885 return PJMEDIA_SDP_ETPORTNOTEQUAL;
886 }
887
888 /* Assign RTCP address */
889 status = pj_sockaddr_init(tp_ice->af, &rtcp_addr,
890 &rtcp_attr.addr,
891 (pj_uint16_t)rtcp_attr.port);
892 if (status != PJ_SUCCESS) {
893 return PJMEDIA_SDP_EINRTCP;
894 }
895 } else {
896 /* Assign RTCP address */
897 status = pj_sockaddr_init(tp_ice->af, &rtcp_addr,
898 NULL,
899 (pj_uint16_t)rtcp_attr.port);
900 if (status != PJ_SUCCESS) {
901 return PJMEDIA_SDP_EINRTCP;
902 }
903 pj_sockaddr_copy_addr(&rtcp_addr, &rem_conn_addr);
904 }
905 } else {
906 unsigned rtcp_port;
907
908 rtcp_port = pj_sockaddr_get_port(&rem_conn_addr) + 1;
909 pj_sockaddr_cp(&rtcp_addr, &rem_conn_addr);
910 pj_sockaddr_set_port(&rtcp_addr, (pj_uint16_t)rtcp_port);
911 }
912 }
913
914 /* Find the default addresses in a=candidate attributes.
915 */
916 for (i=0; i<rem_m->attr_count; ++i) {
917 pj_ice_sess_cand cand;
918
919 if (pj_strcmp(&rem_m->attr[i]->name, &STR_CANDIDATE)!=0)
920 continue;
921
922 status = parse_cand(tp_ice->base.name, tmp_pool,
923 &rem_m->attr[i]->value, &cand);
924 if (status != PJ_SUCCESS) {
925 PJ_LOG(4,(tp_ice->base.name,
926 "Error in parsing SDP candidate attribute '%.*s', "
927 "candidate is ignored",
928 (int)rem_m->attr[i]->value.slen,
929 rem_m->attr[i]->value.ptr));
930 continue;
931 }
932
933 if (!comp1_found && cand.comp_id==COMP_RTP &&
934 pj_sockaddr_cmp(&rem_conn_addr, &cand.addr)==0)
935 {
936 comp1_found = PJ_TRUE;
937 } else if (!comp2_found && cand.comp_id==COMP_RTCP &&
938 pj_sockaddr_cmp(&rtcp_addr, &cand.addr)==0)
939 {
940 comp2_found = PJ_TRUE;
941 }
942
943 if (cand.comp_id == COMP_RTCP)
944 has_rtcp = PJ_TRUE;
945
946 if (comp1_found && (comp2_found || tp_ice->comp_cnt==1))
947 break;
948 }
949
950 /* Check matched component count and ice_mismatch */
951 if (comp1_found && (tp_ice->comp_cnt==1 || !has_rtcp)) {
952 sdp_state->match_comp_cnt = 1;
953 sdp_state->ice_mismatch = PJ_FALSE;
954 } else if (comp1_found && comp2_found) {
955 sdp_state->match_comp_cnt = 2;
956 sdp_state->ice_mismatch = PJ_FALSE;
957 } else {
958 sdp_state->match_comp_cnt = (tp_ice->comp_cnt > 1 && has_rtcp)? 2 : 1;
959 sdp_state->ice_mismatch = PJ_TRUE;
960 }
961
962
963 /* Detect remote restarting session */
964 if (pj_ice_strans_has_sess(tp_ice->ice_st) &&
965 (pj_ice_strans_sess_is_running(tp_ice->ice_st) ||
966 pj_ice_strans_sess_is_complete(tp_ice->ice_st)))
967 {
968 pj_str_t rem_run_ufrag, rem_run_pwd;
969 pj_ice_strans_get_ufrag_pwd(tp_ice->ice_st, NULL, NULL,
970 &rem_run_ufrag, &rem_run_pwd);
971 if (pj_strcmp(&ufrag_attr->value, &rem_run_ufrag) ||
972 pj_strcmp(&pwd_attr->value, &rem_run_pwd))
973 {
974 /* Remote offers to restart ICE */
975 sdp_state->ice_restart = PJ_TRUE;
976 } else {
977 sdp_state->ice_restart = PJ_FALSE;
978 }
979 } else {
980 sdp_state->ice_restart = PJ_FALSE;
981 }
982
983 /* Detect our role */
984 if (current_ice_role==PJ_ICE_SESS_ROLE_CONTROLLING) {
985 sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING;
986 } else {
987 if (pjmedia_sdp_attr_find(rem_sdp->attr_count, rem_sdp->attr,
988 &STR_ICE_LITE, NULL) != NULL)
989 {
990 /* Remote is ICE Lite */
991 sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLING;
992 } else {
993 sdp_state->local_role = PJ_ICE_SESS_ROLE_CONTROLLED;
994 }
995 }
996
997 PJ_LOG(4,(tp_ice->base.name,
998 "Processing SDP: support ICE=%u, common comp_cnt=%u, "
999 "ice_mismatch=%u, ice_restart=%u, local_role=%s",
1000 (sdp_state->match_comp_cnt != 0),
1001 sdp_state->match_comp_cnt,
1002 sdp_state->ice_mismatch,
1003 sdp_state->ice_restart,
1004 pj_ice_sess_role_name(sdp_state->local_role)));
1005
1006 return PJ_SUCCESS;
1007
1008}
1009
1010
1011/* Verify incoming offer and create initial answer */
1012static pj_status_t create_initial_answer(struct transport_ice *tp_ice,
1013 pj_pool_t *sdp_pool,
1014 pjmedia_sdp_session *loc_sdp,
1015 const pjmedia_sdp_session *rem_sdp,
1016 unsigned media_index)
1017{
1018 const pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
1019 pj_status_t status;
1020
1021 /* Check if media is removed (just in case) */
1022 if (rem_m->desc.port == 0) {
1023 return PJ_SUCCESS;
1024 }
1025
1026 /* Verify the offer */
1027 status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index,
1028 PJ_ICE_SESS_ROLE_CONTROLLED,
1029 &tp_ice->rem_offer_state);
1030 if (status != PJ_SUCCESS) {
1031 set_no_ice(tp_ice, "Invalid SDP offer", status);
1032 return status;
1033 }
1034
1035 /* Does remote support ICE? */
1036 if (tp_ice->rem_offer_state.match_comp_cnt==0) {
1037 set_no_ice(tp_ice, "No ICE found in SDP offer", PJ_SUCCESS);
1038 return PJ_SUCCESS;
1039 }
1040
1041 /* ICE ice_mismatch? */
1042 if (tp_ice->rem_offer_state.ice_mismatch) {
1043 set_no_ice(tp_ice, "ICE ice_mismatch in remote offer", PJ_SUCCESS);
1044 encode_ice_mismatch(sdp_pool, loc_sdp, media_index);
1045 return PJ_SUCCESS;
1046 }
1047
1048 /* Encode ICE in SDP */
1049 status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
1050 tp_ice->rem_offer_state.match_comp_cnt,
1051 PJ_FALSE);
1052 if (status != PJ_SUCCESS) {
1053 set_no_ice(tp_ice, "Error encoding SDP answer", status);
1054 return status;
1055 }
1056
1057 return PJ_SUCCESS;
1058}
1059
1060
1061/* Create subsequent SDP offer */
1062static pj_status_t create_subsequent_offer(struct transport_ice *tp_ice,
1063 pj_pool_t *sdp_pool,
1064 pjmedia_sdp_session *loc_sdp,
1065 unsigned media_index)
1066{
1067 unsigned comp_cnt;
1068
1069 if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) {
1070 /* We don't have ICE */
1071 return PJ_SUCCESS;
1072 }
1073
1074 comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st);
1075 return encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
1076 comp_cnt, PJ_FALSE);
1077}
1078
1079
1080/* Create subsequent SDP answer */
1081static pj_status_t create_subsequent_answer(struct transport_ice *tp_ice,
1082 pj_pool_t *sdp_pool,
1083 pjmedia_sdp_session *loc_sdp,
1084 const pjmedia_sdp_session *rem_sdp,
1085 unsigned media_index)
1086{
1087 pj_status_t status;
1088
1089 /* We have a session */
1090 status = verify_ice_sdp(tp_ice, sdp_pool, rem_sdp, media_index,
1091 PJ_ICE_SESS_ROLE_CONTROLLED,
1092 &tp_ice->rem_offer_state);
1093 if (status != PJ_SUCCESS) {
1094 /* Something wrong with the offer */
1095 return status;
1096 }
1097
1098 if (pj_ice_strans_has_sess(tp_ice->ice_st)) {
1099 /*
1100 * Received subsequent offer while we have ICE active.
1101 */
1102
1103 if (tp_ice->rem_offer_state.match_comp_cnt == 0) {
1104 /* Remote no longer offers ICE */
1105 return PJ_SUCCESS;
1106 }
1107
1108 if (tp_ice->rem_offer_state.ice_mismatch) {
1109 encode_ice_mismatch(sdp_pool, loc_sdp, media_index);
1110 return PJ_SUCCESS;
1111 }
1112
1113 status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
1114 tp_ice->rem_offer_state.match_comp_cnt,
1115 tp_ice->rem_offer_state.ice_restart);
1116 if (status != PJ_SUCCESS)
1117 return status;
1118
1119 /* Done */
1120
1121 } else {
1122 /*
1123 * Received subsequent offer while we DON'T have ICE active.
1124 */
1125
1126 if (tp_ice->rem_offer_state.match_comp_cnt == 0) {
1127 /* Remote does not support ICE */
1128 return PJ_SUCCESS;
1129 }
1130
1131 if (tp_ice->rem_offer_state.ice_mismatch) {
1132 encode_ice_mismatch(sdp_pool, loc_sdp, media_index);
1133 return PJ_SUCCESS;
1134 }
1135
1136 /* Looks like now remote is offering ICE, so we need to create
1137 * ICE session now.
1138 */
1139 status = pj_ice_strans_init_ice(tp_ice->ice_st,
1140 PJ_ICE_SESS_ROLE_CONTROLLED,
1141 NULL, NULL);
1142 if (status != PJ_SUCCESS) {
1143 /* Fail to create new ICE session */
1144 return status;
1145 }
1146
1147 status = encode_session_in_sdp(tp_ice, sdp_pool, loc_sdp, media_index,
1148 tp_ice->rem_offer_state.match_comp_cnt,
1149 tp_ice->rem_offer_state.ice_restart);
1150 if (status != PJ_SUCCESS)
1151 return status;
1152
1153 /* Done */
1154 }
1155
1156 return PJ_SUCCESS;
1157}
1158
1159
1160/*
1161 * For both UAC and UAS, pass in the SDP before sending it to remote.
1162 * This will add ICE attributes to the SDP.
1163 */
1164static pj_status_t transport_media_create(pjmedia_transport *tp,
1165 pj_pool_t *sdp_pool,
1166 unsigned options,
1167 const pjmedia_sdp_session *rem_sdp,
1168 unsigned media_index)
1169{
1170 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1171 pj_ice_sess_role ice_role;
1172 pj_status_t status;
1173
1174 PJ_UNUSED_ARG(media_index);
1175 PJ_UNUSED_ARG(sdp_pool);
1176
1177 tp_ice->media_option = options;
1178 tp_ice->oa_role = ROLE_NONE;
1179 tp_ice->initial_sdp = PJ_TRUE;
1180
1181 /* Init ICE, the initial role is set now based on availability of
1182 * rem_sdp, but it will be checked again later.
1183 */
1184 ice_role = (rem_sdp==NULL ? PJ_ICE_SESS_ROLE_CONTROLLING :
1185 PJ_ICE_SESS_ROLE_CONTROLLED);
1186 status = pj_ice_strans_init_ice(tp_ice->ice_st, ice_role, NULL, NULL);
1187
1188 /* Done */
1189 return status;
1190}
1191
1192
1193static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
1194 pj_pool_t *sdp_pool,
1195 pjmedia_sdp_session *sdp_local,
1196 const pjmedia_sdp_session *rem_sdp,
1197 unsigned media_index)
1198{
1199 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1200 pj_status_t status;
1201
1202 /* Validate media transport */
1203 /* This transport only support RTP/AVP transport, unless if
1204 * transport checking is disabled
1205 */
1206 if ((tp_ice->media_option & PJMEDIA_TPMED_NO_TRANSPORT_CHECKING) == 0) {
1207 pjmedia_sdp_media *loc_m, *rem_m;
1208
1209 rem_m = rem_sdp? rem_sdp->media[media_index] : NULL;
1210 loc_m = sdp_local->media[media_index];
1211
1212 if (pj_stricmp(&loc_m->desc.transport, &STR_RTP_AVP) ||
1213 (rem_m && pj_stricmp(&rem_m->desc.transport, &STR_RTP_AVP)))
1214 {
1215 pjmedia_sdp_media_deactivate(sdp_pool, loc_m);
1216 return PJMEDIA_SDP_EINPROTO;
1217 }
1218 }
1219
1220 if (tp_ice->initial_sdp) {
1221 if (rem_sdp) {
1222 status = create_initial_answer(tp_ice, sdp_pool, sdp_local,
1223 rem_sdp, media_index);
1224 } else {
1225 status = create_initial_offer(tp_ice, sdp_pool, sdp_local,
1226 media_index);
1227 }
1228 } else {
1229 if (rem_sdp) {
1230 status = create_subsequent_answer(tp_ice, sdp_pool, sdp_local,
1231 rem_sdp, media_index);
1232 } else {
1233 status = create_subsequent_offer(tp_ice, sdp_pool, sdp_local,
1234 media_index);
1235 }
1236 }
1237
1238 if (status==PJ_SUCCESS) {
1239 if (rem_sdp)
1240 tp_ice->oa_role = ROLE_ANSWERER;
1241 else
1242 tp_ice->oa_role = ROLE_OFFERER;
1243 }
1244
1245 return status;
1246}
1247
1248
1249/* Start ICE session with the specified remote SDP */
1250static pj_status_t start_ice(struct transport_ice *tp_ice,
1251 pj_pool_t *tmp_pool,
1252 const pjmedia_sdp_session *rem_sdp,
1253 unsigned media_index)
1254{
1255 pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
1256 const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
1257 pj_ice_sess_cand *cand;
1258 unsigned i, cand_cnt;
1259 pj_status_t status;
1260
1261 get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr);
1262
1263 /* Allocate candidate array */
1264 cand = (pj_ice_sess_cand*)
1265 pj_pool_calloc(tmp_pool, PJ_ICE_MAX_CAND,
1266 sizeof(pj_ice_sess_cand));
1267
1268 /* Get all candidates in the media */
1269 cand_cnt = 0;
1270 for (i=0; i<rem_m->attr_count && cand_cnt < PJ_ICE_MAX_CAND; ++i) {
1271 pjmedia_sdp_attr *attr;
1272
1273 attr = rem_m->attr[i];
1274
1275 if (pj_strcmp(&attr->name, &STR_CANDIDATE)!=0)
1276 continue;
1277
1278 /* Parse candidate */
1279 status = parse_cand(tp_ice->base.name, tmp_pool, &attr->value,
1280 &cand[cand_cnt]);
1281 if (status != PJ_SUCCESS) {
1282 PJ_LOG(4,(tp_ice->base.name,
1283 "Error in parsing SDP candidate attribute '%.*s', "
1284 "candidate is ignored",
1285 (int)attr->value.slen, attr->value.ptr));
1286 continue;
1287 }
1288
1289 cand_cnt++;
1290 }
1291
1292 /* Start ICE */
1293 return pj_ice_strans_start_ice(tp_ice->ice_st, &ufrag_attr->value,
1294 &pwd_attr->value, cand_cnt, cand);
1295}
1296
1297
1298/*
1299 * Start ICE checks when both offer and answer have been negotiated
1300 * by SDP negotiator.
1301 */
1302static pj_status_t transport_media_start(pjmedia_transport *tp,
1303 pj_pool_t *tmp_pool,
1304 const pjmedia_sdp_session *sdp_local,
1305 const pjmedia_sdp_session *rem_sdp,
1306 unsigned media_index)
1307{
1308 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1309 pjmedia_sdp_media *rem_m;
1310 enum oa_role current_oa_role;
1311 pj_bool_t initial_oa;
1312 pj_status_t status;
1313
1314 PJ_ASSERT_RETURN(tp && tmp_pool && rem_sdp, PJ_EINVAL);
1315 PJ_ASSERT_RETURN(media_index < rem_sdp->media_count, PJ_EINVAL);
1316
1317 rem_m = rem_sdp->media[media_index];
1318
1319 initial_oa = tp_ice->initial_sdp;
1320 current_oa_role = tp_ice->oa_role;
1321
1322 /* SDP has been negotiated */
1323 tp_ice->initial_sdp = PJ_FALSE;
1324 tp_ice->oa_role = ROLE_NONE;
1325
1326 /* Nothing to do if we don't have ICE session */
1327 if (pj_ice_strans_has_sess(tp_ice->ice_st) == PJ_FALSE) {
1328 return PJ_SUCCESS;
1329 }
1330
1331 /* Special case for Session Timer. The re-INVITE for session refresh
1332 * doesn't call transport_encode_sdp(), causing current_oa_role to
1333 * be set to ROLE_NONE. This is a workaround.
1334 */
1335 if (current_oa_role == ROLE_NONE) {
1336 current_oa_role = ROLE_OFFERER;
1337 }
1338
1339 /* Processing depends on the offer/answer role */
1340 if (current_oa_role == ROLE_OFFERER) {
1341 /*
1342 * We are offerer. So this will be the first time we see the
1343 * remote's SDP.
1344 */
1345 struct sdp_state answer_state;
1346
1347 /* Verify the answer */
1348 status = verify_ice_sdp(tp_ice, tmp_pool, rem_sdp, media_index,
1349 PJ_ICE_SESS_ROLE_CONTROLLING, &answer_state);
1350 if (status != PJ_SUCCESS) {
1351 /* Something wrong in the SDP answer */
1352 set_no_ice(tp_ice, "Invalid remote SDP answer", status);
1353 return status;
1354 }
1355
1356 /* Does it have ICE? */
1357 if (answer_state.match_comp_cnt == 0) {
1358 /* Remote doesn't support ICE */
1359 set_no_ice(tp_ice, "Remote answer doesn't support ICE",
1360 PJ_SUCCESS);
1361 return PJ_SUCCESS;
1362 }
1363
1364 /* Check if remote has reported ice-mismatch */
1365 if (pjmedia_sdp_attr_find(rem_m->attr_count, rem_m->attr,
1366 &STR_ICE_MISMATCH, NULL) != NULL)
1367 {
1368 /* Remote has reported ice-mismatch */
1369 set_no_ice(tp_ice,
1370 "Remote answer contains 'ice-mismatch' attribute",
1371 PJ_SUCCESS);
1372 return PJ_SUCCESS;
1373 }
1374
1375 /* Check if remote has indicated a restart */
1376 if (answer_state.ice_restart) {
1377 PJ_LOG(2,(tp_ice->base.name,
1378 "Warning: remote has signalled ICE restart in SDP "
1379 "answer which is disallowed. Remote ICE negotiation"
1380 " may fail."));
1381 }
1382
1383 /* Check if the answer itself is mismatched */
1384 if (answer_state.ice_mismatch) {
1385 /* This happens either when a B2BUA modified remote answer but
1386 * strangely didn't modify our offer, or remote is not capable
1387 * of detecting mismatch in our offer (it didn't put
1388 * 'ice-mismatch' attribute in the answer).
1389 */
1390 PJ_LOG(2,(tp_ice->base.name,
1391 "Warning: remote answer mismatch, but it does not "
1392 "reject our offer with 'ice-mismatch'. ICE negotiation "
1393 "may fail"));
1394 }
1395
1396 /* Do nothing if ICE is complete or running */
1397 if (pj_ice_strans_sess_is_running(tp_ice->ice_st)) {
1398 PJ_LOG(4,(tp_ice->base.name,
1399 "Ignored offer/answer because ICE is running"));
1400 return PJ_SUCCESS;
1401 }
1402
1403 if (pj_ice_strans_sess_is_complete(tp_ice->ice_st)) {
1404 PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged"));
1405 return PJ_SUCCESS;
1406 }
1407
1408 /* Start ICE */
1409
1410 } else {
1411 /*
1412 * We are answerer. We've seen and negotiated remote's SDP
1413 * before, and the result is in "rem_offer_state".
1414 */
1415 const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
1416
1417 /* Check for ICE in remote offer */
1418 if (tp_ice->rem_offer_state.match_comp_cnt == 0) {
1419 /* No ICE attribute present */
1420 set_no_ice(tp_ice, "Remote no longer offers ICE",
1421 PJ_SUCCESS);
1422 return PJ_SUCCESS;
1423 }
1424
1425 /* Check for ICE ice_mismatch condition in the offer */
1426 if (tp_ice->rem_offer_state.ice_mismatch) {
1427 set_no_ice(tp_ice, "Remote offer mismatch: ",
1428 PJNATH_EICEMISMATCH);
1429 return PJ_SUCCESS;
1430 }
1431
1432 /* If ICE is complete and remote doesn't request restart,
1433 * then leave the session as is.
1434 */
1435 if (!initial_oa && tp_ice->rem_offer_state.ice_restart == PJ_FALSE) {
1436 /* Remote has not requested ICE restart, so session is
1437 * unchanged.
1438 */
1439 PJ_LOG(4,(tp_ice->base.name, "ICE session unchanged"));
1440 return PJ_SUCCESS;
1441 }
1442
1443 /* Either remote has requested ICE restart or this is our
1444 * first answer.
1445 */
1446
1447 /* Stop ICE */
1448 if (!initial_oa) {
1449 set_no_ice(tp_ice, "restarting by remote request..", PJ_SUCCESS);
1450
1451 /* We have put new ICE ufrag and pwd in the answer. Now
1452 * create a new ICE session with that ufrag/pwd pair.
1453 */
1454 get_ice_attr(sdp_local, sdp_local->media[media_index],
1455 &ufrag_attr, &pwd_attr);
1456 status = pj_ice_strans_init_ice(tp_ice->ice_st,
1457 tp_ice->rem_offer_state.local_role,
1458 &ufrag_attr->value,
1459 &pwd_attr->value);
1460 if (status != PJ_SUCCESS) {
1461 PJ_LOG(1,(tp_ice->base.name,
1462 "ICE re-initialization failed (status=%d)!",
1463 status));
1464 return status;
1465 }
1466 }
1467
1468 /* Ticket #977: Update role if turns out we're supposed to be the
1469 * Controlling agent (e.g. when talking to ice-lite peer).
1470 */
1471 if (tp_ice->rem_offer_state.local_role==PJ_ICE_SESS_ROLE_CONTROLLING &&
1472 pj_ice_strans_has_sess(tp_ice->ice_st))
1473 {
1474 pj_ice_strans_change_role(tp_ice->ice_st,
1475 PJ_ICE_SESS_ROLE_CONTROLLING);
1476 }
1477
1478
1479 /* start ICE */
1480 }
1481
1482 /* Now start ICE */
1483 status = start_ice(tp_ice, tmp_pool, rem_sdp, media_index);
1484 if (status != PJ_SUCCESS) {
1485 PJ_LOG(1,(tp_ice->base.name,
1486 "ICE restart failed (status=%d)!",
1487 status));
1488 return status;
1489 }
1490
1491 /* Done */
1492 tp_ice->use_ice = PJ_TRUE;
1493
1494 return PJ_SUCCESS;
1495}
1496
1497
1498static pj_status_t transport_media_stop(pjmedia_transport *tp)
1499{
1500 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1501
1502 set_no_ice(tp_ice, "media stop requested", PJ_SUCCESS);
1503
1504 return PJ_SUCCESS;
1505}
1506
1507
1508static pj_status_t transport_get_info(pjmedia_transport *tp,
1509 pjmedia_transport_info *info)
1510{
1511 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1512 pj_ice_sess_cand cand;
1513 pj_status_t status;
1514
1515 pj_bzero(&info->sock_info, sizeof(info->sock_info));
1516 info->sock_info.rtp_sock = info->sock_info.rtcp_sock = PJ_INVALID_SOCKET;
1517
1518 /* Get RTP default address */
1519 status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 1, &cand);
1520 if (status != PJ_SUCCESS)
1521 return status;
1522
1523 pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr);
1524
1525 /* Get RTCP default address */
1526 if (tp_ice->comp_cnt > 1) {
1527 status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand);
1528 if (status != PJ_SUCCESS)
1529 return status;
1530
1531 pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr);
1532 }
1533
1534 /* Set remote address originating RTP & RTCP if this transport has
1535 * ICE activated or received any packets.
1536 */
1537 if (tp_ice->use_ice || tp_ice->rtp_src_cnt) {
1538 info->src_rtp_name = tp_ice->rtp_src_addr;
1539 }
1540 if (tp_ice->use_ice || tp_ice->rtcp_src_cnt) {
1541 info->src_rtcp_name = tp_ice->rtcp_src_addr;
1542 }
1543
1544 /* Fill up transport specific info */
1545 if (info->specific_info_cnt < PJ_ARRAY_SIZE(info->spc_info)) {
1546 pjmedia_transport_specific_info *tsi;
1547 pjmedia_ice_transport_info *ii;
1548 unsigned i;
1549
1550 pj_assert(sizeof(*ii) <= sizeof(tsi->buffer));
1551 tsi = &info->spc_info[info->specific_info_cnt++];
1552 tsi->type = PJMEDIA_TRANSPORT_TYPE_ICE;
1553 tsi->cbsize = sizeof(*ii);
1554
1555 ii = (pjmedia_ice_transport_info*) tsi->buffer;
1556 pj_bzero(ii, sizeof(*ii));
1557
1558 ii->active = tp_ice->use_ice;
1559
1560 if (pj_ice_strans_has_sess(tp_ice->ice_st))
1561 ii->role = pj_ice_strans_get_role(tp_ice->ice_st);
1562 else
1563 ii->role = PJ_ICE_SESS_ROLE_UNKNOWN;
1564 ii->sess_state = pj_ice_strans_get_state(tp_ice->ice_st);
1565 ii->comp_cnt = pj_ice_strans_get_running_comp_cnt(tp_ice->ice_st);
1566
1567 for (i=1; i<=ii->comp_cnt && i<=PJ_ARRAY_SIZE(ii->comp); ++i) {
1568 const pj_ice_sess_check *chk;
1569
1570 chk = pj_ice_strans_get_valid_pair(tp_ice->ice_st, i);
1571 if (chk) {
1572 ii->comp[i-1].lcand_type = chk->lcand->type;
1573 pj_sockaddr_cp(&ii->comp[i-1].lcand_addr,
1574 &chk->lcand->addr);
1575 ii->comp[i-1].rcand_type = chk->rcand->type;
1576 pj_sockaddr_cp(&ii->comp[i-1].rcand_addr,
1577 &chk->rcand->addr);
1578 }
1579 }
1580 }
1581
1582 return PJ_SUCCESS;
1583}
1584
1585
1586static pj_status_t transport_attach (pjmedia_transport *tp,
1587 void *stream,
1588 const pj_sockaddr_t *rem_addr,
1589 const pj_sockaddr_t *rem_rtcp,
1590 unsigned addr_len,
1591 void (*rtp_cb)(void*,
1592 void*,
1593 pj_ssize_t),
1594 void (*rtcp_cb)(void*,
1595 void*,
1596 pj_ssize_t))
1597{
1598 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1599
1600 tp_ice->stream = stream;
1601 tp_ice->rtp_cb = rtp_cb;
1602 tp_ice->rtcp_cb = rtcp_cb;
1603
1604 pj_memcpy(&tp_ice->remote_rtp, rem_addr, addr_len);
1605 pj_memcpy(&tp_ice->remote_rtcp, rem_rtcp, addr_len);
1606 tp_ice->addr_len = addr_len;
1607
1608 /* Init source RTP & RTCP addresses and counter */
1609 tp_ice->rtp_src_addr = tp_ice->remote_rtp;
1610 tp_ice->rtcp_src_addr = tp_ice->remote_rtcp;
1611 tp_ice->rtp_src_cnt = 0;
1612 tp_ice->rtcp_src_cnt = 0;
1613
1614 return PJ_SUCCESS;
1615}
1616
1617
1618static void transport_detach(pjmedia_transport *tp,
1619 void *strm)
1620{
1621 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1622
1623 /* TODO: need to solve ticket #460 here */
1624
1625 tp_ice->rtp_cb = NULL;
1626 tp_ice->rtcp_cb = NULL;
1627 tp_ice->stream = NULL;
1628
1629 PJ_UNUSED_ARG(strm);
1630}
1631
1632
1633static pj_status_t transport_send_rtp(pjmedia_transport *tp,
1634 const void *pkt,
1635 pj_size_t size)
1636{
1637 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1638
1639 /* Simulate packet lost on TX direction */
1640 if (tp_ice->tx_drop_pct) {
1641 if ((pj_rand() % 100) <= (int)tp_ice->tx_drop_pct) {
1642 PJ_LOG(5,(tp_ice->base.name,
1643 "TX RTP packet dropped because of pkt lost "
1644 "simulation"));
1645 return PJ_SUCCESS;
1646 }
1647 }
1648
1649 return pj_ice_strans_sendto(tp_ice->ice_st, 1,
1650 pkt, size, &tp_ice->remote_rtp,
1651 tp_ice->addr_len);
1652}
1653
1654
1655static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
1656 const void *pkt,
1657 pj_size_t size)
1658{
1659 return transport_send_rtcp2(tp, NULL, 0, pkt, size);
1660}
1661
1662static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
1663 const pj_sockaddr_t *addr,
1664 unsigned addr_len,
1665 const void *pkt,
1666 pj_size_t size)
1667{
1668 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1669 if (tp_ice->comp_cnt > 1) {
1670 if (addr == NULL) {
1671 addr = &tp_ice->remote_rtcp;
1672 addr_len = pj_sockaddr_get_len(addr);
1673 }
1674 return pj_ice_strans_sendto(tp_ice->ice_st, 2, pkt, size,
1675 addr, addr_len);
1676 } else {
1677 return PJ_SUCCESS;
1678 }
1679}
1680
1681
1682static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id,
1683 void *pkt, pj_size_t size,
1684 const pj_sockaddr_t *src_addr,
1685 unsigned src_addr_len)
1686{
1687 struct transport_ice *tp_ice;
1688 pj_bool_t discard = PJ_FALSE;
1689
1690 tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st);
1691
1692 if (comp_id==1 && tp_ice->rtp_cb) {
1693
1694 /* Simulate packet lost on RX direction */
1695 if (tp_ice->rx_drop_pct) {
1696 if ((pj_rand() % 100) <= (int)tp_ice->rx_drop_pct) {
1697 PJ_LOG(5,(tp_ice->base.name,
1698 "RX RTP packet dropped because of pkt lost "
1699 "simulation"));
1700 return;
1701 }
1702 }
1703
1704 /* See if source address of RTP packet is different than the
1705 * configured address, and switch RTP remote address to
1706 * source packet address after several consecutive packets
1707 * have been received.
1708 */
1709 if (!tp_ice->use_ice) {
1710 pj_bool_t enable_switch =
1711 ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0);
1712
1713 if (!enable_switch ||
1714 pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) == 0)
1715 {
1716 /* Don't switch while we're receiving from remote_rtp */
1717 tp_ice->rtp_src_cnt = 0;
1718 } else {
1719
1720 ++tp_ice->rtp_src_cnt;
1721
1722 /* Check if the source address is recognized. */
1723 if (pj_sockaddr_cmp(src_addr, &tp_ice->rtp_src_addr) != 0) {
1724 /* Remember the new source address. */
1725 pj_sockaddr_cp(&tp_ice->rtp_src_addr, src_addr);
1726 /* Reset counter */
1727 tp_ice->rtp_src_cnt = 0;
1728 discard = PJ_TRUE;
1729 }
1730
1731 if (tp_ice->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) {
1732 discard = PJ_TRUE;
1733 } else {
1734 char addr_text[80];
1735
1736 /* Set remote RTP address to source address */
1737 pj_sockaddr_cp(&tp_ice->remote_rtp, &tp_ice->rtp_src_addr);
1738 tp_ice->addr_len = pj_sockaddr_get_len(&tp_ice->remote_rtp);
1739
1740 /* Reset counter */
1741 tp_ice->rtp_src_cnt = 0;
1742
1743 PJ_LOG(4,(tp_ice->base.name,
1744 "Remote RTP address switched to %s",
1745 pj_sockaddr_print(&tp_ice->remote_rtp, addr_text,
1746 sizeof(addr_text), 3)));
1747
1748 /* Also update remote RTCP address if actual RTCP source
1749 * address is not heard yet.
1750 */
1751 if (!pj_sockaddr_has_addr(&tp_ice->rtcp_src_addr)) {
1752 pj_uint16_t port;
1753
1754 pj_sockaddr_cp(&tp_ice->remote_rtcp,
1755 &tp_ice->remote_rtp);
1756
1757 port = (pj_uint16_t)
1758 (pj_sockaddr_get_port(&tp_ice->remote_rtp)+1);
1759 pj_sockaddr_set_port(&tp_ice->remote_rtcp, port);
1760
1761 PJ_LOG(4,(tp_ice->base.name,
1762 "Remote RTCP address switched to predicted "
1763 "address %s",
1764 pj_sockaddr_print(&tp_ice->remote_rtcp,
1765 addr_text,
1766 sizeof(addr_text), 3)));
1767 }
1768 }
1769 }
1770 }
1771
1772 if (!discard)
1773 (*tp_ice->rtp_cb)(tp_ice->stream, pkt, size);
1774
1775 } else if (comp_id==2 && tp_ice->rtcp_cb) {
1776
1777 /* Check if RTCP source address is the same as the configured
1778 * remote address, and switch the address when they are
1779 * different.
1780 */
1781 if (!tp_ice->use_ice &&
1782 (tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0)
1783 {
1784 if (pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) == 0) {
1785 tp_ice->rtcp_src_cnt = 0;
1786 } else {
1787 char addr_text[80];
1788
1789 ++tp_ice->rtcp_src_cnt;
1790 if (tp_ice->rtcp_src_cnt < PJMEDIA_RTCP_NAT_PROBATION_CNT) {
1791 discard = PJ_TRUE;
1792 } else {
1793 tp_ice->rtcp_src_cnt = 0;
1794 pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr);
1795 pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr);
1796
1797 pj_assert(tp_ice->addr_len==pj_sockaddr_get_len(src_addr));
1798
1799 PJ_LOG(4,(tp_ice->base.name,
1800 "Remote RTCP address switched to %s",
1801 pj_sockaddr_print(&tp_ice->remote_rtcp,
1802 addr_text, sizeof(addr_text),
1803 3)));
1804 }
1805 }
1806 }
1807
1808 if (!discard)
1809 (*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size);
1810 }
1811
1812 PJ_UNUSED_ARG(src_addr_len);
1813}
1814
1815
1816static void ice_on_ice_complete(pj_ice_strans *ice_st,
1817 pj_ice_strans_op op,
1818 pj_status_t result)
1819{
1820 struct transport_ice *tp_ice;
1821
1822 tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st);
1823
1824 /* Notify application */
1825 if (tp_ice->cb.on_ice_complete)
1826 (*tp_ice->cb.on_ice_complete)(&tp_ice->base, op, result);
1827}
1828
1829
1830/* Simulate lost */
1831static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
1832 pjmedia_dir dir,
1833 unsigned pct_lost)
1834{
1835 struct transport_ice *ice = (struct transport_ice*) tp;
1836
1837 PJ_ASSERT_RETURN(tp && pct_lost <= 100, PJ_EINVAL);
1838
1839 if (dir & PJMEDIA_DIR_ENCODING)
1840 ice->tx_drop_pct = pct_lost;
1841
1842 if (dir & PJMEDIA_DIR_DECODING)
1843 ice->rx_drop_pct = pct_lost;
1844
1845 return PJ_SUCCESS;
1846}
1847
1848
1849/*
1850 * Destroy ICE media transport.
1851 */
1852static pj_status_t transport_destroy(pjmedia_transport *tp)
1853{
1854 struct transport_ice *tp_ice = (struct transport_ice*)tp;
1855
1856 if (tp_ice->ice_st) {
1857 pj_ice_strans_destroy(tp_ice->ice_st);
1858 tp_ice->ice_st = NULL;
1859 }
1860
1861 if (tp_ice->pool) {
1862 pj_pool_t *pool = tp_ice->pool;
1863 tp_ice->pool = NULL;
1864 pj_pool_release(pool);
1865 }
1866
1867 return PJ_SUCCESS;
1868}
1869