1
/* $Id: icedemo.c 3603 2011-07-07 01:53:35Z bennylp $ */
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
status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
619
if (status != PJ_SUCCESS)
622
/* And encode the candidates as SDP */
623
for (j=0; j<cand_cnt; ++j) {
624
printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
626
return -PJ_ETOOSMALL;
631
if (p == buffer+maxlen)
632
return -PJ_ETOOSMALL;
640
* Show information contained in the ICE stream transport. This is
641
* invoked from the menu.
643
static void icedemo_show_ice(void)
645
static char buffer[1000];
648
if (icedemo.icest == NULL) {
649
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
653
puts("General info");
654
puts("---------------");
655
printf("Component count : %d\n", icedemo.opt.comp_cnt);
657
if (pj_ice_strans_sess_is_complete(icedemo.icest))
658
puts("negotiation complete");
659
else if (pj_ice_strans_sess_is_running(icedemo.icest))
660
puts("negotiation is in progress");
661
else if (pj_ice_strans_has_sess(icedemo.icest))
662
puts("session ready");
664
puts("session not created");
666
if (!pj_ice_strans_has_sess(icedemo.icest)) {
667
puts("Create the session first to see more info");
671
printf("Negotiated comp_cnt: %d\n",
672
pj_ice_strans_get_running_comp_cnt(icedemo.icest));
673
printf("Role : %s\n",
674
pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
675
"controlled" : "controlling");
677
len = encode_session(buffer, sizeof(buffer));
679
err_exit("not enough buffer to show ICE status", -len);
682
printf("Local SDP (paste this to remote host):\n"
683
"--------------------------------------\n"
688
puts("Remote info:\n"
689
"----------------------");
690
if (icedemo.rem.cand_cnt==0) {
691
puts("No remote info yet");
695
printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
696
printf("Remote password : %s\n", icedemo.rem.pwd);
697
printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
699
for (i=0; i<icedemo.rem.cand_cnt; ++i) {
700
len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
702
err_exit("not enough buffer to show ICE status", -len);
704
printf(" %s", buffer);
711
* Input and parse SDP from the remote (containing remote's ICE information)
712
* and save it to global variables.
714
static void icedemo_input_remote(void)
717
unsigned media_cnt = 0;
718
unsigned comp0_port = 0;
720
pj_bool_t done = PJ_FALSE;
722
puts("Paste SDP from remote host, end with empty line");
726
comp0_addr[0] = '\0';
733
if (stdout) fflush(stdout);
735
if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
738
len = strlen(linebuf);
739
while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
740
linebuf[--len] = '\0';
743
while (len && pj_isspace(*line))
749
/* Ignore subsequent media descriptors */
757
char media[32], portstr[32];
761
puts("Media line ignored");
765
cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
767
PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
771
comp0_port = atoi(portstr);
778
char c[32], net[32], ip[80];
780
cnt = sscanf(line+2, "%s %s %s", c, net, ip);
782
PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
786
strcpy(comp0_addr, ip);
791
char *attr = strtok(line+2, ": \t\r\n");
792
if (strcmp(attr, "ice-ufrag")==0) {
793
strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
794
} else if (strcmp(attr, "ice-pwd")==0) {
795
strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
796
} else if (strcmp(attr, "rtcp")==0) {
797
char *val = attr+strlen(attr)+1;
800
char net[32], ip[64];
804
cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
806
PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
815
pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
816
tmp_addr = pj_str(ip);
817
status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
819
if (status != PJ_SUCCESS) {
820
PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
823
pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
825
} else if (strcmp(attr, "candidate")==0) {
826
char *sdpcand = attr+strlen(attr)+1;
828
char foundation[32], transport[12], ipaddr[80], type[32];
830
int comp_id, prio, port;
831
pj_ice_sess_cand *cand;
834
cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
843
PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
847
cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
848
pj_bzero(cand, sizeof(*cand));
850
if (strcmp(type, "host")==0)
851
cand->type = PJ_ICE_CAND_TYPE_HOST;
852
else if (strcmp(type, "srflx")==0)
853
cand->type = PJ_ICE_CAND_TYPE_SRFLX;
854
else if (strcmp(type, "relay")==0)
855
cand->type = PJ_ICE_CAND_TYPE_RELAYED;
857
PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
862
cand->comp_id = (pj_uint8_t)comp_id;
863
pj_strdup2(icedemo.pool, &cand->foundation, foundation);
866
if (strchr(ipaddr, ':'))
871
tmpaddr = pj_str(ipaddr);
872
pj_sockaddr_init(af, &cand->addr, NULL, 0);
873
status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
874
if (status != PJ_SUCCESS) {
875
PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
880
pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
882
++icedemo.rem.cand_cnt;
884
if (cand->comp_id > icedemo.rem.comp_cnt)
885
icedemo.rem.comp_cnt = cand->comp_id;
892
if (icedemo.rem.cand_cnt==0 ||
893
icedemo.rem.ufrag[0]==0 ||
894
icedemo.rem.pwd[0]==0 ||
895
icedemo.rem.comp_cnt == 0)
897
PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
901
if (comp0_port==0 || comp0_addr[0]=='\0') {
902
PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
909
if (strchr(comp0_addr, ':'))
914
pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
915
tmp_addr = pj_str(comp0_addr);
916
status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
918
if (status != PJ_SUCCESS) {
919
PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
922
pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
925
PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
926
icedemo.rem.cand_cnt));
935
* Start ICE negotiation! This function is invoked from the menu.
937
static void icedemo_start_nego(void)
939
pj_str_t rufrag, rpwd;
942
if (icedemo.icest == NULL) {
943
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
947
if (!pj_ice_strans_has_sess(icedemo.icest)) {
948
PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
952
if (icedemo.rem.cand_cnt == 0) {
953
PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
957
PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
959
status = pj_ice_strans_start_ice(icedemo.icest,
960
pj_cstr(&rufrag, icedemo.rem.ufrag),
961
pj_cstr(&rpwd, icedemo.rem.pwd),
962
icedemo.rem.cand_cnt,
964
if (status != PJ_SUCCESS)
965
icedemo_perror("Error starting ICE", status);
967
PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
972
* Send application data to remote agent.
974
static void icedemo_send_data(unsigned comp_id, const char *data)
978
if (icedemo.icest == NULL) {
979
PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
983
if (!pj_ice_strans_has_sess(icedemo.icest)) {
984
PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
989
if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
990
PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
995
if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
996
PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
1000
status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
1001
&icedemo.rem.def_addr[comp_id-1],
1002
pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
1003
if (status != PJ_SUCCESS)
1004
icedemo_perror("Error sending data", status);
1006
PJ_LOG(3,(THIS_FILE, "Data sent"));
1011
* Display help for the menu.
1013
static void icedemo_help_menu(void)
1016
puts("-= Help on using ICE and this icedemo program =-");
1018
puts("This application demonstrates how to use ICE in pjnath without having\n"
1019
"to use the SIP protocol. To use this application, you will need to run\n"
1020
"two instances of this application, to simulate two ICE agents.\n");
1022
puts("Basic ICE flow:\n"
1023
" create instance [menu \"c\"]\n"
1024
" repeat these steps as wanted:\n"
1025
" - init session as offerer or answerer [menu \"i\"]\n"
1026
" - display our SDP [menu \"s\"]\n"
1027
" - \"send\" our SDP from the \"show\" output above to remote, by\n"
1028
" copy-pasting the SDP to the other icedemo application\n"
1029
" - parse remote SDP, by pasting SDP generated by the other icedemo\n"
1030
" instance [menu \"r\"]\n"
1031
" - begin ICE negotiation in our end [menu \"b\"], and \n"
1032
" - immediately begin ICE negotiation in the other icedemo instance\n"
1033
" - ICE negotiation will run, and result will be printed to screen\n"
1034
" - send application data to remote [menu \"x\"]\n"
1035
" - end/stop ICE session [menu \"e\"]\n"
1036
" destroy instance [menu \"d\"]\n"
1040
puts("This concludes the help screen.");
1046
* Display console menu
1048
static void icedemo_print_menu(void)
1051
puts("+----------------------------------------------------------------------+");
1052
puts("| M E N U |");
1053
puts("+---+------------------------------------------------------------------+");
1054
puts("| c | create Create the instance |");
1055
puts("| d | destroy Destroy the instance |");
1056
puts("| i | init o|a Initialize ICE session as offerer or answerer |");
1057
puts("| e | stop End/stop ICE session |");
1058
puts("| s | show Display local ICE info |");
1059
puts("| r | remote Input remote ICE info |");
1060
puts("| b | start Begin ICE negotiation |");
1061
puts("| x | send <compid> .. Send data to remote |");
1062
puts("+---+------------------------------------------------------------------+");
1063
puts("| h | help * Help! * |");
1064
puts("| q | quit Quit |");
1065
puts("+----------------------------------------------------------------------+");
1070
* Main console loop.
1072
static void icedemo_console(void)
1074
pj_bool_t app_quit = PJ_FALSE;
1077
char input[80], *cmd;
1078
const char *SEP = " \t\r\n";
1081
icedemo_print_menu();
1084
if (stdout) fflush(stdout);
1086
pj_bzero(input, sizeof(input));
1087
if (fgets(input, sizeof(input), stdin) == NULL)
1090
len = strlen(input);
1091
while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1092
input[--len] = '\0';
1094
cmd = strtok(input, SEP);
1098
if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
1100
icedemo_create_instance();
1102
} else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
1104
icedemo_destroy_instance();
1106
} else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
1108
char *role = strtok(NULL, SEP);
1110
icedemo_init_session(*role);
1112
puts("error: Role required");
1114
} else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1116
icedemo_stop_session();
1118
} else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1122
} else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1124
icedemo_input_remote();
1126
} else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1128
icedemo_start_nego();
1130
} else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1132
char *comp = strtok(NULL, SEP);
1135
PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1137
char *data = comp + strlen(comp) + 1;
1140
icedemo_send_data(atoi(comp), data);
1143
} else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
1145
icedemo_help_menu();
1147
} else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
1153
printf("Invalid command '%s'\n", cmd);
1161
* Display program usage.
1163
static void icedemo_usage()
1165
puts("Usage: icedemo [optons]");
1166
printf("icedemo v%s by pjsip.org\n", pj_get_version());
1168
puts("General options:");
1169
puts(" --comp-cnt, -c N Component count (default=1)");
1170
puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
1171
puts(" resolution");
1172
puts(" --max-host, -H N Set max number of host candidates to N");
1173
puts(" --regular, -R Use regular nomination (default aggressive)");
1174
puts(" --log-file, -L FILE Save output to log FILE");
1175
puts(" --help, -h Display this screen.");
1177
puts("STUN related options:");
1178
puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
1179
puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1180
puts(" name if DNS SRV resolution is used.");
1182
puts("TURN related options:");
1183
puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
1184
puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1185
puts(" name if DNS SRV resolution is used.");
1186
puts(" --turn-tcp, -T Use TCP to connect to TURN server");
1187
puts(" --turn-username, -u UID Set TURN username of the credential to UID");
1188
puts(" --turn-password, -p PWD Set password of the credential to WPWD");
1189
puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
1195
* And here's the main()
1197
int main(int argc, char *argv[])
1199
struct pj_getopt_option long_options[] = {
1200
{ "comp-cnt", 1, 0, 'c'},
1201
{ "nameserver", 1, 0, 'n'},
1202
{ "max-host", 1, 0, 'H'},
1203
{ "help", 0, 0, 'h'},
1204
{ "stun-srv", 1, 0, 's'},
1205
{ "turn-srv", 1, 0, 't'},
1206
{ "turn-tcp", 0, 0, 'T'},
1207
{ "turn-username", 1, 0, 'u'},
1208
{ "turn-password", 1, 0, 'p'},
1209
{ "turn-fingerprint", 0, 0, 'F'},
1210
{ "regular", 0, 0, 'R'},
1211
{ "log-file", 1, 0, 'L'},
1216
icedemo.opt.comp_cnt = 1;
1217
icedemo.opt.max_host = -1;
1219
while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
1222
icedemo.opt.comp_cnt = atoi(pj_optarg);
1223
if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
1224
puts("Invalid component count value");
1229
icedemo.opt.ns = pj_str(pj_optarg);
1232
icedemo.opt.max_host = atoi(pj_optarg);
1238
icedemo.opt.stun_srv = pj_str(pj_optarg);
1241
icedemo.opt.turn_srv = pj_str(pj_optarg);
1244
icedemo.opt.turn_tcp = PJ_TRUE;
1247
icedemo.opt.turn_username = pj_str(pj_optarg);
1250
icedemo.opt.turn_password = pj_str(pj_optarg);
1253
icedemo.opt.turn_fingerprint = PJ_TRUE;
1256
icedemo.opt.regular = PJ_TRUE;
1259
icedemo.opt.log_file = pj_optarg;
1262
printf("Argument \"%s\" is not valid. Use -h to see help",
1268
status = icedemo_init();
1269
if (status != PJ_SUCCESS)
1274
err_exit("Quitting..", PJ_SUCCESS);