WARNING: The online documentation has moved to https://docs.pjsip.org.

Visit the new documentation at https://docs.pjsip.org:

BLOG | DOCUMENTATION | GITHUB

Home --> Documentations --> PJNATH Reference

ice_demo, an interactive ICE endpoint

This sample demonstrates how to use ICE stream transport without using signaling protocol such as SIP. It provides interactive user interface to create and manage the ICE sessions as well as to exchange SDP with another ice_demo instance.

Features of the demo application:

  • supports host, STUN, and TURN candidates
  • disabling of host candidates
  • DNS SRV resolution for STUN and TURN servers
  • TCP connection to TURN server
  • Optional use of fingerprint for TURN
  • prints and parse SDP containing ICE infos
  • exchange SDP with copy/paste

This file is pjsip-apps/src/samples/icedemo.c

Screenshot on WinXP:

ice_demo on WinXP
1/*
2 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18#include <stdio.h>
19#include <stdlib.h>
20#include <pjlib.h>
21#include <pjlib-util.h>
22#include <pjnath.h>
23
24
25#define THIS_FILE "icedemo.c"
26
27/* For this demo app, configure longer STUN keep-alive time
28 * so that it does't clutter the screen output.
29 */
30#define KA_INTERVAL 300
31
32
33/* This is our global variables */
34static struct app_t
35{
36 /* Command line options are stored here */
37 struct options
38 {
39 unsigned comp_cnt;
40 pj_str_t ns;
41 int max_host;
42 pj_bool_t regular;
43 pj_str_t stun_srv;
44 pj_str_t turn_srv;
45 pj_bool_t turn_tcp;
46 pj_str_t turn_username;
47 pj_str_t turn_password;
48 pj_bool_t turn_fingerprint;
49 const char *log_file;
50 } opt;
51
52 /* Our global variables */
54 pj_pool_t *pool;
55 pj_thread_t *thread;
56 pj_bool_t thread_quit_flag;
57 pj_ice_strans_cfg ice_cfg;
58 pj_ice_strans *icest;
59 FILE *log_fhnd;
60
61 /* Variables to store parsed remote ICE info */
62 struct rem_info
63 {
64 char ufrag[80];
65 char pwd[80];
66 unsigned comp_cnt;
68 unsigned cand_cnt;
70 } rem;
71
72} icedemo;
73
74/* Utility to display error messages */
75static void icedemo_perror(const char *title, pj_status_t status)
76{
77 char errmsg[PJ_ERR_MSG_SIZE];
78
79 pj_strerror(status, errmsg, sizeof(errmsg));
80 PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
81}
82
83/* Utility: display error message and exit application (usually
84 * because of fatal error.
85 */
86static void err_exit(const char *title, pj_status_t status)
87{
88 if (status != PJ_SUCCESS) {
89 icedemo_perror(title, status);
90 }
91 PJ_LOG(3,(THIS_FILE, "Shutting down.."));
92
93 if (icedemo.icest)
94 pj_ice_strans_destroy(icedemo.icest);
95
96 pj_thread_sleep(500);
97
98 icedemo.thread_quit_flag = PJ_TRUE;
99 if (icedemo.thread) {
100 pj_thread_join(icedemo.thread);
101 pj_thread_destroy(icedemo.thread);
102 }
103
104 if (icedemo.ice_cfg.stun_cfg.ioqueue)
105 pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
106
107 if (icedemo.ice_cfg.stun_cfg.timer_heap)
108 pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
109
110 pj_caching_pool_destroy(&icedemo.cp);
111
112 pj_shutdown();
113
114 if (icedemo.log_fhnd) {
115 fclose(icedemo.log_fhnd);
116 icedemo.log_fhnd = NULL;
117 }
118
119 exit(status != PJ_SUCCESS);
120}
121
122#define CHECK(expr) status=expr; \
123 if (status!=PJ_SUCCESS) { \
124 err_exit(#expr, status); \
125 }
126
127/*
128 * This function checks for events from both timer and ioqueue (for
129 * network events). It is invoked by the worker thread.
130 */
131static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
132{
133 enum { MAX_NET_EVENTS = 1 };
134 pj_time_val max_timeout = {0, 0};
135 pj_time_val timeout = { 0, 0};
136 unsigned count = 0, net_event_count = 0;
137 int c;
138
139 max_timeout.msec = max_msec;
140
141 /* Poll the timer to run it and also to retrieve the earliest entry. */
142 timeout.sec = timeout.msec = 0;
143 c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
144 if (c > 0)
145 count += c;
146
147 /* timer_heap_poll should never ever returns negative value, or otherwise
148 * ioqueue_poll() will block forever!
149 */
150 pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
151 if (timeout.msec >= 1000) timeout.msec = 999;
152
153 /* compare the value with the timeout to wait from timer, and use the
154 * minimum value.
155 */
156 if (PJ_TIME_VAL_GT(timeout, max_timeout))
157 timeout = max_timeout;
158
159 /* Poll ioqueue.
160 * Repeat polling the ioqueue while we have immediate events, because
161 * timer heap may process more than one events, so if we only process
162 * one network events at a time (such as when IOCP backend is used),
163 * the ioqueue may have trouble keeping up with the request rate.
164 *
165 * For example, for each send() request, one network event will be
166 * reported by ioqueue for the send() completion. If we don't poll
167 * the ioqueue often enough, the send() completion will not be
168 * reported in timely manner.
169 */
170 do {
171 c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
172 if (c < 0) {
175 if (p_count)
176 *p_count = count;
177 return err;
178 } else if (c == 0) {
179 break;
180 } else {
181 net_event_count += c;
182 timeout.sec = timeout.msec = 0;
183 }
184 } while (c > 0 && net_event_count < MAX_NET_EVENTS);
185
186 count += net_event_count;
187 if (p_count)
188 *p_count = count;
189
190 return PJ_SUCCESS;
191
192}
193
194/*
195 * This is the worker thread that polls event in the background.
196 */
197static int icedemo_worker_thread(void *unused)
198{
199 PJ_UNUSED_ARG(unused);
200
201 while (!icedemo.thread_quit_flag) {
202 handle_events(500, NULL);
203 }
204
205 return 0;
206}
207
208/*
209 * This is the callback that is registered to the ICE stream transport to
210 * receive notification about incoming data. By "data" it means application
211 * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
212 * as STUN connectivity checks or TURN signaling).
213 */
214static void cb_on_rx_data(pj_ice_strans *ice_st,
215 unsigned comp_id,
216 void *pkt, pj_size_t size,
217 const pj_sockaddr_t *src_addr,
218 unsigned src_addr_len)
219{
220 char ipstr[PJ_INET6_ADDRSTRLEN+10];
221
222 PJ_UNUSED_ARG(ice_st);
223 PJ_UNUSED_ARG(src_addr_len);
224 PJ_UNUSED_ARG(pkt);
225
226 // Don't do this! It will ruin the packet buffer in case TCP is used!
227 //((char*)pkt)[size] = '\0';
228
229 PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
230 comp_id, size,
231 pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
232 (unsigned)size,
233 (char*)pkt));
234}
235
236/*
237 * This is the callback that is registered to the ICE stream transport to
238 * receive notification about ICE state progression.
239 */
240static void cb_on_ice_complete(pj_ice_strans *ice_st,
242 pj_status_t status)
243{
244 const char *opname =
245 (op==PJ_ICE_STRANS_OP_INIT? "initialization" :
246 (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
247
248 if (status == PJ_SUCCESS) {
249 PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
250 } else {
251 char errmsg[PJ_ERR_MSG_SIZE];
252
253 pj_strerror(status, errmsg, sizeof(errmsg));
254 PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
255 pj_ice_strans_destroy(ice_st);
256 icedemo.icest = NULL;
257 }
258}
259
260/* log callback to write to file */
261static void log_func(int level, const char *data, int len)
262{
263 pj_log_write(level, data, len);
264 if (icedemo.log_fhnd) {
265 if (fwrite(data, len, 1, icedemo.log_fhnd) != 1)
266 return;
267 }
268}
269
270/*
271 * This is the main application initialization function. It is called
272 * once (and only once) during application initialization sequence by
273 * main().
274 */
275static pj_status_t icedemo_init(void)
276{
277 pj_status_t status;
278
279 if (icedemo.opt.log_file) {
280 icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
281 pj_log_set_log_func(&log_func);
282 }
283
284 /* Initialize the libraries before anything else */
285 CHECK( pj_init() );
286 CHECK( pjlib_util_init() );
287 CHECK( pjnath_init() );
288
289 /* Must create pool factory, where memory allocations come from */
290 pj_caching_pool_init(&icedemo.cp, NULL, 0);
291
292 /* Init our ICE settings with null values */
293 pj_ice_strans_cfg_default(&icedemo.ice_cfg);
294
295 icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
296
297 /* Create application memory pool */
298 icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
299 512, 512, NULL);
300
301 /* Create timer heap for timer stuff */
302 CHECK( pj_timer_heap_create(icedemo.pool, 100,
303 &icedemo.ice_cfg.stun_cfg.timer_heap) );
304
305 /* and create ioqueue for network I/O stuff */
306 CHECK( pj_ioqueue_create(icedemo.pool, 16,
307 &icedemo.ice_cfg.stun_cfg.ioqueue) );
308
309 /* something must poll the timer heap and ioqueue,
310 * unless we're on Symbian where the timer heap and ioqueue run
311 * on themselves.
312 */
313 CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
314 NULL, 0, 0, &icedemo.thread) );
315
316 icedemo.ice_cfg.af = pj_AF_INET();
317
318 /* Create DNS resolver if nameserver is set */
319 if (icedemo.opt.ns.slen) {
320 CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
321 "resolver",
322 0,
323 icedemo.ice_cfg.stun_cfg.timer_heap,
324 icedemo.ice_cfg.stun_cfg.ioqueue,
325 &icedemo.ice_cfg.resolver) );
326
327 CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
328 &icedemo.opt.ns, NULL) );
329 }
330
331 /* -= Start initializing ICE stream transport config =- */
332
333 /* Maximum number of host candidates */
334 if (icedemo.opt.max_host != -1)
335 icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
336
337 /* Nomination strategy */
338 if (icedemo.opt.regular)
339 icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
340 else
341 icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
342
343 /* Configure STUN/srflx candidate resolution */
344 if (icedemo.opt.stun_srv.slen) {
345 char *pos;
346
347 /* Command line option may contain port number */
348 if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
349 icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
350 icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
351
352 icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
353 } else {
354 icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
355 icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
356 }
357
358 /* For this demo app, configure longer STUN keep-alive time
359 * so that it does't clutter the screen output.
360 */
361 icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
362 }
363
364 /* Configure TURN candidate */
365 if (icedemo.opt.turn_srv.slen) {
366 char *pos;
367
368 /* Command line option may contain port number */
369 if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
370 icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
371 icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
372
373 icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
374 } else {
375 icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
376 icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
377 }
378
379 /* TURN credential */
380 icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
381 icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
382 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
383 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
384
385 /* Connection type to TURN server */
386 if (icedemo.opt.turn_tcp)
387 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
388 else
389 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
390
391 /* For this demo app, configure longer keep-alive time
392 * so that it does't clutter the screen output.
393 */
394 icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
395 }
396
397 /* -= That's it for now, initialization is complete =- */
398 return PJ_SUCCESS;
399}
400
401
402/*
403 * Create ICE stream transport instance, invoked from the menu.
404 */
405static void icedemo_create_instance(void)
406{
407 pj_ice_strans_cb icecb;
408 pj_status_t status;
409
410 if (icedemo.icest != NULL) {
411 puts("ICE instance already created, destroy it first");
412 return;
413 }
414
415 /* init the callback */
416 pj_bzero(&icecb, sizeof(icecb));
417 icecb.on_rx_data = cb_on_rx_data;
418 icecb.on_ice_complete = cb_on_ice_complete;
419
420 /* create the instance */
421 status = pj_ice_strans_create("icedemo", /* object name */
422 &icedemo.ice_cfg, /* settings */
423 icedemo.opt.comp_cnt, /* comp_cnt */
424 NULL, /* user data */
425 &icecb, /* callback */
426 &icedemo.icest) /* instance ptr */
427 ;
428 if (status != PJ_SUCCESS)
429 icedemo_perror("error creating ice", status);
430 else
431 PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
432}
433
434/* Utility to nullify parsed remote info */
435static void reset_rem_info(void)
436{
437 pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
438}
439
440
441/*
442 * Destroy ICE stream transport instance, invoked from the menu.
443 */
444static void icedemo_destroy_instance(void)
445{
446 if (icedemo.icest == NULL) {
447 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
448 return;
449 }
450
451 pj_ice_strans_destroy(icedemo.icest);
452 icedemo.icest = NULL;
453
454 reset_rem_info();
455
456 PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
457}
458
459
460/*
461 * Create ICE session, invoked from the menu.
462 */
463static void icedemo_init_session(unsigned rolechar)
464{
465 pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ?
468 pj_status_t status;
469
470 if (icedemo.icest == NULL) {
471 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
472 return;
473 }
474
475 if (pj_ice_strans_has_sess(icedemo.icest)) {
476 PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
477 return;
478 }
479
480 status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
481 if (status != PJ_SUCCESS)
482 icedemo_perror("error creating session", status);
483 else
484 PJ_LOG(3,(THIS_FILE, "ICE session created"));
485
486 reset_rem_info();
487}
488
489
490/*
491 * Stop/destroy ICE session, invoked from the menu.
492 */
493static void icedemo_stop_session(void)
494{
495 pj_status_t status;
496
497 if (icedemo.icest == NULL) {
498 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
499 return;
500 }
501
502 if (!pj_ice_strans_has_sess(icedemo.icest)) {
503 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
504 return;
505 }
506
507 status = pj_ice_strans_stop_ice(icedemo.icest);
508 if (status != PJ_SUCCESS)
509 icedemo_perror("error stopping session", status);
510 else
511 PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
512
513 reset_rem_info();
514}
515
516#define PRINT(...) \
517 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
518 __VA_ARGS__); \
519 if (printed <= 0 || printed >= (int)(maxlen - (p-buffer))) \
520 return -PJ_ETOOSMALL; \
521 p += printed
522
523
524/* Utility to create a=candidate SDP attribute */
525static int print_cand(char buffer[], unsigned maxlen,
526 const pj_ice_sess_cand *cand)
527{
528 char ipaddr[PJ_INET6_ADDRSTRLEN];
529 char *p = buffer;
530 int printed;
531
532 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
533 (int)cand->foundation.slen,
534 cand->foundation.ptr,
535 (unsigned)cand->comp_id,
536 cand->prio,
537 pj_sockaddr_print(&cand->addr, ipaddr,
538 sizeof(ipaddr), 0),
539 (unsigned)pj_sockaddr_get_port(&cand->addr));
540
541 PRINT("%s\n",
543
544 if (p == buffer+maxlen)
545 return -PJ_ETOOSMALL;
546
547 *p = '\0';
548
549 return (int)(p-buffer);
550}
551
552/*
553 * Encode ICE information in SDP.
554 */
555static int encode_session(char buffer[], unsigned maxlen)
556{
557 char *p = buffer;
558 unsigned comp;
559 int printed;
560 pj_str_t local_ufrag, local_pwd;
561 pj_status_t status;
562
563 /* Write "dummy" SDP v=, o=, s=, and t= lines */
564 PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n");
565
566 /* Get ufrag and pwd from current session */
567 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
568 NULL, NULL);
569
570 /* Write the a=ice-ufrag and a=ice-pwd attributes */
571 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
572 (int)local_ufrag.slen,
573 local_ufrag.ptr,
574 (int)local_pwd.slen,
575 local_pwd.ptr);
576
577 /* Write each component */
578 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
579 unsigned j, cand_cnt;
581 char ipaddr[PJ_INET6_ADDRSTRLEN];
582
583 /* Get default candidate for the component */
584 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
585 if (status != PJ_SUCCESS)
586 return -status;
587
588 /* Write the default address */
589 if (comp==0) {
590 /* For component 1, default address is in m= and c= lines */
591 PRINT("m=audio %d RTP/AVP 0\n"
592 "c=IN IP4 %s\n",
593 (int)pj_sockaddr_get_port(&cand[0].addr),
594 pj_sockaddr_print(&cand[0].addr, ipaddr,
595 sizeof(ipaddr), 0));
596 } else if (comp==1) {
597 /* For component 2, default address is in a=rtcp line */
598 PRINT("a=rtcp:%d IN IP4 %s\n",
599 (int)pj_sockaddr_get_port(&cand[0].addr),
600 pj_sockaddr_print(&cand[0].addr, ipaddr,
601 sizeof(ipaddr), 0));
602 } else {
603 /* For other components, we'll just invent this.. */
604 PRINT("a=Xice-defcand:%d IN IP4 %s\n",
605 (int)pj_sockaddr_get_port(&cand[0].addr),
606 pj_sockaddr_print(&cand[0].addr, ipaddr,
607 sizeof(ipaddr), 0));
608 }
609
610 /* Enumerate all candidates for this component */
611 cand_cnt = PJ_ARRAY_SIZE(cand);
612 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
613 &cand_cnt, cand);
614 if (status != PJ_SUCCESS)
615 return -status;
616
617 /* And encode the candidates as SDP */
618 for (j=0; j<cand_cnt; ++j) {
619 printed = print_cand(p, maxlen - (unsigned)(p-buffer), &cand[j]);
620 if (printed < 0)
621 return -PJ_ETOOSMALL;
622 p += printed;
623 }
624 }
625
626 if (p == buffer+maxlen)
627 return -PJ_ETOOSMALL;
628
629 *p = '\0';
630 return (int)(p - buffer);
631}
632
633
634/*
635 * Show information contained in the ICE stream transport. This is
636 * invoked from the menu.
637 */
638static void icedemo_show_ice(void)
639{
640 static char buffer[1000];
641 int len;
642
643 if (icedemo.icest == NULL) {
644 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
645 return;
646 }
647
648 puts("General info");
649 puts("---------------");
650 printf("Component count : %d\n", icedemo.opt.comp_cnt);
651 printf("Status : ");
652 if (pj_ice_strans_sess_is_complete(icedemo.icest))
653 puts("negotiation complete");
654 else if (pj_ice_strans_sess_is_running(icedemo.icest))
655 puts("negotiation is in progress");
656 else if (pj_ice_strans_has_sess(icedemo.icest))
657 puts("session ready");
658 else
659 puts("session not created");
660
661 if (!pj_ice_strans_has_sess(icedemo.icest)) {
662 puts("Create the session first to see more info");
663 return;
664 }
665
666 printf("Negotiated comp_cnt: %d\n",
668 printf("Role : %s\n",
670 "controlled" : "controlling");
671
672 len = encode_session(buffer, sizeof(buffer));
673 if (len < 0)
674 err_exit("not enough buffer to show ICE status", -len);
675
676 puts("");
677 printf("Local SDP (paste this to remote host):\n"
678 "--------------------------------------\n"
679 "%s\n", buffer);
680
681
682 puts("");
683 puts("Remote info:\n"
684 "----------------------");
685 if (icedemo.rem.cand_cnt==0) {
686 puts("No remote info yet");
687 } else {
688 unsigned i;
689
690 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
691 printf("Remote password : %s\n", icedemo.rem.pwd);
692 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
693
694 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
695 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
696 if (len < 0)
697 err_exit("not enough buffer to show ICE status", -len);
698
699 printf(" %s", buffer);
700 }
701 }
702}
703
704
705/*
706 * Input and parse SDP from the remote (containing remote's ICE information)
707 * and save it to global variables.
708 */
709static void icedemo_input_remote(void)
710{
711 char linebuf[80];
712 unsigned media_cnt = 0;
713 unsigned comp0_port = 0;
714 char comp0_addr[80];
715 pj_bool_t done = PJ_FALSE;
716
717 puts("Paste SDP from remote host, end with empty line");
718
719 reset_rem_info();
720
721 comp0_addr[0] = '\0';
722
723 while (!done) {
724 pj_size_t len;
725 char *line;
726
727 printf(">");
728 if (stdout) fflush(stdout);
729
730 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
731 break;
732
733 len = strlen(linebuf);
734 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
735 linebuf[--len] = '\0';
736
737 line = linebuf;
738 while (len && pj_isspace(*line))
739 ++line, --len;
740
741 if (len==0)
742 break;
743
744 /* Ignore subsequent media descriptors */
745 if (media_cnt > 1)
746 continue;
747
748 switch (line[0]) {
749 case 'm':
750 {
751 int cnt;
752 char media[32], portstr[32];
753
754 ++media_cnt;
755 if (media_cnt > 1) {
756 puts("Media line ignored");
757 break;
758 }
759
760 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
761 if (cnt != 2) {
762 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
763 goto on_error;
764 }
765
766 comp0_port = atoi(portstr);
767
768 }
769 break;
770 case 'c':
771 {
772 int cnt;
773 char c[32], net[32], ip[80];
774
775 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
776 if (cnt != 3) {
777 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
778 goto on_error;
779 }
780
781 strcpy(comp0_addr, ip);
782 }
783 break;
784 case 'a':
785 {
786 char *attr = strtok(line+2, ": \t\r\n");
787 if (strcmp(attr, "ice-ufrag")==0) {
788 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
789 } else if (strcmp(attr, "ice-pwd")==0) {
790 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
791 } else if (strcmp(attr, "rtcp")==0) {
792 char *val = attr+strlen(attr)+1;
793 int af, cnt;
794 int port;
795 char net[32], ip[64];
796 pj_str_t tmp_addr;
797 pj_status_t status;
798
799 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
800 if (cnt != 3) {
801 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
802 goto on_error;
803 }
804
805 if (strchr(ip, ':'))
806 af = pj_AF_INET6();
807 else
808 af = pj_AF_INET();
809
810 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
811 tmp_addr = pj_str(ip);
812 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
813 &tmp_addr);
814 if (status != PJ_SUCCESS) {
815 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
816 goto on_error;
817 }
818 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
819
820 } else if (strcmp(attr, "candidate")==0) {
821 char *sdpcand = attr+strlen(attr)+1;
822 int af, cnt;
823 char foundation[32], transport[12], ipaddr[80], type[32];
824 pj_str_t tmpaddr;
825 int comp_id, prio, port;
826 pj_ice_sess_cand *cand;
827 pj_status_t status;
828
829 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
830 foundation,
831 &comp_id,
832 transport,
833 &prio,
834 ipaddr,
835 &port,
836 type);
837 if (cnt != 7) {
838 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
839 goto on_error;
840 }
841
842 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
843 pj_bzero(cand, sizeof(*cand));
844
845 if (strcmp(type, "host")==0)
847 else if (strcmp(type, "srflx")==0)
849 else if (strcmp(type, "relay")==0)
851 else {
852 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
853 type));
854 goto on_error;
855 }
856
857 cand->comp_id = (pj_uint8_t)comp_id;
858 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
859 cand->prio = prio;
860
861 if (strchr(ipaddr, ':'))
862 af = pj_AF_INET6();
863 else
864 af = pj_AF_INET();
865
866 tmpaddr = pj_str(ipaddr);
867 pj_sockaddr_init(af, &cand->addr, NULL, 0);
868 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
869 if (status != PJ_SUCCESS) {
870 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
871 ipaddr));
872 goto on_error;
873 }
874
876
877 ++icedemo.rem.cand_cnt;
878
879 if (cand->comp_id > icedemo.rem.comp_cnt)
880 icedemo.rem.comp_cnt = cand->comp_id;
881 }
882 }
883 break;
884 }
885 }
886
887 if (icedemo.rem.cand_cnt==0 ||
888 icedemo.rem.ufrag[0]==0 ||
889 icedemo.rem.pwd[0]==0 ||
890 icedemo.rem.comp_cnt == 0)
891 {
892 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
893 goto on_error;
894 }
895
896 if (comp0_port==0 || comp0_addr[0]=='\0') {
897 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
898 goto on_error;
899 } else {
900 int af;
901 pj_str_t tmp_addr;
902 pj_status_t status;
903
904 if (strchr(comp0_addr, ':'))
905 af = pj_AF_INET6();
906 else
907 af = pj_AF_INET();
908
909 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
910 tmp_addr = pj_str(comp0_addr);
911 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
912 &tmp_addr);
913 if (status != PJ_SUCCESS) {
914 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
915 goto on_error;
916 }
917 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
918 }
919
920 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
921 icedemo.rem.cand_cnt));
922 return;
923
924on_error:
925 reset_rem_info();
926}
927
928
929/*
930 * Start ICE negotiation! This function is invoked from the menu.
931 */
932static void icedemo_start_nego(void)
933{
934 pj_str_t rufrag, rpwd;
935 pj_status_t status;
936
937 if (icedemo.icest == NULL) {
938 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
939 return;
940 }
941
942 if (!pj_ice_strans_has_sess(icedemo.icest)) {
943 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
944 return;
945 }
946
947 if (icedemo.rem.cand_cnt == 0) {
948 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
949 return;
950 }
951
952 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
953
954 status = pj_ice_strans_start_ice(icedemo.icest,
955 pj_cstr(&rufrag, icedemo.rem.ufrag),
956 pj_cstr(&rpwd, icedemo.rem.pwd),
957 icedemo.rem.cand_cnt,
958 icedemo.rem.cand);
959 if (status != PJ_SUCCESS)
960 icedemo_perror("Error starting ICE", status);
961 else
962 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
963}
964
965
966/*
967 * Send application data to remote agent.
968 */
969static void icedemo_send_data(unsigned comp_id, const char *data)
970{
971 pj_status_t status;
972
973 if (icedemo.icest == NULL) {
974 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
975 return;
976 }
977
978 if (!pj_ice_strans_has_sess(icedemo.icest)) {
979 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
980 return;
981 }
982
983 /*
984 if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
985 PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
986 return;
987 }
988 */
989
990 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
991 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
992 return;
993 }
994
995 status = pj_ice_strans_sendto2(icedemo.icest, comp_id, data, strlen(data),
996 &icedemo.rem.def_addr[comp_id-1],
997 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
998 if (status != PJ_SUCCESS && status != PJ_EPENDING)
999 icedemo_perror("Error sending data", status);
1000 else
1001 PJ_LOG(3,(THIS_FILE, "Data sent/will be sent"));
1002}
1003
1004
1005/*
1006 * Display help for the menu.
1007 */
1008static void icedemo_help_menu(void)
1009{
1010 puts("");
1011 puts("-= Help on using ICE and this icedemo program =-");
1012 puts("");
1013 puts("This application demonstrates how to use ICE in pjnath without having\n"
1014 "to use the SIP protocol. To use this application, you will need to run\n"
1015 "two instances of this application, to simulate two ICE agents.\n");
1016
1017 puts("Basic ICE flow:\n"
1018 " create instance [menu \"c\"]\n"
1019 " repeat these steps as wanted:\n"
1020 " - init session as offerer or answerer [menu \"i\"]\n"
1021 " - display our SDP [menu \"s\"]\n"
1022 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
1023 " copy-pasting the SDP to the other icedemo application\n"
1024 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
1025 " instance [menu \"r\"]\n"
1026 " - begin ICE negotiation in our end [menu \"b\"], and \n"
1027 " - immediately begin ICE negotiation in the other icedemo instance\n"
1028 " - ICE negotiation will run, and result will be printed to screen\n"
1029 " - send application data to remote [menu \"x\"]\n"
1030 " - end/stop ICE session [menu \"e\"]\n"
1031 " destroy instance [menu \"d\"]\n"
1032 "");
1033
1034 puts("");
1035 puts("This concludes the help screen.");
1036 puts("");
1037}
1038
1039
1040/*
1041 * Display console menu
1042 */
1043static void icedemo_print_menu(void)
1044{
1045 puts("");
1046 puts("+----------------------------------------------------------------------+");
1047 puts("| M E N U |");
1048 puts("+---+------------------------------------------------------------------+");
1049 puts("| c | create Create the instance |");
1050 puts("| d | destroy Destroy the instance |");
1051 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
1052 puts("| e | stop End/stop ICE session |");
1053 puts("| s | show Display local ICE info |");
1054 puts("| r | remote Input remote ICE info |");
1055 puts("| b | start Begin ICE negotiation |");
1056 puts("| x | send <compid> .. Send data to remote |");
1057 puts("+---+------------------------------------------------------------------+");
1058 puts("| h | help * Help! * |");
1059 puts("| q | quit Quit |");
1060 puts("+----------------------------------------------------------------------+");
1061}
1062
1063
1064/*
1065 * Main console loop.
1066 */
1067static void icedemo_console(void)
1068{
1069 pj_bool_t app_quit = PJ_FALSE;
1070
1071 while (!app_quit) {
1072 char input[80], *cmd;
1073 const char *SEP = " \t\r\n";
1074 pj_size_t len;
1075
1076 icedemo_print_menu();
1077
1078 printf("Input: ");
1079 if (stdout) fflush(stdout);
1080
1081 pj_bzero(input, sizeof(input));
1082 if (fgets(input, sizeof(input), stdin) == NULL)
1083 break;
1084
1085 len = strlen(input);
1086 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1087 input[--len] = '\0';
1088
1089 cmd = strtok(input, SEP);
1090 if (!cmd)
1091 continue;
1092
1093 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
1094
1095 icedemo_create_instance();
1096
1097 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
1098
1099 icedemo_destroy_instance();
1100
1101 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
1102
1103 char *role = strtok(NULL, SEP);
1104 if (role)
1105 icedemo_init_session(*role);
1106 else
1107 puts("error: Role required");
1108
1109 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1110
1111 icedemo_stop_session();
1112
1113 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1114
1115 icedemo_show_ice();
1116
1117 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1118
1119 icedemo_input_remote();
1120
1121 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1122
1123 icedemo_start_nego();
1124
1125 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1126
1127 char *comp = strtok(NULL, SEP);
1128
1129 if (!comp) {
1130 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1131 } else {
1132 char *data = comp + strlen(comp) + 1;
1133 if (!data)
1134 data = "";
1135 icedemo_send_data(atoi(comp), data);
1136 }
1137
1138 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
1139
1140 icedemo_help_menu();
1141
1142 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
1143
1144 app_quit = PJ_TRUE;
1145
1146 } else {
1147
1148 printf("Invalid command '%s'\n", cmd);
1149
1150 }
1151 }
1152}
1153
1154
1155/*
1156 * Display program usage.
1157 */
1158static void icedemo_usage()
1159{
1160 puts("Usage: icedemo [optons]");
1161 printf("icedemo v%s by pjsip.org\n", pj_get_version());
1162 puts("");
1163 puts("General options:");
1164 puts(" --comp-cnt, -c N Component count (default=1)");
1165 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
1166 puts(" resolution");
1167 puts(" --max-host, -H N Set max number of host candidates to N");
1168 puts(" --regular, -R Use regular nomination (default aggressive)");
1169 puts(" --log-file, -L FILE Save output to log FILE");
1170 puts(" --help, -h Display this screen.");
1171 puts("");
1172 puts("STUN related options:");
1173 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
1174 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1175 puts(" name if DNS SRV resolution is used.");
1176 puts("");
1177 puts("TURN related options:");
1178 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
1179 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1180 puts(" name if DNS SRV resolution is used.");
1181 puts(" --turn-tcp, -T Use TCP to connect to TURN server");
1182 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
1183 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
1184 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
1185 puts("");
1186}
1187
1188
1189/*
1190 * And here's the main()
1191 */
1192int main(int argc, char *argv[])
1193{
1194 struct pj_getopt_option long_options[] = {
1195 { "comp-cnt", 1, 0, 'c'},
1196 { "nameserver", 1, 0, 'n'},
1197 { "max-host", 1, 0, 'H'},
1198 { "help", 0, 0, 'h'},
1199 { "stun-srv", 1, 0, 's'},
1200 { "turn-srv", 1, 0, 't'},
1201 { "turn-tcp", 0, 0, 'T'},
1202 { "turn-username", 1, 0, 'u'},
1203 { "turn-password", 1, 0, 'p'},
1204 { "turn-fingerprint", 0, 0, 'F'},
1205 { "regular", 0, 0, 'R'},
1206 { "log-file", 1, 0, 'L'},
1207 };
1208 int c, opt_id;
1209 pj_status_t status;
1210
1211 icedemo.opt.comp_cnt = 1;
1212 icedemo.opt.max_host = -1;
1213
1214 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
1215 switch (c) {
1216 case 'c':
1217 icedemo.opt.comp_cnt = atoi(pj_optarg);
1218 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
1219 puts("Invalid component count value");
1220 return 1;
1221 }
1222 break;
1223 case 'n':
1224 icedemo.opt.ns = pj_str(pj_optarg);
1225 break;
1226 case 'H':
1227 icedemo.opt.max_host = atoi(pj_optarg);
1228 break;
1229 case 'h':
1230 icedemo_usage();
1231 return 0;
1232 case 's':
1233 icedemo.opt.stun_srv = pj_str(pj_optarg);
1234 break;
1235 case 't':
1236 icedemo.opt.turn_srv = pj_str(pj_optarg);
1237 break;
1238 case 'T':
1239 icedemo.opt.turn_tcp = PJ_TRUE;
1240 break;
1241 case 'u':
1242 icedemo.opt.turn_username = pj_str(pj_optarg);
1243 break;
1244 case 'p':
1245 icedemo.opt.turn_password = pj_str(pj_optarg);
1246 break;
1247 case 'F':
1248 icedemo.opt.turn_fingerprint = PJ_TRUE;
1249 break;
1250 case 'R':
1251 icedemo.opt.regular = PJ_TRUE;
1252 break;
1253 case 'L':
1254 icedemo.opt.log_file = pj_optarg;
1255 break;
1256 default:
1257 printf("Argument \"%s\" is not valid. Use -h to see help",
1258 argv[pj_optind]);
1259 return 1;
1260 }
1261 }
1262
1263 status = icedemo_init();
1264 if (status != PJ_SUCCESS)
1265 return 1;
1266
1267 icedemo_console();
1268
1269 err_exit("Quitting..", PJ_SUCCESS);
1270 return 0;
1271}
const char * pj_get_version(void)
pj_status_t pjlib_util_init(void)
#define PJ_STUN_PORT
Definition: config.h:122
#define PJ_ICE_MAX_COMP
Definition: config.h:288
#define PJ_ICE_ST_MAX_CAND
Definition: config.h:250
pj_ice_sess_role
Definition: ice_session.h:553
const char * pj_ice_get_cand_type_name(pj_ice_cand_type type)
@ PJ_ICE_SESS_ROLE_CONTROLLING
Definition: ice_session.h:567
@ PJ_ICE_SESS_ROLE_CONTROLLED
Definition: ice_session.h:562
@ PJ_ICE_CAND_TYPE_RELAYED
Definition: ice_session.h:156
@ PJ_ICE_CAND_TYPE_HOST
Definition: ice_session.h:137
@ PJ_ICE_CAND_TYPE_SRFLX
Definition: ice_session.h:144
pj_bool_t pj_ice_strans_sess_is_complete(pj_ice_strans *ice_st)
pj_status_t pj_ice_strans_destroy(pj_ice_strans *ice_st)
pj_bool_t pj_ice_strans_sess_is_running(pj_ice_strans *ice_st)
pj_status_t pj_ice_strans_sendto2(pj_ice_strans *ice_st, unsigned comp_id, const void *data, pj_size_t data_len, const pj_sockaddr_t *dst_addr, int dst_addr_len)
pj_ice_sess_role pj_ice_strans_get_role(pj_ice_strans *ice_st)
pj_status_t pj_ice_strans_start_ice(pj_ice_strans *ice_st, const pj_str_t *rem_ufrag, const pj_str_t *rem_passwd, unsigned rcand_cnt, const pj_ice_sess_cand rcand[])
pj_bool_t pj_ice_strans_has_sess(pj_ice_strans *ice_st)
pj_status_t pj_ice_strans_enum_cands(pj_ice_strans *ice_st, unsigned comp_id, unsigned *count, pj_ice_sess_cand cand[])
pj_status_t pj_ice_strans_get_ufrag_pwd(pj_ice_strans *ice_st, pj_str_t *loc_ufrag, pj_str_t *loc_pwd, pj_str_t *rem_ufrag, pj_str_t *rem_pwd)
unsigned pj_ice_strans_get_running_comp_cnt(pj_ice_strans *ice_st)
pj_ice_strans_op
Definition: ice_strans.h:128
void pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg)
struct pj_ice_strans pj_ice_strans
Definition: ice_strans.h:124
pj_status_t pj_ice_strans_stop_ice(pj_ice_strans *ice_st)
pj_status_t pj_ice_strans_init_ice(pj_ice_strans *ice_st, pj_ice_sess_role role, const pj_str_t *local_ufrag, const pj_str_t *local_passwd)
pj_status_t pj_ice_strans_create(const char *name, const pj_ice_strans_cfg *cfg, unsigned comp_cnt, void *user_data, const pj_ice_strans_cb *cb, pj_ice_strans **p_ice_st)
pj_status_t pj_ice_strans_get_def_cand(pj_ice_strans *ice_st, unsigned comp_id, pj_ice_sess_cand *cand)
@ PJ_ICE_STRANS_OP_INIT
Definition: ice_strans.h:130
@ PJ_ICE_STRANS_OP_NEGOTIATION
Definition: ice_strans.h:133
@ PJ_STUN_AUTH_CRED_STATIC
Definition: stun_auth.h:74
@ PJ_STUN_PASSWD_PLAIN
Definition: stun_auth.h:95
@ PJ_TURN_TP_TCP
Definition: turn_session.h:148
@ PJ_TURN_TP_UDP
Definition: turn_session.h:143
pj_status_t pjnath_init(void)
unsigned char pj_uint8_t
pj_status_t pj_init(void)
int pj_bool_t
unsigned short pj_uint16_t
size_t pj_size_t
void pj_sockaddr_t
int pj_status_t
struct pj_thread_t pj_thread_t
#define PJ_ARRAY_SIZE(a)
void pj_shutdown(void)
PJ_SUCCESS
PJ_TRUE
PJ_FALSE
void pj_caching_pool_destroy(pj_caching_pool *ch_pool)
void pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity)
pj_status_t pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, const pj_str_t servers[], const pj_uint16_t ports[])
pj_status_t pj_dns_resolver_create(pj_pool_factory *pf, const char *name, unsigned options, pj_timer_heap_t *timer, pj_ioqueue_t *ioqueue, pj_dns_resolver **p_resolver)
pj_status_t pj_ioqueue_destroy(pj_ioqueue_t *ioque)
pj_status_t pj_ioqueue_create(pj_pool_t *pool, pj_size_t max_fd, pj_ioqueue_t **ioqueue)
int pj_ioqueue_poll(pj_ioqueue_t *ioque, const pj_time_val *timeout)
void pj_log_write(int level, const char *buffer, int len)
#define PJ_LOG(level, arg)
void pj_log_set_log_func(pj_log_func *func)
pj_pool_t * pj_pool_create(pj_pool_factory *factory, const char *name, pj_size_t initial_size, pj_size_t increment_size, pj_pool_callback *callback)
pj_str_t pj_str(char *str)
pj_str_t * pj_strdup2(pj_pool_t *pool, pj_str_t *dst, const char *src)
const pj_str_t * pj_cstr(pj_str_t *str, const char *s)
char * pj_strchr(const pj_str_t *str, int chr)
void pj_bzero(void *dst, pj_size_t size)
pj_status_t pj_sockaddr_init(int af, pj_sockaddr *addr, const pj_str_t *cp, pj_uint16_t port)
char * pj_sockaddr_print(const pj_sockaddr_t *addr, char *buf, int size, unsigned flags)
#define PJ_INET6_ADDRSTRLEN
pj_uint16_t pj_sockaddr_get_port(const pj_sockaddr_t *addr)
pj_status_t pj_sockaddr_set_port(pj_sockaddr *addr, pj_uint16_t hostport)
#define pj_AF_INET6()
#define pj_AF_INET()
pj_status_t pj_sockaddr_set_str_addr(int af, pj_sockaddr *addr, const pj_str_t *cp)
unsigned pj_sockaddr_get_len(const pj_sockaddr_t *addr)
pj_status_t pj_thread_destroy(pj_thread_t *thread)
pj_status_t pj_thread_join(pj_thread_t *thread)
pj_status_t pj_thread_create(pj_pool_t *pool, const char *thread_name, pj_thread_proc *proc, void *arg, pj_size_t stack_size, unsigned flags, pj_thread_t **thread)
pj_status_t pj_thread_sleep(unsigned msec)
unsigned pj_timer_heap_poll(pj_timer_heap_t *ht, pj_time_val *next_delay)
void pj_timer_heap_destroy(pj_timer_heap_t *ht)
pj_status_t pj_timer_heap_create(pj_pool_t *pool, pj_size_t count, pj_timer_heap_t **ht)
#define PJ_TIME_VAL_GT(t1, t2)
#define PJ_TIME_VAL_MSEC(t)
#define pj_assert(expr)
#define PJ_UNUSED_ARG(arg)
int pj_tolower(unsigned char c)
int pj_isspace(unsigned char c)
#define PJ_ERR_MSG_SIZE
pj_str_t pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize)
pj_status_t pj_get_netos_error(void)
#define PJ_ETOOSMALL
Definition: ice_session.h:242
pj_sockaddr addr
Definition: ice_session.h:302
pj_str_t foundation
Definition: ice_session.h:284
pj_ice_cand_type type
Definition: ice_session.h:251
pj_uint32_t prio
Definition: ice_session.h:291
pj_uint8_t comp_id
Definition: ice_session.h:266
Definition: ice_strans.h:151
void(* on_rx_data)(pj_ice_strans *ice_st, unsigned comp_id, void *pkt, pj_size_t size, const pj_sockaddr_t *src_addr, unsigned src_addr_len)
Definition: ice_strans.h:164
void(* on_ice_complete)(pj_ice_strans *ice_st, pj_ice_strans_op op, pj_status_t status)
Definition: ice_strans.h:198
Definition: ice_strans.h:386
pj_ssize_t slen
char * ptr

.

 


PJNATH - Open Source NAT traversal helper library supporting STUN, TURN, and ICE
Copyright (C) 2006-2009 Teluu Inc.