1
/* $Id: icedemo.c 4217 2012-07-27 17:24:12Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
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.
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.
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
22
#include <pjlib-util.h>
26
#define THIS_FILE "icedemo.c"
28
/* For this demo app, configure longer STUN keep-alive time
29
* so that it does't clutter the screen output.
31
#define KA_INTERVAL 300
34
/* This is our global variables */
37
/* Command line options are stored here */
47
pj_str_t turn_username;
48
pj_str_t turn_password;
49
pj_bool_t turn_fingerprint;
53
/* Our global variables */
57
pj_bool_t thread_quit_flag;
58
pj_ice_strans_cfg ice_cfg;
62
/* Variables to store parsed remote ICE info */
68
pj_sockaddr def_addr[PJ_ICE_MAX_COMP];
70
pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
75
/* Utility to display error messages */
76
static void icedemo_perror(const char *title, pj_status_t status)
78
char errmsg[PJ_ERR_MSG_SIZE];
80
pj_strerror(status, errmsg, sizeof(errmsg));
81
PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
84
/* Utility: display error message and exit application (usually
85
* because of fatal error.
87
static void err_exit(const char *title, pj_status_t status)
89
if (status != PJ_SUCCESS) {
90
icedemo_perror(title, status);
92
PJ_LOG(3,(THIS_FILE, "Shutting down.."));
95
pj_ice_strans_destroy(icedemo.icest);
99
icedemo.thread_quit_flag = PJ_TRUE;
100
if (icedemo.thread) {
101
pj_thread_join(icedemo.thread);
102
pj_thread_destroy(icedemo.thread);
105
if (icedemo.ice_cfg.stun_cfg.ioqueue)
106
pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
108
if (icedemo.ice_cfg.stun_cfg.timer_heap)
109
pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
111
pj_caching_pool_destroy(&icedemo.cp);
115
if (icedemo.log_fhnd) {
116
fclose(icedemo.log_fhnd);
117
icedemo.log_fhnd = NULL;
120
exit(status != PJ_SUCCESS);
123
#define CHECK(expr) status=expr; \
124
if (status!=PJ_SUCCESS) { \
125
err_exit(#expr, status); \
129
* This function checks for events from both timer and ioqueue (for
130
* network events). It is invoked by the worker thread.
132
static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
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;
140
max_timeout.msec = max_msec;
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 );
148
/* timer_heap_poll should never ever returns negative value, or otherwise
149
* ioqueue_poll() will block forever!
151
pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
152
if (timeout.msec >= 1000) timeout.msec = 999;
154
/* compare the value with the timeout to wait from timer, and use the
157
if (PJ_TIME_VAL_GT(timeout, max_timeout))
158
timeout = max_timeout;
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.
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.
172
c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
174
pj_status_t err = pj_get_netos_error();
175
pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
182
net_event_count += c;
183
timeout.sec = timeout.msec = 0;
185
} while (c > 0 && net_event_count < MAX_NET_EVENTS);
187
count += net_event_count;
196
* This is the worker thread that polls event in the background.
198
static int icedemo_worker_thread(void *unused)
200
PJ_UNUSED_ARG(unused);
202
while (!icedemo.thread_quit_flag) {
203
handle_events(500, NULL);
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).
215
static void cb_on_rx_data(pj_ice_strans *ice_st,
217
void *pkt, pj_size_t size,
218
const pj_sockaddr_t *src_addr,
219
unsigned src_addr_len)
221
char ipstr[PJ_INET6_ADDRSTRLEN+10];
223
PJ_UNUSED_ARG(ice_st);
224
PJ_UNUSED_ARG(src_addr_len);
227
// Don't do this! It will ruin the packet buffer in case TCP is used!
228
//((char*)pkt)[size] = '\0';
230
PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
232
pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
238
* This is the callback that is registered to the ICE stream transport to
239
* receive notification about ICE state progression.
241
static void cb_on_ice_complete(pj_ice_strans *ice_st,
246
(op==PJ_ICE_STRANS_OP_INIT? "initialization" :
247
(op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
249
if (status == PJ_SUCCESS) {
250
PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
252
char errmsg[PJ_ERR_MSG_SIZE];
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;
261
/* log callback to write to file */
262
static void log_func(int level, const char *data, int len)
264
pj_log_write(level, data, len);
265
if (icedemo.log_fhnd) {
266
if (fwrite(data, len, 1, icedemo.log_fhnd) != 1)
272
* This is the main application initialization function. It is called
273
* once (and only once) during application initialization sequence by
276
static pj_status_t icedemo_init(void)
280
if (icedemo.opt.log_file) {
281
icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
282
pj_log_set_log_func(&log_func);
285
/* Initialize the libraries before anything else */
287
CHECK( pjlib_util_init() );
288
CHECK( pjnath_init() );
290
/* Must create pool factory, where memory allocations come from */
291
pj_caching_pool_init(&icedemo.cp, NULL, 0);
293
/* Init our ICE settings with null values */
294
pj_ice_strans_cfg_default(&icedemo.ice_cfg);
296
icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
298
/* Create application memory pool */
299
icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
302
/* Create timer heap for timer stuff */
303
CHECK( pj_timer_heap_create(icedemo.pool, 100,
304
&icedemo.ice_cfg.stun_cfg.timer_heap) );
306
/* and create ioqueue for network I/O stuff */
307
CHECK( pj_ioqueue_create(icedemo.pool, 16,
308
&icedemo.ice_cfg.stun_cfg.ioqueue) );
310
/* something must poll the timer heap and ioqueue,
311
* unless we're on Symbian where the timer heap and ioqueue run
314
CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
315
NULL, 0, 0, &icedemo.thread) );
317
icedemo.ice_cfg.af = pj_AF_INET();
319
/* Create DNS resolver if nameserver is set */
320
if (icedemo.opt.ns.slen) {
321
CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
324
icedemo.ice_cfg.stun_cfg.timer_heap,
325
icedemo.ice_cfg.stun_cfg.ioqueue,
326
&icedemo.ice_cfg.resolver) );
328
CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
329
&icedemo.opt.ns, NULL) );
332
/* -= Start initializing ICE stream transport config =- */
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;
338
/* Nomination strategy */
339
if (icedemo.opt.regular)
340
icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
342
icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
344
/* Configure STUN/srflx candidate resolution */
345
if (icedemo.opt.stun_srv.slen) {
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);
353
icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
355
icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
356
icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
359
/* For this demo app, configure longer STUN keep-alive time
360
* so that it does't clutter the screen output.
362
icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
365
/* Configure TURN candidate */
366
if (icedemo.opt.turn_srv.slen) {
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);
374
icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
376
icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
377
icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
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;
386
/* Connection type to TURN server */
387
if (icedemo.opt.turn_tcp)
388
icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
390
icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
392
/* For this demo app, configure longer keep-alive time
393
* so that it does't clutter the screen output.
395
icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
398
/* -= That's it for now, initialization is complete =- */
404
* Create ICE stream transport instance, invoked from the menu.
406
static void icedemo_create_instance(void)
408
pj_ice_strans_cb icecb;
411
if (icedemo.icest != NULL) {
412
puts("ICE instance already created, destroy it first");
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;
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 */
429
if (status != PJ_SUCCESS)
430
icedemo_perror("error creating ice", status);
432
PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
435
/* Utility to nullify parsed remote info */
436
static void reset_rem_info(void)
438
pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
443
* Destroy ICE stream transport instance, invoked from the menu.
445
static void icedemo_destroy_instance(void)
447
if (icedemo.icest == NULL) {
448
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
452
pj_ice_strans_destroy(icedemo.icest);
453
icedemo.icest = NULL;
457
PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
462
* Create ICE session, invoked from the menu.
464
static void icedemo_init_session(unsigned rolechar)
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);
471
if (icedemo.icest == NULL) {
472
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
476
if (pj_ice_strans_has_sess(icedemo.icest)) {
477
PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
481
status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
482
if (status != PJ_SUCCESS)
483
icedemo_perror("error creating session", status);
485
PJ_LOG(3,(THIS_FILE, "ICE session created"));
492
* Stop/destroy ICE session, invoked from the menu.
494
static void icedemo_stop_session(void)
498
if (icedemo.icest == NULL) {
499
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
503
if (!pj_ice_strans_has_sess(icedemo.icest)) {
504
PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
508
status = pj_ice_strans_stop_ice(icedemo.icest);
509
if (status != PJ_SUCCESS)
510
icedemo_perror("error stopping session", status);
512
PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
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; \
524
/* Utility to create a=candidate SDP attribute */
525
static int print_cand(char buffer[], unsigned maxlen,
526
const pj_ice_sess_cand *cand)
528
char ipaddr[PJ_INET6_ADDRSTRLEN];
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,
537
pj_sockaddr_print(&cand->addr, ipaddr,
539
(unsigned)pj_sockaddr_get_port(&cand->addr));
542
pj_ice_get_cand_type_name(cand->type),
545
if (p == buffer+maxlen)
546
return -PJ_ETOOSMALL;
554
* Encode ICE information in SDP.
556
static int encode_session(char buffer[], unsigned maxlen)
561
pj_str_t local_ufrag, local_pwd;
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",
568
/* Get ufrag and pwd from current session */
569
pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
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,
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];
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)
591
/* Write the default address */
593
/* For component 1, default address is in m= and c= lines */
594
PRINT("m=audio %d RTP/AVP 0\n"
596
(int)pj_sockaddr_get_port(&cand[0].addr),
597
pj_sockaddr_print(&cand[0].addr, ipaddr,
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,
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,
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,
620
if (status != PJ_SUCCESS)
623
/* And encode the candidates as SDP */
624
for (j=0; j<cand_cnt; ++j) {
625
printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
627
return -PJ_ETOOSMALL;
632
if (p == buffer+maxlen)
633
return -PJ_ETOOSMALL;
641
* Show information contained in the ICE stream transport. This is
642
* invoked from the menu.
644
static void icedemo_show_ice(void)
646
static char buffer[1000];
649
if (icedemo.icest == NULL) {
650
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
654
puts("General info");
655
puts("---------------");
656
printf("Component count : %d\n", icedemo.opt.comp_cnt);
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");
665
puts("session not created");
667
if (!pj_ice_strans_has_sess(icedemo.icest)) {
668
puts("Create the session first to see more info");
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");
678
len = encode_session(buffer, sizeof(buffer));
680
err_exit("not enough buffer to show ICE status", -len);
683
printf("Local SDP (paste this to remote host):\n"
684
"--------------------------------------\n"
689
puts("Remote info:\n"
690
"----------------------");
691
if (icedemo.rem.cand_cnt==0) {
692
puts("No remote info yet");
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);
700
for (i=0; i<icedemo.rem.cand_cnt; ++i) {
701
len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
703
err_exit("not enough buffer to show ICE status", -len);
705
printf(" %s", buffer);
712
* Input and parse SDP from the remote (containing remote's ICE information)
713
* and save it to global variables.
715
static void icedemo_input_remote(void)
718
unsigned media_cnt = 0;
719
unsigned comp0_port = 0;
721
pj_bool_t done = PJ_FALSE;
723
puts("Paste SDP from remote host, end with empty line");
727
comp0_addr[0] = '\0';
734
if (stdout) fflush(stdout);
736
if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
739
len = strlen(linebuf);
740
while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
741
linebuf[--len] = '\0';
744
while (len && pj_isspace(*line))
750
/* Ignore subsequent media descriptors */
758
char media[32], portstr[32];
762
puts("Media line ignored");
766
cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
768
PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
772
comp0_port = atoi(portstr);
779
char c[32], net[32], ip[80];
781
cnt = sscanf(line+2, "%s %s %s", c, net, ip);
783
PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
787
strcpy(comp0_addr, ip);
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;
801
char net[32], ip[64];
805
cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
807
PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
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],
820
if (status != PJ_SUCCESS) {
821
PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
824
pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
826
} else if (strcmp(attr, "candidate")==0) {
827
char *sdpcand = attr+strlen(attr)+1;
829
char foundation[32], transport[12], ipaddr[80], type[32];
831
int comp_id, prio, port;
832
pj_ice_sess_cand *cand;
835
cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
844
PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
848
cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
849
pj_bzero(cand, sizeof(*cand));
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;
858
PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
863
cand->comp_id = (pj_uint8_t)comp_id;
864
pj_strdup2(icedemo.pool, &cand->foundation, foundation);
867
if (strchr(ipaddr, ':'))
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'",
881
pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
883
++icedemo.rem.cand_cnt;
885
if (cand->comp_id > icedemo.rem.comp_cnt)
886
icedemo.rem.comp_cnt = cand->comp_id;
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)
898
PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
902
if (comp0_port==0 || comp0_addr[0]=='\0') {
903
PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
910
if (strchr(comp0_addr, ':'))
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],
919
if (status != PJ_SUCCESS) {
920
PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
923
pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
926
PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
927
icedemo.rem.cand_cnt));
936
* Start ICE negotiation! This function is invoked from the menu.
938
static void icedemo_start_nego(void)
940
pj_str_t rufrag, rpwd;
943
if (icedemo.icest == NULL) {
944
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
948
if (!pj_ice_strans_has_sess(icedemo.icest)) {
949
PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
953
if (icedemo.rem.cand_cnt == 0) {
954
PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
958
PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
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,
965
if (status != PJ_SUCCESS)
966
icedemo_perror("Error starting ICE", status);
968
PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
973
* Send application data to remote agent.
975
static void icedemo_send_data(unsigned comp_id, const char *data)
979
if (icedemo.icest == NULL) {
980
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
984
if (!pj_ice_strans_has_sess(icedemo.icest)) {
985
PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
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"));
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"));
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);
1007
PJ_LOG(3,(THIS_FILE, "Data sent"));
1012
* Display help for the menu.
1014
static void icedemo_help_menu(void)
1017
puts("-= Help on using ICE and this icedemo program =-");
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");
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"
1041
puts("This concludes the help screen.");
1047
* Display console menu
1049
static void icedemo_print_menu(void)
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("+----------------------------------------------------------------------+");
1071
* Main console loop.
1073
static void icedemo_console(void)
1075
pj_bool_t app_quit = PJ_FALSE;
1078
char input[80], *cmd;
1079
const char *SEP = " \t\r\n";
1082
icedemo_print_menu();
1085
if (stdout) fflush(stdout);
1087
pj_bzero(input, sizeof(input));
1088
if (fgets(input, sizeof(input), stdin) == NULL)
1091
len = strlen(input);
1092
while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1093
input[--len] = '\0';
1095
cmd = strtok(input, SEP);
1099
if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
1101
icedemo_create_instance();
1103
} else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
1105
icedemo_destroy_instance();
1107
} else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
1109
char *role = strtok(NULL, SEP);
1111
icedemo_init_session(*role);
1113
puts("error: Role required");
1115
} else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1117
icedemo_stop_session();
1119
} else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1123
} else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1125
icedemo_input_remote();
1127
} else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1129
icedemo_start_nego();
1131
} else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1133
char *comp = strtok(NULL, SEP);
1136
PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1138
char *data = comp + strlen(comp) + 1;
1141
icedemo_send_data(atoi(comp), data);
1144
} else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
1146
icedemo_help_menu();
1148
} else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
1154
printf("Invalid command '%s'\n", cmd);
1162
* Display program usage.
1164
static void icedemo_usage()
1166
puts("Usage: icedemo [optons]");
1167
printf("icedemo v%s by pjsip.org\n", pj_get_version());
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.");
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.");
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");
1196
* And here's the main()
1198
int main(int argc, char *argv[])
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'},
1217
icedemo.opt.comp_cnt = 1;
1218
icedemo.opt.max_host = -1;
1220
while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
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");
1230
icedemo.opt.ns = pj_str(pj_optarg);
1233
icedemo.opt.max_host = atoi(pj_optarg);
1239
icedemo.opt.stun_srv = pj_str(pj_optarg);
1242
icedemo.opt.turn_srv = pj_str(pj_optarg);
1245
icedemo.opt.turn_tcp = PJ_TRUE;
1248
icedemo.opt.turn_username = pj_str(pj_optarg);
1251
icedemo.opt.turn_password = pj_str(pj_optarg);
1254
icedemo.opt.turn_fingerprint = PJ_TRUE;
1257
icedemo.opt.regular = PJ_TRUE;
1260
icedemo.opt.log_file = pj_optarg;
1263
printf("Argument \"%s\" is not valid. Use -h to see help",
1269
status = icedemo_init();
1270
if (status != PJ_SUCCESS)
1275
err_exit("Quitting..", PJ_SUCCESS);