00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include <stdio.h>
00020 #include <stdlib.h>
00021 #include <pjlib.h>
00022 #include <pjlib-util.h>
00023 #include <pjnath.h>
00024
00025
00026 #define THIS_FILE "icedemo.c"
00027
00028
00029
00030
00031 #define KA_INTERVAL 300
00032
00033
00034
00035 static struct app_t
00036 {
00037
00038 struct options
00039 {
00040 unsigned comp_cnt;
00041 pj_str_t ns;
00042 int max_host;
00043 pj_bool_t regular;
00044 pj_str_t stun_srv;
00045 pj_str_t turn_srv;
00046 pj_bool_t turn_tcp;
00047 pj_str_t turn_username;
00048 pj_str_t turn_password;
00049 pj_bool_t turn_fingerprint;
00050 const char *log_file;
00051 } opt;
00052
00053
00054 pj_caching_pool cp;
00055 pj_pool_t *pool;
00056 pj_thread_t *thread;
00057 pj_bool_t thread_quit_flag;
00058 pj_ice_strans_cfg ice_cfg;
00059 pj_ice_strans *icest;
00060 FILE *log_fhnd;
00061
00062
00063 struct rem_info
00064 {
00065 char ufrag[80];
00066 char pwd[80];
00067 unsigned comp_cnt;
00068 pj_sockaddr def_addr[PJ_ICE_MAX_COMP];
00069 unsigned cand_cnt;
00070 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
00071 } rem;
00072
00073 } icedemo;
00074
00075
00076 static void icedemo_perror(const char *title, pj_status_t status)
00077 {
00078 char errmsg[PJ_ERR_MSG_SIZE];
00079
00080 pj_strerror(status, errmsg, sizeof(errmsg));
00081 PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
00082 }
00083
00084
00085
00086
00087 static void err_exit(const char *title, pj_status_t status)
00088 {
00089 if (status != PJ_SUCCESS) {
00090 icedemo_perror(title, status);
00091 }
00092 PJ_LOG(3,(THIS_FILE, "Shutting down.."));
00093
00094 if (icedemo.icest)
00095 pj_ice_strans_destroy(icedemo.icest);
00096
00097 pj_thread_sleep(500);
00098
00099 icedemo.thread_quit_flag = PJ_TRUE;
00100 if (icedemo.thread) {
00101 pj_thread_join(icedemo.thread);
00102 pj_thread_destroy(icedemo.thread);
00103 }
00104
00105 if (icedemo.ice_cfg.stun_cfg.ioqueue)
00106 pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
00107
00108 if (icedemo.ice_cfg.stun_cfg.timer_heap)
00109 pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
00110
00111 pj_caching_pool_destroy(&icedemo.cp);
00112
00113 pj_shutdown();
00114
00115 if (icedemo.log_fhnd) {
00116 fclose(icedemo.log_fhnd);
00117 icedemo.log_fhnd = NULL;
00118 }
00119
00120 exit(status != PJ_SUCCESS);
00121 }
00122
00123 #define CHECK(expr) status=expr; \
00124 if (status!=PJ_SUCCESS) { \
00125 err_exit(#expr, status); \
00126 }
00127
00128
00129
00130
00131
00132 static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
00133 {
00134 enum { MAX_NET_EVENTS = 1 };
00135 pj_time_val max_timeout = {0, 0};
00136 pj_time_val timeout = { 0, 0};
00137 unsigned count = 0, net_event_count = 0;
00138 int c;
00139
00140 max_timeout.msec = max_msec;
00141
00142
00143 timeout.sec = timeout.msec = 0;
00144 c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
00145 if (c > 0)
00146 count += c;
00147
00148
00149
00150
00151 pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
00152 if (timeout.msec >= 1000) timeout.msec = 999;
00153
00154
00155
00156
00157 if (PJ_TIME_VAL_GT(timeout, max_timeout))
00158 timeout = max_timeout;
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171 do {
00172 c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
00173 if (c < 0) {
00174 pj_status_t err = pj_get_netos_error();
00175 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
00176 if (p_count)
00177 *p_count = count;
00178 return err;
00179 } else if (c == 0) {
00180 break;
00181 } else {
00182 net_event_count += c;
00183 timeout.sec = timeout.msec = 0;
00184 }
00185 } while (c > 0 && net_event_count < MAX_NET_EVENTS);
00186
00187 count += net_event_count;
00188 if (p_count)
00189 *p_count = count;
00190
00191 return PJ_SUCCESS;
00192
00193 }
00194
00195
00196
00197
00198 static int icedemo_worker_thread(void *unused)
00199 {
00200 PJ_UNUSED_ARG(unused);
00201
00202 while (!icedemo.thread_quit_flag) {
00203 handle_events(500, NULL);
00204 }
00205
00206 return 0;
00207 }
00208
00209
00210
00211
00212
00213
00214
00215 static void cb_on_rx_data(pj_ice_strans *ice_st,
00216 unsigned comp_id,
00217 void *pkt, pj_size_t size,
00218 const pj_sockaddr_t *src_addr,
00219 unsigned src_addr_len)
00220 {
00221 char ipstr[PJ_INET6_ADDRSTRLEN+10];
00222
00223 PJ_UNUSED_ARG(ice_st);
00224 PJ_UNUSED_ARG(src_addr_len);
00225 PJ_UNUSED_ARG(pkt);
00226
00227
00228
00229
00230 PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
00231 comp_id, size,
00232 pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
00233 (unsigned)size,
00234 (char*)pkt));
00235 }
00236
00237
00238
00239
00240
00241 static void cb_on_ice_complete(pj_ice_strans *ice_st,
00242 pj_ice_strans_op op,
00243 pj_status_t status)
00244 {
00245 const char *opname =
00246 (op==PJ_ICE_STRANS_OP_INIT? "initialization" :
00247 (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
00248
00249 if (status == PJ_SUCCESS) {
00250 PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
00251 } else {
00252 char errmsg[PJ_ERR_MSG_SIZE];
00253
00254 pj_strerror(status, errmsg, sizeof(errmsg));
00255 PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
00256 pj_ice_strans_destroy(ice_st);
00257 icedemo.icest = NULL;
00258 }
00259 }
00260
00261
00262 static void log_func(int level, const char *data, int len)
00263 {
00264 pj_log_write(level, data, len);
00265 if (icedemo.log_fhnd) {
00266 if (fwrite(data, len, 1, icedemo.log_fhnd) != 1)
00267 return;
00268 }
00269 }
00270
00271
00272
00273
00274
00275
00276 static pj_status_t icedemo_init(void)
00277 {
00278 pj_status_t status;
00279
00280 if (icedemo.opt.log_file) {
00281 icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
00282 pj_log_set_log_func(&log_func);
00283 }
00284
00285
00286 CHECK( pj_init() );
00287 CHECK( pjlib_util_init() );
00288 CHECK( pjnath_init() );
00289
00290
00291 pj_caching_pool_init(&icedemo.cp, NULL, 0);
00292
00293
00294 pj_ice_strans_cfg_default(&icedemo.ice_cfg);
00295
00296 icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
00297
00298
00299 icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
00300 512, 512, NULL);
00301
00302
00303 CHECK( pj_timer_heap_create(icedemo.pool, 100,
00304 &icedemo.ice_cfg.stun_cfg.timer_heap) );
00305
00306
00307 CHECK( pj_ioqueue_create(icedemo.pool, 16,
00308 &icedemo.ice_cfg.stun_cfg.ioqueue) );
00309
00310
00311
00312
00313
00314 CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
00315 NULL, 0, 0, &icedemo.thread) );
00316
00317 icedemo.ice_cfg.af = pj_AF_INET();
00318
00319
00320 if (icedemo.opt.ns.slen) {
00321 CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
00322 "resolver",
00323 0,
00324 icedemo.ice_cfg.stun_cfg.timer_heap,
00325 icedemo.ice_cfg.stun_cfg.ioqueue,
00326 &icedemo.ice_cfg.resolver) );
00327
00328 CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
00329 &icedemo.opt.ns, NULL) );
00330 }
00331
00332
00333
00334
00335 if (icedemo.opt.max_host != -1)
00336 icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
00337
00338
00339 if (icedemo.opt.regular)
00340 icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
00341 else
00342 icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
00343
00344
00345 if (icedemo.opt.stun_srv.slen) {
00346 char *pos;
00347
00348
00349 if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
00350 icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
00351 icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
00352
00353 icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
00354 } else {
00355 icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
00356 icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
00357 }
00358
00359
00360
00361
00362 icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
00363 }
00364
00365
00366 if (icedemo.opt.turn_srv.slen) {
00367 char *pos;
00368
00369
00370 if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
00371 icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
00372 icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
00373
00374 icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
00375 } else {
00376 icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
00377 icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
00378 }
00379
00380
00381 icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
00382 icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
00383 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
00384 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
00385
00386
00387 if (icedemo.opt.turn_tcp)
00388 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
00389 else
00390 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
00391
00392
00393
00394
00395 icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
00396 }
00397
00398
00399 return PJ_SUCCESS;
00400 }
00401
00402
00403
00404
00405
00406 static void icedemo_create_instance(void)
00407 {
00408 pj_ice_strans_cb icecb;
00409 pj_status_t status;
00410
00411 if (icedemo.icest != NULL) {
00412 puts("ICE instance already created, destroy it first");
00413 return;
00414 }
00415
00416
00417 pj_bzero(&icecb, sizeof(icecb));
00418 icecb.on_rx_data = cb_on_rx_data;
00419 icecb.on_ice_complete = cb_on_ice_complete;
00420
00421
00422 status = pj_ice_strans_create("icedemo",
00423 &icedemo.ice_cfg,
00424 icedemo.opt.comp_cnt,
00425 NULL,
00426 &icecb,
00427 &icedemo.icest)
00428 ;
00429 if (status != PJ_SUCCESS)
00430 icedemo_perror("error creating ice", status);
00431 else
00432 PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
00433 }
00434
00435
00436 static void reset_rem_info(void)
00437 {
00438 pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
00439 }
00440
00441
00442
00443
00444
00445 static void icedemo_destroy_instance(void)
00446 {
00447 if (icedemo.icest == NULL) {
00448 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00449 return;
00450 }
00451
00452 pj_ice_strans_destroy(icedemo.icest);
00453 icedemo.icest = NULL;
00454
00455 reset_rem_info();
00456
00457 PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
00458 }
00459
00460
00461
00462
00463
00464 static void icedemo_init_session(unsigned rolechar)
00465 {
00466 pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ?
00467 PJ_ICE_SESS_ROLE_CONTROLLING :
00468 PJ_ICE_SESS_ROLE_CONTROLLED);
00469 pj_status_t status;
00470
00471 if (icedemo.icest == NULL) {
00472 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00473 return;
00474 }
00475
00476 if (pj_ice_strans_has_sess(icedemo.icest)) {
00477 PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
00478 return;
00479 }
00480
00481 status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
00482 if (status != PJ_SUCCESS)
00483 icedemo_perror("error creating session", status);
00484 else
00485 PJ_LOG(3,(THIS_FILE, "ICE session created"));
00486
00487 reset_rem_info();
00488 }
00489
00490
00491
00492
00493
00494 static void icedemo_stop_session(void)
00495 {
00496 pj_status_t status;
00497
00498 if (icedemo.icest == NULL) {
00499 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00500 return;
00501 }
00502
00503 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00504 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00505 return;
00506 }
00507
00508 status = pj_ice_strans_stop_ice(icedemo.icest);
00509 if (status != PJ_SUCCESS)
00510 icedemo_perror("error stopping session", status);
00511 else
00512 PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
00513
00514 reset_rem_info();
00515 }
00516
00517 #define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5) \
00518 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
00519 fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
00520 if (printed <= 0) return -PJ_ETOOSMALL; \
00521 p += printed
00522
00523
00524
00525 static int print_cand(char buffer[], unsigned maxlen,
00526 const pj_ice_sess_cand *cand)
00527 {
00528 char ipaddr[PJ_INET6_ADDRSTRLEN];
00529 char *p = buffer;
00530 int printed;
00531
00532 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
00533 (int)cand->foundation.slen,
00534 cand->foundation.ptr,
00535 (unsigned)cand->comp_id,
00536 cand->prio,
00537 pj_sockaddr_print(&cand->addr, ipaddr,
00538 sizeof(ipaddr), 0),
00539 (unsigned)pj_sockaddr_get_port(&cand->addr));
00540
00541 PRINT("%s\n",
00542 pj_ice_get_cand_type_name(cand->type),
00543 0, 0, 0, 0, 0);
00544
00545 if (p == buffer+maxlen)
00546 return -PJ_ETOOSMALL;
00547
00548 *p = '\0';
00549
00550 return p-buffer;
00551 }
00552
00553
00554
00555
00556 static int encode_session(char buffer[], unsigned maxlen)
00557 {
00558 char *p = buffer;
00559 unsigned comp;
00560 int printed;
00561 pj_str_t local_ufrag, local_pwd;
00562 pj_status_t status;
00563
00564
00565 PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n",
00566 0, 0, 0, 0, 0, 0);
00567
00568
00569 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
00570 NULL, NULL);
00571
00572
00573 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
00574 (int)local_ufrag.slen,
00575 local_ufrag.ptr,
00576 (int)local_pwd.slen,
00577 local_pwd.ptr,
00578 0, 0);
00579
00580
00581 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
00582 unsigned j, cand_cnt;
00583 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
00584 char ipaddr[PJ_INET6_ADDRSTRLEN];
00585
00586
00587 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
00588 if (status != PJ_SUCCESS)
00589 return -status;
00590
00591
00592 if (comp==0) {
00593
00594 PRINT("m=audio %d RTP/AVP 0\n"
00595 "c=IN IP4 %s\n",
00596 (int)pj_sockaddr_get_port(&cand[0].addr),
00597 pj_sockaddr_print(&cand[0].addr, ipaddr,
00598 sizeof(ipaddr), 0),
00599 0, 0, 0, 0);
00600 } else if (comp==1) {
00601
00602 PRINT("a=rtcp:%d IN IP4 %s\n",
00603 (int)pj_sockaddr_get_port(&cand[0].addr),
00604 pj_sockaddr_print(&cand[0].addr, ipaddr,
00605 sizeof(ipaddr), 0),
00606 0, 0, 0, 0);
00607 } else {
00608
00609 PRINT("a=Xice-defcand:%d IN IP4 %s\n",
00610 (int)pj_sockaddr_get_port(&cand[0].addr),
00611 pj_sockaddr_print(&cand[0].addr, ipaddr,
00612 sizeof(ipaddr), 0),
00613 0, 0, 0, 0);
00614 }
00615
00616
00617 cand_cnt = PJ_ARRAY_SIZE(cand);
00618 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
00619 &cand_cnt, cand);
00620 if (status != PJ_SUCCESS)
00621 return -status;
00622
00623
00624 for (j=0; j<cand_cnt; ++j) {
00625 printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
00626 if (printed < 0)
00627 return -PJ_ETOOSMALL;
00628 p += printed;
00629 }
00630 }
00631
00632 if (p == buffer+maxlen)
00633 return -PJ_ETOOSMALL;
00634
00635 *p = '\0';
00636 return p - buffer;
00637 }
00638
00639
00640
00641
00642
00643
00644 static void icedemo_show_ice(void)
00645 {
00646 static char buffer[1000];
00647 int len;
00648
00649 if (icedemo.icest == NULL) {
00650 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00651 return;
00652 }
00653
00654 puts("General info");
00655 puts("---------------");
00656 printf("Component count : %d\n", icedemo.opt.comp_cnt);
00657 printf("Status : ");
00658 if (pj_ice_strans_sess_is_complete(icedemo.icest))
00659 puts("negotiation complete");
00660 else if (pj_ice_strans_sess_is_running(icedemo.icest))
00661 puts("negotiation is in progress");
00662 else if (pj_ice_strans_has_sess(icedemo.icest))
00663 puts("session ready");
00664 else
00665 puts("session not created");
00666
00667 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00668 puts("Create the session first to see more info");
00669 return;
00670 }
00671
00672 printf("Negotiated comp_cnt: %d\n",
00673 pj_ice_strans_get_running_comp_cnt(icedemo.icest));
00674 printf("Role : %s\n",
00675 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
00676 "controlled" : "controlling");
00677
00678 len = encode_session(buffer, sizeof(buffer));
00679 if (len < 0)
00680 err_exit("not enough buffer to show ICE status", -len);
00681
00682 puts("");
00683 printf("Local SDP (paste this to remote host):\n"
00684 "--------------------------------------\n"
00685 "%s\n", buffer);
00686
00687
00688 puts("");
00689 puts("Remote info:\n"
00690 "----------------------");
00691 if (icedemo.rem.cand_cnt==0) {
00692 puts("No remote info yet");
00693 } else {
00694 unsigned i;
00695
00696 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
00697 printf("Remote password : %s\n", icedemo.rem.pwd);
00698 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
00699
00700 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
00701 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
00702 if (len < 0)
00703 err_exit("not enough buffer to show ICE status", -len);
00704
00705 printf(" %s", buffer);
00706 }
00707 }
00708 }
00709
00710
00711
00712
00713
00714
00715 static void icedemo_input_remote(void)
00716 {
00717 char linebuf[80];
00718 unsigned media_cnt = 0;
00719 unsigned comp0_port = 0;
00720 char comp0_addr[80];
00721 pj_bool_t done = PJ_FALSE;
00722
00723 puts("Paste SDP from remote host, end with empty line");
00724
00725 reset_rem_info();
00726
00727 comp0_addr[0] = '\0';
00728
00729 while (!done) {
00730 int len;
00731 char *line;
00732
00733 printf(">");
00734 if (stdout) fflush(stdout);
00735
00736 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
00737 break;
00738
00739 len = strlen(linebuf);
00740 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
00741 linebuf[--len] = '\0';
00742
00743 line = linebuf;
00744 while (len && pj_isspace(*line))
00745 ++line, --len;
00746
00747 if (len==0)
00748 break;
00749
00750
00751 if (media_cnt > 1)
00752 continue;
00753
00754 switch (line[0]) {
00755 case 'm':
00756 {
00757 int cnt;
00758 char media[32], portstr[32];
00759
00760 ++media_cnt;
00761 if (media_cnt > 1) {
00762 puts("Media line ignored");
00763 break;
00764 }
00765
00766 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
00767 if (cnt != 2) {
00768 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
00769 goto on_error;
00770 }
00771
00772 comp0_port = atoi(portstr);
00773
00774 }
00775 break;
00776 case 'c':
00777 {
00778 int cnt;
00779 char c[32], net[32], ip[80];
00780
00781 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
00782 if (cnt != 3) {
00783 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
00784 goto on_error;
00785 }
00786
00787 strcpy(comp0_addr, ip);
00788 }
00789 break;
00790 case 'a':
00791 {
00792 char *attr = strtok(line+2, ": \t\r\n");
00793 if (strcmp(attr, "ice-ufrag")==0) {
00794 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
00795 } else if (strcmp(attr, "ice-pwd")==0) {
00796 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
00797 } else if (strcmp(attr, "rtcp")==0) {
00798 char *val = attr+strlen(attr)+1;
00799 int af, cnt;
00800 int port;
00801 char net[32], ip[64];
00802 pj_str_t tmp_addr;
00803 pj_status_t status;
00804
00805 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
00806 if (cnt != 3) {
00807 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
00808 goto on_error;
00809 }
00810
00811 if (strchr(ip, ':'))
00812 af = pj_AF_INET6();
00813 else
00814 af = pj_AF_INET();
00815
00816 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
00817 tmp_addr = pj_str(ip);
00818 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
00819 &tmp_addr);
00820 if (status != PJ_SUCCESS) {
00821 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
00822 goto on_error;
00823 }
00824 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
00825
00826 } else if (strcmp(attr, "candidate")==0) {
00827 char *sdpcand = attr+strlen(attr)+1;
00828 int af, cnt;
00829 char foundation[32], transport[12], ipaddr[80], type[32];
00830 pj_str_t tmpaddr;
00831 int comp_id, prio, port;
00832 pj_ice_sess_cand *cand;
00833 pj_status_t status;
00834
00835 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
00836 foundation,
00837 &comp_id,
00838 transport,
00839 &prio,
00840 ipaddr,
00841 &port,
00842 type);
00843 if (cnt != 7) {
00844 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
00845 goto on_error;
00846 }
00847
00848 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
00849 pj_bzero(cand, sizeof(*cand));
00850
00851 if (strcmp(type, "host")==0)
00852 cand->type = PJ_ICE_CAND_TYPE_HOST;
00853 else if (strcmp(type, "srflx")==0)
00854 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
00855 else if (strcmp(type, "relay")==0)
00856 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
00857 else {
00858 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
00859 type));
00860 goto on_error;
00861 }
00862
00863 cand->comp_id = (pj_uint8_t)comp_id;
00864 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
00865 cand->prio = prio;
00866
00867 if (strchr(ipaddr, ':'))
00868 af = pj_AF_INET6();
00869 else
00870 af = pj_AF_INET();
00871
00872 tmpaddr = pj_str(ipaddr);
00873 pj_sockaddr_init(af, &cand->addr, NULL, 0);
00874 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
00875 if (status != PJ_SUCCESS) {
00876 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
00877 ipaddr));
00878 goto on_error;
00879 }
00880
00881 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
00882
00883 ++icedemo.rem.cand_cnt;
00884
00885 if (cand->comp_id > icedemo.rem.comp_cnt)
00886 icedemo.rem.comp_cnt = cand->comp_id;
00887 }
00888 }
00889 break;
00890 }
00891 }
00892
00893 if (icedemo.rem.cand_cnt==0 ||
00894 icedemo.rem.ufrag[0]==0 ||
00895 icedemo.rem.pwd[0]==0 ||
00896 icedemo.rem.comp_cnt == 0)
00897 {
00898 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
00899 goto on_error;
00900 }
00901
00902 if (comp0_port==0 || comp0_addr[0]=='\0') {
00903 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
00904 goto on_error;
00905 } else {
00906 int af;
00907 pj_str_t tmp_addr;
00908 pj_status_t status;
00909
00910 if (strchr(comp0_addr, ':'))
00911 af = pj_AF_INET6();
00912 else
00913 af = pj_AF_INET();
00914
00915 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
00916 tmp_addr = pj_str(comp0_addr);
00917 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
00918 &tmp_addr);
00919 if (status != PJ_SUCCESS) {
00920 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
00921 goto on_error;
00922 }
00923 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
00924 }
00925
00926 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
00927 icedemo.rem.cand_cnt));
00928 return;
00929
00930 on_error:
00931 reset_rem_info();
00932 }
00933
00934
00935
00936
00937
00938 static void icedemo_start_nego(void)
00939 {
00940 pj_str_t rufrag, rpwd;
00941 pj_status_t status;
00942
00943 if (icedemo.icest == NULL) {
00944 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00945 return;
00946 }
00947
00948 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00949 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00950 return;
00951 }
00952
00953 if (icedemo.rem.cand_cnt == 0) {
00954 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
00955 return;
00956 }
00957
00958 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
00959
00960 status = pj_ice_strans_start_ice(icedemo.icest,
00961 pj_cstr(&rufrag, icedemo.rem.ufrag),
00962 pj_cstr(&rpwd, icedemo.rem.pwd),
00963 icedemo.rem.cand_cnt,
00964 icedemo.rem.cand);
00965 if (status != PJ_SUCCESS)
00966 icedemo_perror("Error starting ICE", status);
00967 else
00968 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
00969 }
00970
00971
00972
00973
00974
00975 static void icedemo_send_data(unsigned comp_id, const char *data)
00976 {
00977 pj_status_t status;
00978
00979 if (icedemo.icest == NULL) {
00980 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00981 return;
00982 }
00983
00984 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00985 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00986 return;
00987 }
00988
00989
00990
00991
00992
00993
00994
00995
00996 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
00997 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
00998 return;
00999 }
01000
01001 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
01002 &icedemo.rem.def_addr[comp_id-1],
01003 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
01004 if (status != PJ_SUCCESS)
01005 icedemo_perror("Error sending data", status);
01006 else
01007 PJ_LOG(3,(THIS_FILE, "Data sent"));
01008 }
01009
01010
01011
01012
01013
01014 static void icedemo_help_menu(void)
01015 {
01016 puts("");
01017 puts("-= Help on using ICE and this icedemo program =-");
01018 puts("");
01019 puts("This application demonstrates how to use ICE in pjnath without having\n"
01020 "to use the SIP protocol. To use this application, you will need to run\n"
01021 "two instances of this application, to simulate two ICE agents.\n");
01022
01023 puts("Basic ICE flow:\n"
01024 " create instance [menu \"c\"]\n"
01025 " repeat these steps as wanted:\n"
01026 " - init session as offerer or answerer [menu \"i\"]\n"
01027 " - display our SDP [menu \"s\"]\n"
01028 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
01029 " copy-pasting the SDP to the other icedemo application\n"
01030 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
01031 " instance [menu \"r\"]\n"
01032 " - begin ICE negotiation in our end [menu \"b\"], and \n"
01033 " - immediately begin ICE negotiation in the other icedemo instance\n"
01034 " - ICE negotiation will run, and result will be printed to screen\n"
01035 " - send application data to remote [menu \"x\"]\n"
01036 " - end/stop ICE session [menu \"e\"]\n"
01037 " destroy instance [menu \"d\"]\n"
01038 "");
01039
01040 puts("");
01041 puts("This concludes the help screen.");
01042 puts("");
01043 }
01044
01045
01046
01047
01048
01049 static void icedemo_print_menu(void)
01050 {
01051 puts("");
01052 puts("+----------------------------------------------------------------------+");
01053 puts("| M E N U |");
01054 puts("+---+------------------------------------------------------------------+");
01055 puts("| c | create Create the instance |");
01056 puts("| d | destroy Destroy the instance |");
01057 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
01058 puts("| e | stop End/stop ICE session |");
01059 puts("| s | show Display local ICE info |");
01060 puts("| r | remote Input remote ICE info |");
01061 puts("| b | start Begin ICE negotiation |");
01062 puts("| x | send <compid> .. Send data to remote |");
01063 puts("+---+------------------------------------------------------------------+");
01064 puts("| h | help * Help! * |");
01065 puts("| q | quit Quit |");
01066 puts("+----------------------------------------------------------------------+");
01067 }
01068
01069
01070
01071
01072
01073 static void icedemo_console(void)
01074 {
01075 pj_bool_t app_quit = PJ_FALSE;
01076
01077 while (!app_quit) {
01078 char input[80], *cmd;
01079 const char *SEP = " \t\r\n";
01080 int len;
01081
01082 icedemo_print_menu();
01083
01084 printf("Input: ");
01085 if (stdout) fflush(stdout);
01086
01087 pj_bzero(input, sizeof(input));
01088 if (fgets(input, sizeof(input), stdin) == NULL)
01089 break;
01090
01091 len = strlen(input);
01092 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
01093 input[--len] = '\0';
01094
01095 cmd = strtok(input, SEP);
01096 if (!cmd)
01097 continue;
01098
01099 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
01100
01101 icedemo_create_instance();
01102
01103 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
01104
01105 icedemo_destroy_instance();
01106
01107 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
01108
01109 char *role = strtok(NULL, SEP);
01110 if (role)
01111 icedemo_init_session(*role);
01112 else
01113 puts("error: Role required");
01114
01115 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
01116
01117 icedemo_stop_session();
01118
01119 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
01120
01121 icedemo_show_ice();
01122
01123 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
01124
01125 icedemo_input_remote();
01126
01127 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
01128
01129 icedemo_start_nego();
01130
01131 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
01132
01133 char *comp = strtok(NULL, SEP);
01134
01135 if (!comp) {
01136 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
01137 } else {
01138 char *data = comp + strlen(comp) + 1;
01139 if (!data)
01140 data = "";
01141 icedemo_send_data(atoi(comp), data);
01142 }
01143
01144 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
01145
01146 icedemo_help_menu();
01147
01148 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
01149
01150 app_quit = PJ_TRUE;
01151
01152 } else {
01153
01154 printf("Invalid command '%s'\n", cmd);
01155
01156 }
01157 }
01158 }
01159
01160
01161
01162
01163
01164 static void icedemo_usage()
01165 {
01166 puts("Usage: icedemo [optons]");
01167 printf("icedemo v%s by pjsip.org\n", pj_get_version());
01168 puts("");
01169 puts("General options:");
01170 puts(" --comp-cnt, -c N Component count (default=1)");
01171 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
01172 puts(" resolution");
01173 puts(" --max-host, -H N Set max number of host candidates to N");
01174 puts(" --regular, -R Use regular nomination (default aggressive)");
01175 puts(" --log-file, -L FILE Save output to log FILE");
01176 puts(" --help, -h Display this screen.");
01177 puts("");
01178 puts("STUN related options:");
01179 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
01180 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
01181 puts(" name if DNS SRV resolution is used.");
01182 puts("");
01183 puts("TURN related options:");
01184 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
01185 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
01186 puts(" name if DNS SRV resolution is used.");
01187 puts(" --turn-tcp, -T Use TCP to connect to TURN server");
01188 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
01189 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
01190 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
01191 puts("");
01192 }
01193
01194
01195
01196
01197
01198 int main(int argc, char *argv[])
01199 {
01200 struct pj_getopt_option long_options[] = {
01201 { "comp-cnt", 1, 0, 'c'},
01202 { "nameserver", 1, 0, 'n'},
01203 { "max-host", 1, 0, 'H'},
01204 { "help", 0, 0, 'h'},
01205 { "stun-srv", 1, 0, 's'},
01206 { "turn-srv", 1, 0, 't'},
01207 { "turn-tcp", 0, 0, 'T'},
01208 { "turn-username", 1, 0, 'u'},
01209 { "turn-password", 1, 0, 'p'},
01210 { "turn-fingerprint", 0, 0, 'F'},
01211 { "regular", 0, 0, 'R'},
01212 { "log-file", 1, 0, 'L'},
01213 };
01214 int c, opt_id;
01215 pj_status_t status;
01216
01217 icedemo.opt.comp_cnt = 1;
01218 icedemo.opt.max_host = -1;
01219
01220 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
01221 switch (c) {
01222 case 'c':
01223 icedemo.opt.comp_cnt = atoi(pj_optarg);
01224 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
01225 puts("Invalid component count value");
01226 return 1;
01227 }
01228 break;
01229 case 'n':
01230 icedemo.opt.ns = pj_str(pj_optarg);
01231 break;
01232 case 'H':
01233 icedemo.opt.max_host = atoi(pj_optarg);
01234 break;
01235 case 'h':
01236 icedemo_usage();
01237 return 0;
01238 case 's':
01239 icedemo.opt.stun_srv = pj_str(pj_optarg);
01240 break;
01241 case 't':
01242 icedemo.opt.turn_srv = pj_str(pj_optarg);
01243 break;
01244 case 'T':
01245 icedemo.opt.turn_tcp = PJ_TRUE;
01246 break;
01247 case 'u':
01248 icedemo.opt.turn_username = pj_str(pj_optarg);
01249 break;
01250 case 'p':
01251 icedemo.opt.turn_password = pj_str(pj_optarg);
01252 break;
01253 case 'F':
01254 icedemo.opt.turn_fingerprint = PJ_TRUE;
01255 break;
01256 case 'R':
01257 icedemo.opt.regular = PJ_TRUE;
01258 break;
01259 case 'L':
01260 icedemo.opt.log_file = pj_optarg;
01261 break;
01262 default:
01263 printf("Argument \"%s\" is not valid. Use -h to see help",
01264 argv[pj_optind]);
01265 return 1;
01266 }
01267 }
01268
01269 status = icedemo_init();
01270 if (status != PJ_SUCCESS)
01271 return 1;
01272
01273 icedemo_console();
01274
01275 err_exit("Quitting..", PJ_SUCCESS);
01276 return 0;
01277 }
.