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 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
00618 &cand_cnt, cand);
00619 if (status != PJ_SUCCESS)
00620 return -status;
00621
00622
00623 for (j=0; j<cand_cnt; ++j) {
00624 printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
00625 if (printed < 0)
00626 return -PJ_ETOOSMALL;
00627 p += printed;
00628 }
00629 }
00630
00631 if (p == buffer+maxlen)
00632 return -PJ_ETOOSMALL;
00633
00634 *p = '\0';
00635 return p - buffer;
00636 }
00637
00638
00639
00640
00641
00642
00643 static void icedemo_show_ice(void)
00644 {
00645 static char buffer[1000];
00646 int len;
00647
00648 if (icedemo.icest == NULL) {
00649 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00650 return;
00651 }
00652
00653 puts("General info");
00654 puts("---------------");
00655 printf("Component count : %d\n", icedemo.opt.comp_cnt);
00656 printf("Status : ");
00657 if (pj_ice_strans_sess_is_complete(icedemo.icest))
00658 puts("negotiation complete");
00659 else if (pj_ice_strans_sess_is_running(icedemo.icest))
00660 puts("negotiation is in progress");
00661 else if (pj_ice_strans_has_sess(icedemo.icest))
00662 puts("session ready");
00663 else
00664 puts("session not created");
00665
00666 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00667 puts("Create the session first to see more info");
00668 return;
00669 }
00670
00671 printf("Negotiated comp_cnt: %d\n",
00672 pj_ice_strans_get_running_comp_cnt(icedemo.icest));
00673 printf("Role : %s\n",
00674 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
00675 "controlled" : "controlling");
00676
00677 len = encode_session(buffer, sizeof(buffer));
00678 if (len < 0)
00679 err_exit("not enough buffer to show ICE status", -len);
00680
00681 puts("");
00682 printf("Local SDP (paste this to remote host):\n"
00683 "--------------------------------------\n"
00684 "%s\n", buffer);
00685
00686
00687 puts("");
00688 puts("Remote info:\n"
00689 "----------------------");
00690 if (icedemo.rem.cand_cnt==0) {
00691 puts("No remote info yet");
00692 } else {
00693 unsigned i;
00694
00695 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
00696 printf("Remote password : %s\n", icedemo.rem.pwd);
00697 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
00698
00699 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
00700 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
00701 if (len < 0)
00702 err_exit("not enough buffer to show ICE status", -len);
00703
00704 printf(" %s", buffer);
00705 }
00706 }
00707 }
00708
00709
00710
00711
00712
00713
00714 static void icedemo_input_remote(void)
00715 {
00716 char linebuf[80];
00717 unsigned media_cnt = 0;
00718 unsigned comp0_port = 0;
00719 char comp0_addr[80];
00720 pj_bool_t done = PJ_FALSE;
00721
00722 puts("Paste SDP from remote host, end with empty line");
00723
00724 reset_rem_info();
00725
00726 comp0_addr[0] = '\0';
00727
00728 while (!done) {
00729 int len;
00730 char *line;
00731
00732 printf(">");
00733 if (stdout) fflush(stdout);
00734
00735 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
00736 break;
00737
00738 len = strlen(linebuf);
00739 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
00740 linebuf[--len] = '\0';
00741
00742 line = linebuf;
00743 while (len && pj_isspace(*line))
00744 ++line, --len;
00745
00746 if (len==0)
00747 break;
00748
00749
00750 if (media_cnt > 1)
00751 continue;
00752
00753 switch (line[0]) {
00754 case 'm':
00755 {
00756 int cnt;
00757 char media[32], portstr[32];
00758
00759 ++media_cnt;
00760 if (media_cnt > 1) {
00761 puts("Media line ignored");
00762 break;
00763 }
00764
00765 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
00766 if (cnt != 2) {
00767 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
00768 goto on_error;
00769 }
00770
00771 comp0_port = atoi(portstr);
00772
00773 }
00774 break;
00775 case 'c':
00776 {
00777 int cnt;
00778 char c[32], net[32], ip[80];
00779
00780 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
00781 if (cnt != 3) {
00782 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
00783 goto on_error;
00784 }
00785
00786 strcpy(comp0_addr, ip);
00787 }
00788 break;
00789 case 'a':
00790 {
00791 char *attr = strtok(line+2, ": \t\r\n");
00792 if (strcmp(attr, "ice-ufrag")==0) {
00793 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
00794 } else if (strcmp(attr, "ice-pwd")==0) {
00795 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
00796 } else if (strcmp(attr, "rtcp")==0) {
00797 char *val = attr+strlen(attr)+1;
00798 int af, cnt;
00799 int port;
00800 char net[32], ip[64];
00801 pj_str_t tmp_addr;
00802 pj_status_t status;
00803
00804 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
00805 if (cnt != 3) {
00806 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
00807 goto on_error;
00808 }
00809
00810 if (strchr(ip, ':'))
00811 af = pj_AF_INET6();
00812 else
00813 af = pj_AF_INET();
00814
00815 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
00816 tmp_addr = pj_str(ip);
00817 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
00818 &tmp_addr);
00819 if (status != PJ_SUCCESS) {
00820 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
00821 goto on_error;
00822 }
00823 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
00824
00825 } else if (strcmp(attr, "candidate")==0) {
00826 char *sdpcand = attr+strlen(attr)+1;
00827 int af, cnt;
00828 char foundation[32], transport[12], ipaddr[80], type[32];
00829 pj_str_t tmpaddr;
00830 int comp_id, prio, port;
00831 pj_ice_sess_cand *cand;
00832 pj_status_t status;
00833
00834 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
00835 foundation,
00836 &comp_id,
00837 transport,
00838 &prio,
00839 ipaddr,
00840 &port,
00841 type);
00842 if (cnt != 7) {
00843 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
00844 goto on_error;
00845 }
00846
00847 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
00848 pj_bzero(cand, sizeof(*cand));
00849
00850 if (strcmp(type, "host")==0)
00851 cand->type = PJ_ICE_CAND_TYPE_HOST;
00852 else if (strcmp(type, "srflx")==0)
00853 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
00854 else if (strcmp(type, "relay")==0)
00855 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
00856 else {
00857 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
00858 type));
00859 goto on_error;
00860 }
00861
00862 cand->comp_id = (pj_uint8_t)comp_id;
00863 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
00864 cand->prio = prio;
00865
00866 if (strchr(ipaddr, ':'))
00867 af = pj_AF_INET6();
00868 else
00869 af = pj_AF_INET();
00870
00871 tmpaddr = pj_str(ipaddr);
00872 pj_sockaddr_init(af, &cand->addr, NULL, 0);
00873 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
00874 if (status != PJ_SUCCESS) {
00875 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
00876 ipaddr));
00877 goto on_error;
00878 }
00879
00880 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
00881
00882 ++icedemo.rem.cand_cnt;
00883
00884 if (cand->comp_id > icedemo.rem.comp_cnt)
00885 icedemo.rem.comp_cnt = cand->comp_id;
00886 }
00887 }
00888 break;
00889 }
00890 }
00891
00892 if (icedemo.rem.cand_cnt==0 ||
00893 icedemo.rem.ufrag[0]==0 ||
00894 icedemo.rem.pwd[0]==0 ||
00895 icedemo.rem.comp_cnt == 0)
00896 {
00897 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
00898 goto on_error;
00899 }
00900
00901 if (comp0_port==0 || comp0_addr[0]=='\0') {
00902 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
00903 goto on_error;
00904 } else {
00905 int af;
00906 pj_str_t tmp_addr;
00907 pj_status_t status;
00908
00909 if (strchr(comp0_addr, ':'))
00910 af = pj_AF_INET6();
00911 else
00912 af = pj_AF_INET();
00913
00914 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
00915 tmp_addr = pj_str(comp0_addr);
00916 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
00917 &tmp_addr);
00918 if (status != PJ_SUCCESS) {
00919 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
00920 goto on_error;
00921 }
00922 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
00923 }
00924
00925 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
00926 icedemo.rem.cand_cnt));
00927 return;
00928
00929 on_error:
00930 reset_rem_info();
00931 }
00932
00933
00934
00935
00936
00937 static void icedemo_start_nego(void)
00938 {
00939 pj_str_t rufrag, rpwd;
00940 pj_status_t status;
00941
00942 if (icedemo.icest == NULL) {
00943 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00944 return;
00945 }
00946
00947 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00948 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00949 return;
00950 }
00951
00952 if (icedemo.rem.cand_cnt == 0) {
00953 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
00954 return;
00955 }
00956
00957 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
00958
00959 status = pj_ice_strans_start_ice(icedemo.icest,
00960 pj_cstr(&rufrag, icedemo.rem.ufrag),
00961 pj_cstr(&rpwd, icedemo.rem.pwd),
00962 icedemo.rem.cand_cnt,
00963 icedemo.rem.cand);
00964 if (status != PJ_SUCCESS)
00965 icedemo_perror("Error starting ICE", status);
00966 else
00967 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
00968 }
00969
00970
00971
00972
00973
00974 static void icedemo_send_data(unsigned comp_id, const char *data)
00975 {
00976 pj_status_t status;
00977
00978 if (icedemo.icest == NULL) {
00979 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00980 return;
00981 }
00982
00983 if (!pj_ice_strans_has_sess(icedemo.icest)) {
00984 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00985 return;
00986 }
00987
00988
00989
00990
00991
00992
00993
00994
00995 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
00996 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
00997 return;
00998 }
00999
01000 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
01001 &icedemo.rem.def_addr[comp_id-1],
01002 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
01003 if (status != PJ_SUCCESS)
01004 icedemo_perror("Error sending data", status);
01005 else
01006 PJ_LOG(3,(THIS_FILE, "Data sent"));
01007 }
01008
01009
01010
01011
01012
01013 static void icedemo_help_menu(void)
01014 {
01015 puts("");
01016 puts("-= Help on using ICE and this icedemo program =-");
01017 puts("");
01018 puts("This application demonstrates how to use ICE in pjnath without having\n"
01019 "to use the SIP protocol. To use this application, you will need to run\n"
01020 "two instances of this application, to simulate two ICE agents.\n");
01021
01022 puts("Basic ICE flow:\n"
01023 " create instance [menu \"c\"]\n"
01024 " repeat these steps as wanted:\n"
01025 " - init session as offerer or answerer [menu \"i\"]\n"
01026 " - display our SDP [menu \"s\"]\n"
01027 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
01028 " copy-pasting the SDP to the other icedemo application\n"
01029 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
01030 " instance [menu \"r\"]\n"
01031 " - begin ICE negotiation in our end [menu \"b\"], and \n"
01032 " - immediately begin ICE negotiation in the other icedemo instance\n"
01033 " - ICE negotiation will run, and result will be printed to screen\n"
01034 " - send application data to remote [menu \"x\"]\n"
01035 " - end/stop ICE session [menu \"e\"]\n"
01036 " destroy instance [menu \"d\"]\n"
01037 "");
01038
01039 puts("");
01040 puts("This concludes the help screen.");
01041 puts("");
01042 }
01043
01044
01045
01046
01047
01048 static void icedemo_print_menu(void)
01049 {
01050 puts("");
01051 puts("+----------------------------------------------------------------------+");
01052 puts("| M E N U |");
01053 puts("+---+------------------------------------------------------------------+");
01054 puts("| c | create Create the instance |");
01055 puts("| d | destroy Destroy the instance |");
01056 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
01057 puts("| e | stop End/stop ICE session |");
01058 puts("| s | show Display local ICE info |");
01059 puts("| r | remote Input remote ICE info |");
01060 puts("| b | start Begin ICE negotiation |");
01061 puts("| x | send <compid> .. Send data to remote |");
01062 puts("+---+------------------------------------------------------------------+");
01063 puts("| h | help * Help! * |");
01064 puts("| q | quit Quit |");
01065 puts("+----------------------------------------------------------------------+");
01066 }
01067
01068
01069
01070
01071
01072 static void icedemo_console(void)
01073 {
01074 pj_bool_t app_quit = PJ_FALSE;
01075
01076 while (!app_quit) {
01077 char input[80], *cmd;
01078 const char *SEP = " \t\r\n";
01079 int len;
01080
01081 icedemo_print_menu();
01082
01083 printf("Input: ");
01084 if (stdout) fflush(stdout);
01085
01086 pj_bzero(input, sizeof(input));
01087 if (fgets(input, sizeof(input), stdin) == NULL)
01088 break;
01089
01090 len = strlen(input);
01091 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
01092 input[--len] = '\0';
01093
01094 cmd = strtok(input, SEP);
01095 if (!cmd)
01096 continue;
01097
01098 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
01099
01100 icedemo_create_instance();
01101
01102 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
01103
01104 icedemo_destroy_instance();
01105
01106 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
01107
01108 char *role = strtok(NULL, SEP);
01109 if (role)
01110 icedemo_init_session(*role);
01111 else
01112 puts("error: Role required");
01113
01114 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
01115
01116 icedemo_stop_session();
01117
01118 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
01119
01120 icedemo_show_ice();
01121
01122 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
01123
01124 icedemo_input_remote();
01125
01126 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
01127
01128 icedemo_start_nego();
01129
01130 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
01131
01132 char *comp = strtok(NULL, SEP);
01133
01134 if (!comp) {
01135 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
01136 } else {
01137 char *data = comp + strlen(comp) + 1;
01138 if (!data)
01139 data = "";
01140 icedemo_send_data(atoi(comp), data);
01141 }
01142
01143 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
01144
01145 icedemo_help_menu();
01146
01147 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
01148
01149 app_quit = PJ_TRUE;
01150
01151 } else {
01152
01153 printf("Invalid command '%s'\n", cmd);
01154
01155 }
01156 }
01157 }
01158
01159
01160
01161
01162
01163 static void icedemo_usage()
01164 {
01165 puts("Usage: icedemo [optons]");
01166 printf("icedemo v%s by pjsip.org\n", pj_get_version());
01167 puts("");
01168 puts("General options:");
01169 puts(" --comp-cnt, -c N Component count (default=1)");
01170 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
01171 puts(" resolution");
01172 puts(" --max-host, -H N Set max number of host candidates to N");
01173 puts(" --regular, -R Use regular nomination (default aggressive)");
01174 puts(" --log-file, -L FILE Save output to log FILE");
01175 puts(" --help, -h Display this screen.");
01176 puts("");
01177 puts("STUN related options:");
01178 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
01179 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
01180 puts(" name if DNS SRV resolution is used.");
01181 puts("");
01182 puts("TURN related options:");
01183 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
01184 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
01185 puts(" name if DNS SRV resolution is used.");
01186 puts(" --turn-tcp, -T Use TCP to connect to TURN server");
01187 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
01188 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
01189 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
01190 puts("");
01191 }
01192
01193
01194
01195
01196
01197 int main(int argc, char *argv[])
01198 {
01199 struct pj_getopt_option long_options[] = {
01200 { "comp-cnt", 1, 0, 'c'},
01201 { "nameserver", 1, 0, 'n'},
01202 { "max-host", 1, 0, 'H'},
01203 { "help", 0, 0, 'h'},
01204 { "stun-srv", 1, 0, 's'},
01205 { "turn-srv", 1, 0, 't'},
01206 { "turn-tcp", 0, 0, 'T'},
01207 { "turn-username", 1, 0, 'u'},
01208 { "turn-password", 1, 0, 'p'},
01209 { "turn-fingerprint", 0, 0, 'F'},
01210 { "regular", 0, 0, 'R'},
01211 { "log-file", 1, 0, 'L'},
01212 };
01213 int c, opt_id;
01214 pj_status_t status;
01215
01216 icedemo.opt.comp_cnt = 1;
01217 icedemo.opt.max_host = -1;
01218
01219 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
01220 switch (c) {
01221 case 'c':
01222 icedemo.opt.comp_cnt = atoi(pj_optarg);
01223 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
01224 puts("Invalid component count value");
01225 return 1;
01226 }
01227 break;
01228 case 'n':
01229 icedemo.opt.ns = pj_str(pj_optarg);
01230 break;
01231 case 'H':
01232 icedemo.opt.max_host = atoi(pj_optarg);
01233 break;
01234 case 'h':
01235 icedemo_usage();
01236 return 0;
01237 case 's':
01238 icedemo.opt.stun_srv = pj_str(pj_optarg);
01239 break;
01240 case 't':
01241 icedemo.opt.turn_srv = pj_str(pj_optarg);
01242 break;
01243 case 'T':
01244 icedemo.opt.turn_tcp = PJ_TRUE;
01245 break;
01246 case 'u':
01247 icedemo.opt.turn_username = pj_str(pj_optarg);
01248 break;
01249 case 'p':
01250 icedemo.opt.turn_password = pj_str(pj_optarg);
01251 break;
01252 case 'F':
01253 icedemo.opt.turn_fingerprint = PJ_TRUE;
01254 break;
01255 case 'R':
01256 icedemo.opt.regular = PJ_TRUE;
01257 break;
01258 case 'L':
01259 icedemo.opt.log_file = pj_optarg;
01260 break;
01261 default:
01262 printf("Argument \"%s\" is not valid. Use -h to see help",
01263 argv[pj_optind]);
01264 return 1;
01265 }
01266 }
01267
01268 status = icedemo_init();
01269 if (status != PJ_SUCCESS)
01270 return 1;
01271
01272 icedemo_console();
01273
01274 err_exit("Quitting..", PJ_SUCCESS);
01275 return 0;
01276 }
.