blob: 9d811374a17ca05ed4e10221fcf648f86442a987 [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id$ */
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(...) \
518 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
519 __VA_ARGS__); \
520 if (printed <= 0 || printed >= (int)(maxlen - (p-buffer))) \
521 return -PJ_ETOOSMALL; \
522 p += printed
523
524
525/* Utility to create a=candidate SDP attribute */
526static int print_cand(char buffer[], unsigned maxlen,
527 const pj_ice_sess_cand *cand)
528{
529 char ipaddr[PJ_INET6_ADDRSTRLEN];
530 char *p = buffer;
531 int printed;
532
533 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
534 (int)cand->foundation.slen,
535 cand->foundation.ptr,
536 (unsigned)cand->comp_id,
537 cand->prio,
538 pj_sockaddr_print(&cand->addr, ipaddr,
539 sizeof(ipaddr), 0),
540 (unsigned)pj_sockaddr_get_port(&cand->addr));
541
542 PRINT("%s\n",
543 pj_ice_get_cand_type_name(cand->type));
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
567 /* Get ufrag and pwd from current session */
568 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
569 NULL, NULL);
570
571 /* Write the a=ice-ufrag and a=ice-pwd attributes */
572 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
573 (int)local_ufrag.slen,
574 local_ufrag.ptr,
575 (int)local_pwd.slen,
576 local_pwd.ptr);
577
578 /* Write each component */
579 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
580 unsigned j, cand_cnt;
581 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
582 char ipaddr[PJ_INET6_ADDRSTRLEN];
583
584 /* Get default candidate for the component */
585 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
586 if (status != PJ_SUCCESS)
587 return -status;
588
589 /* Write the default address */
590 if (comp==0) {
591 /* For component 1, default address is in m= and c= lines */
592 PRINT("m=audio %d RTP/AVP 0\n"
593 "c=IN IP4 %s\n",
594 (int)pj_sockaddr_get_port(&cand[0].addr),
595 pj_sockaddr_print(&cand[0].addr, ipaddr,
596 sizeof(ipaddr), 0));
597 } else if (comp==1) {
598 /* For component 2, default address is in a=rtcp line */
599 PRINT("a=rtcp:%d IN IP4 %s\n",
600 (int)pj_sockaddr_get_port(&cand[0].addr),
601 pj_sockaddr_print(&cand[0].addr, ipaddr,
602 sizeof(ipaddr), 0));
603 } else {
604 /* For other components, we'll just invent this.. */
605 PRINT("a=Xice-defcand:%d IN IP4 %s\n",
606 (int)pj_sockaddr_get_port(&cand[0].addr),
607 pj_sockaddr_print(&cand[0].addr, ipaddr,
608 sizeof(ipaddr), 0));
609 }
610
611 /* Enumerate all candidates for this component */
612 cand_cnt = PJ_ARRAY_SIZE(cand);
613 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
614 &cand_cnt, cand);
615 if (status != PJ_SUCCESS)
616 return -status;
617
618 /* And encode the candidates as SDP */
619 for (j=0; j<cand_cnt; ++j) {
620 printed = print_cand(p, maxlen - (unsigned)(p-buffer), &cand[j]);
621 if (printed < 0)
622 return -PJ_ETOOSMALL;
623 p += printed;
624 }
625 }
626
627 if (p == buffer+maxlen)
628 return -PJ_ETOOSMALL;
629
630 *p = '\0';
631 return (int)(p - buffer);
632}
633
634
635/*
636 * Show information contained in the ICE stream transport. This is
637 * invoked from the menu.
638 */
639static void icedemo_show_ice(void)
640{
641 static char buffer[1000];
642 int len;
643
644 if (icedemo.icest == NULL) {
645 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
646 return;
647 }
648
649 puts("General info");
650 puts("---------------");
651 printf("Component count : %d\n", icedemo.opt.comp_cnt);
652 printf("Status : ");
653 if (pj_ice_strans_sess_is_complete(icedemo.icest))
654 puts("negotiation complete");
655 else if (pj_ice_strans_sess_is_running(icedemo.icest))
656 puts("negotiation is in progress");
657 else if (pj_ice_strans_has_sess(icedemo.icest))
658 puts("session ready");
659 else
660 puts("session not created");
661
662 if (!pj_ice_strans_has_sess(icedemo.icest)) {
663 puts("Create the session first to see more info");
664 return;
665 }
666
667 printf("Negotiated comp_cnt: %d\n",
668 pj_ice_strans_get_running_comp_cnt(icedemo.icest));
669 printf("Role : %s\n",
670 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
671 "controlled" : "controlling");
672
673 len = encode_session(buffer, sizeof(buffer));
674 if (len < 0)
675 err_exit("not enough buffer to show ICE status", -len);
676
677 puts("");
678 printf("Local SDP (paste this to remote host):\n"
679 "--------------------------------------\n"
680 "%s\n", buffer);
681
682
683 puts("");
684 puts("Remote info:\n"
685 "----------------------");
686 if (icedemo.rem.cand_cnt==0) {
687 puts("No remote info yet");
688 } else {
689 unsigned i;
690
691 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
692 printf("Remote password : %s\n", icedemo.rem.pwd);
693 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
694
695 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
696 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
697 if (len < 0)
698 err_exit("not enough buffer to show ICE status", -len);
699
700 printf(" %s", buffer);
701 }
702 }
703}
704
705
706/*
707 * Input and parse SDP from the remote (containing remote's ICE information)
708 * and save it to global variables.
709 */
710static void icedemo_input_remote(void)
711{
712 char linebuf[80];
713 unsigned media_cnt = 0;
714 unsigned comp0_port = 0;
715 char comp0_addr[80];
716 pj_bool_t done = PJ_FALSE;
717
718 puts("Paste SDP from remote host, end with empty line");
719
720 reset_rem_info();
721
722 comp0_addr[0] = '\0';
723
724 while (!done) {
725 pj_size_t len;
726 char *line;
727
728 printf(">");
729 if (stdout) fflush(stdout);
730
731 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
732 break;
733
734 len = strlen(linebuf);
735 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
736 linebuf[--len] = '\0';
737
738 line = linebuf;
739 while (len && pj_isspace(*line))
740 ++line, --len;
741
742 if (len==0)
743 break;
744
745 /* Ignore subsequent media descriptors */
746 if (media_cnt > 1)
747 continue;
748
749 switch (line[0]) {
750 case 'm':
751 {
752 int cnt;
753 char media[32], portstr[32];
754
755 ++media_cnt;
756 if (media_cnt > 1) {
757 puts("Media line ignored");
758 break;
759 }
760
761 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
762 if (cnt != 2) {
763 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
764 goto on_error;
765 }
766
767 comp0_port = atoi(portstr);
768
769 }
770 break;
771 case 'c':
772 {
773 int cnt;
774 char c[32], net[32], ip[80];
775
776 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
777 if (cnt != 3) {
778 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
779 goto on_error;
780 }
781
782 strcpy(comp0_addr, ip);
783 }
784 break;
785 case 'a':
786 {
787 char *attr = strtok(line+2, ": \t\r\n");
788 if (strcmp(attr, "ice-ufrag")==0) {
789 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
790 } else if (strcmp(attr, "ice-pwd")==0) {
791 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
792 } else if (strcmp(attr, "rtcp")==0) {
793 char *val = attr+strlen(attr)+1;
794 int af, cnt;
795 int port;
796 char net[32], ip[64];
797 pj_str_t tmp_addr;
798 pj_status_t status;
799
800 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
801 if (cnt != 3) {
802 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
803 goto on_error;
804 }
805
806 if (strchr(ip, ':'))
807 af = pj_AF_INET6();
808 else
809 af = pj_AF_INET();
810
811 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
812 tmp_addr = pj_str(ip);
813 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
814 &tmp_addr);
815 if (status != PJ_SUCCESS) {
816 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
817 goto on_error;
818 }
819 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
820
821 } else if (strcmp(attr, "candidate")==0) {
822 char *sdpcand = attr+strlen(attr)+1;
823 int af, cnt;
824 char foundation[32], transport[12], ipaddr[80], type[32];
825 pj_str_t tmpaddr;
826 int comp_id, prio, port;
827 pj_ice_sess_cand *cand;
828 pj_status_t status;
829
830 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
831 foundation,
832 &comp_id,
833 transport,
834 &prio,
835 ipaddr,
836 &port,
837 type);
838 if (cnt != 7) {
839 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
840 goto on_error;
841 }
842
843 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
844 pj_bzero(cand, sizeof(*cand));
845
846 if (strcmp(type, "host")==0)
847 cand->type = PJ_ICE_CAND_TYPE_HOST;
848 else if (strcmp(type, "srflx")==0)
849 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
850 else if (strcmp(type, "relay")==0)
851 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
852 else {
853 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
854 type));
855 goto on_error;
856 }
857
858 cand->comp_id = (pj_uint8_t)comp_id;
859 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
860 cand->prio = prio;
861
862 if (strchr(ipaddr, ':'))
863 af = pj_AF_INET6();
864 else
865 af = pj_AF_INET();
866
867 tmpaddr = pj_str(ipaddr);
868 pj_sockaddr_init(af, &cand->addr, NULL, 0);
869 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
870 if (status != PJ_SUCCESS) {
871 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
872 ipaddr));
873 goto on_error;
874 }
875
876 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
877
878 ++icedemo.rem.cand_cnt;
879
880 if (cand->comp_id > icedemo.rem.comp_cnt)
881 icedemo.rem.comp_cnt = cand->comp_id;
882 }
883 }
884 break;
885 }
886 }
887
888 if (icedemo.rem.cand_cnt==0 ||
889 icedemo.rem.ufrag[0]==0 ||
890 icedemo.rem.pwd[0]==0 ||
891 icedemo.rem.comp_cnt == 0)
892 {
893 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
894 goto on_error;
895 }
896
897 if (comp0_port==0 || comp0_addr[0]=='\0') {
898 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
899 goto on_error;
900 } else {
901 int af;
902 pj_str_t tmp_addr;
903 pj_status_t status;
904
905 if (strchr(comp0_addr, ':'))
906 af = pj_AF_INET6();
907 else
908 af = pj_AF_INET();
909
910 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
911 tmp_addr = pj_str(comp0_addr);
912 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
913 &tmp_addr);
914 if (status != PJ_SUCCESS) {
915 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
916 goto on_error;
917 }
918 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
919 }
920
921 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
922 icedemo.rem.cand_cnt));
923 return;
924
925on_error:
926 reset_rem_info();
927}
928
929
930/*
931 * Start ICE negotiation! This function is invoked from the menu.
932 */
933static void icedemo_start_nego(void)
934{
935 pj_str_t rufrag, rpwd;
936 pj_status_t status;
937
938 if (icedemo.icest == NULL) {
939 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
940 return;
941 }
942
943 if (!pj_ice_strans_has_sess(icedemo.icest)) {
944 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
945 return;
946 }
947
948 if (icedemo.rem.cand_cnt == 0) {
949 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
950 return;
951 }
952
953 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
954
955 status = pj_ice_strans_start_ice(icedemo.icest,
956 pj_cstr(&rufrag, icedemo.rem.ufrag),
957 pj_cstr(&rpwd, icedemo.rem.pwd),
958 icedemo.rem.cand_cnt,
959 icedemo.rem.cand);
960 if (status != PJ_SUCCESS)
961 icedemo_perror("Error starting ICE", status);
962 else
963 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
964}
965
966
967/*
968 * Send application data to remote agent.
969 */
970static void icedemo_send_data(unsigned comp_id, const char *data)
971{
972 pj_status_t status;
973
974 if (icedemo.icest == NULL) {
975 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
976 return;
977 }
978
979 if (!pj_ice_strans_has_sess(icedemo.icest)) {
980 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
981 return;
982 }
983
984 /*
985 if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
986 PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
987 return;
988 }
989 */
990
991 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
992 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
993 return;
994 }
995
996 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
997 &icedemo.rem.def_addr[comp_id-1],
998 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
999 if (status != PJ_SUCCESS)
1000 icedemo_perror("Error sending data", status);
1001 else
1002 PJ_LOG(3,(THIS_FILE, "Data sent"));
1003}
1004
1005
1006/*
1007 * Display help for the menu.
1008 */
1009static void icedemo_help_menu(void)
1010{
1011 puts("");
1012 puts("-= Help on using ICE and this icedemo program =-");
1013 puts("");
1014 puts("This application demonstrates how to use ICE in pjnath without having\n"
1015 "to use the SIP protocol. To use this application, you will need to run\n"
1016 "two instances of this application, to simulate two ICE agents.\n");
1017
1018 puts("Basic ICE flow:\n"
1019 " create instance [menu \"c\"]\n"
1020 " repeat these steps as wanted:\n"
1021 " - init session as offerer or answerer [menu \"i\"]\n"
1022 " - display our SDP [menu \"s\"]\n"
1023 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
1024 " copy-pasting the SDP to the other icedemo application\n"
1025 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
1026 " instance [menu \"r\"]\n"
1027 " - begin ICE negotiation in our end [menu \"b\"], and \n"
1028 " - immediately begin ICE negotiation in the other icedemo instance\n"
1029 " - ICE negotiation will run, and result will be printed to screen\n"
1030 " - send application data to remote [menu \"x\"]\n"
1031 " - end/stop ICE session [menu \"e\"]\n"
1032 " destroy instance [menu \"d\"]\n"
1033 "");
1034
1035 puts("");
1036 puts("This concludes the help screen.");
1037 puts("");
1038}
1039
1040
1041/*
1042 * Display console menu
1043 */
1044static void icedemo_print_menu(void)
1045{
1046 puts("");
1047 puts("+----------------------------------------------------------------------+");
1048 puts("| M E N U |");
1049 puts("+---+------------------------------------------------------------------+");
1050 puts("| c | create Create the instance |");
1051 puts("| d | destroy Destroy the instance |");
1052 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
1053 puts("| e | stop End/stop ICE session |");
1054 puts("| s | show Display local ICE info |");
1055 puts("| r | remote Input remote ICE info |");
1056 puts("| b | start Begin ICE negotiation |");
1057 puts("| x | send <compid> .. Send data to remote |");
1058 puts("+---+------------------------------------------------------------------+");
1059 puts("| h | help * Help! * |");
1060 puts("| q | quit Quit |");
1061 puts("+----------------------------------------------------------------------+");
1062}
1063
1064
1065/*
1066 * Main console loop.
1067 */
1068static void icedemo_console(void)
1069{
1070 pj_bool_t app_quit = PJ_FALSE;
1071
1072 while (!app_quit) {
1073 char input[80], *cmd;
1074 const char *SEP = " \t\r\n";
1075 pj_size_t len;
1076
1077 icedemo_print_menu();
1078
1079 printf("Input: ");
1080 if (stdout) fflush(stdout);
1081
1082 pj_bzero(input, sizeof(input));
1083 if (fgets(input, sizeof(input), stdin) == NULL)
1084 break;
1085
1086 len = strlen(input);
1087 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1088 input[--len] = '\0';
1089
1090 cmd = strtok(input, SEP);
1091 if (!cmd)
1092 continue;
1093
1094 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
1095
1096 icedemo_create_instance();
1097
1098 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
1099
1100 icedemo_destroy_instance();
1101
1102 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
1103
1104 char *role = strtok(NULL, SEP);
1105 if (role)
1106 icedemo_init_session(*role);
1107 else
1108 puts("error: Role required");
1109
1110 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1111
1112 icedemo_stop_session();
1113
1114 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1115
1116 icedemo_show_ice();
1117
1118 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1119
1120 icedemo_input_remote();
1121
1122 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1123
1124 icedemo_start_nego();
1125
1126 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1127
1128 char *comp = strtok(NULL, SEP);
1129
1130 if (!comp) {
1131 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1132 } else {
1133 char *data = comp + strlen(comp) + 1;
1134 if (!data)
1135 data = "";
1136 icedemo_send_data(atoi(comp), data);
1137 }
1138
1139 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
1140
1141 icedemo_help_menu();
1142
1143 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
1144
1145 app_quit = PJ_TRUE;
1146
1147 } else {
1148
1149 printf("Invalid command '%s'\n", cmd);
1150
1151 }
1152 }
1153}
1154
1155
1156/*
1157 * Display program usage.
1158 */
1159static void icedemo_usage()
1160{
1161 puts("Usage: icedemo [optons]");
1162 printf("icedemo v%s by pjsip.org\n", pj_get_version());
1163 puts("");
1164 puts("General options:");
1165 puts(" --comp-cnt, -c N Component count (default=1)");
1166 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
1167 puts(" resolution");
1168 puts(" --max-host, -H N Set max number of host candidates to N");
1169 puts(" --regular, -R Use regular nomination (default aggressive)");
1170 puts(" --log-file, -L FILE Save output to log FILE");
1171 puts(" --help, -h Display this screen.");
1172 puts("");
1173 puts("STUN related options:");
1174 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
1175 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1176 puts(" name if DNS SRV resolution is used.");
1177 puts("");
1178 puts("TURN related options:");
1179 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN 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(" --turn-tcp, -T Use TCP to connect to TURN server");
1183 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
1184 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
1185 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
1186 puts("");
1187}
1188
1189
1190/*
1191 * And here's the main()
1192 */
1193int main(int argc, char *argv[])
1194{
1195 struct pj_getopt_option long_options[] = {
1196 { "comp-cnt", 1, 0, 'c'},
1197 { "nameserver", 1, 0, 'n'},
1198 { "max-host", 1, 0, 'H'},
1199 { "help", 0, 0, 'h'},
1200 { "stun-srv", 1, 0, 's'},
1201 { "turn-srv", 1, 0, 't'},
1202 { "turn-tcp", 0, 0, 'T'},
1203 { "turn-username", 1, 0, 'u'},
1204 { "turn-password", 1, 0, 'p'},
1205 { "turn-fingerprint", 0, 0, 'F'},
1206 { "regular", 0, 0, 'R'},
1207 { "log-file", 1, 0, 'L'},
1208 };
1209 int c, opt_id;
1210 pj_status_t status;
1211
1212 icedemo.opt.comp_cnt = 1;
1213 icedemo.opt.max_host = -1;
1214
1215 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
1216 switch (c) {
1217 case 'c':
1218 icedemo.opt.comp_cnt = atoi(pj_optarg);
1219 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
1220 puts("Invalid component count value");
1221 return 1;
1222 }
1223 break;
1224 case 'n':
1225 icedemo.opt.ns = pj_str(pj_optarg);
1226 break;
1227 case 'H':
1228 icedemo.opt.max_host = atoi(pj_optarg);
1229 break;
1230 case 'h':
1231 icedemo_usage();
1232 return 0;
1233 case 's':
1234 icedemo.opt.stun_srv = pj_str(pj_optarg);
1235 break;
1236 case 't':
1237 icedemo.opt.turn_srv = pj_str(pj_optarg);
1238 break;
1239 case 'T':
1240 icedemo.opt.turn_tcp = PJ_TRUE;
1241 break;
1242 case 'u':
1243 icedemo.opt.turn_username = pj_str(pj_optarg);
1244 break;
1245 case 'p':
1246 icedemo.opt.turn_password = pj_str(pj_optarg);
1247 break;
1248 case 'F':
1249 icedemo.opt.turn_fingerprint = PJ_TRUE;
1250 break;
1251 case 'R':
1252 icedemo.opt.regular = PJ_TRUE;
1253 break;
1254 case 'L':
1255 icedemo.opt.log_file = pj_optarg;
1256 break;
1257 default:
1258 printf("Argument \"%s\" is not valid. Use -h to see help",
1259 argv[pj_optind]);
1260 return 1;
1261 }
1262 }
1263
1264 status = icedemo_init();
1265 if (status != PJ_SUCCESS)
1266 return 1;
1267
1268 icedemo_console();
1269
1270 err_exit("Quitting..", PJ_SUCCESS);
1271 return 0;
1272}