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