BLOG | DOCUMENTATION | TRAC

Home --> Documentations --> PJNATH Reference

ice_demo, an interactive ICE endpoint

This sample demonstrates how to use ICE stream transport without using signaling protocol such as SIP. It provides interactive user interface to create and manage the ICE sessions as well as to exchange SDP with another ice_demo instance.

Features of the demo application:

  • supports host, STUN, and TURN candidates
  • disabling of host candidates
  • DNS SRV resolution for STUN and TURN servers
  • TCP connection to TURN server
  • Optional use of fingerprint for TURN
  • prints and parse SDP containing ICE infos
  • exchange SDP with copy/paste

This file is pjsip-apps/src/samples/icedemo.c

Screenshot on WinXP:

ice_demo.jpg

ice_demo on WinXP

00001 /* $Id: icedemo.c 3841 2011-10-24 09:28:13Z ming $ */
00002 /* 
00003  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
00004  *
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 2 of the License, or
00008  * (at your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
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 /* For this demo app, configure longer STUN keep-alive time
00029  * so that it does't clutter the screen output.
00030  */
00031 #define KA_INTERVAL 300
00032 
00033 
00034 /* This is our global variables */
00035 static struct app_t
00036 {
00037     /* Command line options are stored here */
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     /* Our global variables */
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     /* Variables to store parsed remote ICE info */
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 /* Utility to display error messages */
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 /* Utility: display error message and exit application (usually
00085  * because of fatal error.
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  * This function checks for events from both timer and ioqueue (for
00130  * network events). It is invoked by the worker thread.
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     /* Poll the timer to run it and also to retrieve the earliest entry. */
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     /* timer_heap_poll should never ever returns negative value, or otherwise
00149      * ioqueue_poll() will block forever!
00150      */
00151     pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
00152     if (timeout.msec >= 1000) timeout.msec = 999;
00153 
00154     /* compare the value with the timeout to wait from timer, and use the 
00155      * minimum value. 
00156     */
00157     if (PJ_TIME_VAL_GT(timeout, max_timeout))
00158         timeout = max_timeout;
00159 
00160     /* Poll ioqueue. 
00161      * Repeat polling the ioqueue while we have immediate events, because
00162      * timer heap may process more than one events, so if we only process
00163      * one network events at a time (such as when IOCP backend is used),
00164      * the ioqueue may have trouble keeping up with the request rate.
00165      *
00166      * For example, for each send() request, one network event will be
00167      *   reported by ioqueue for the send() completion. If we don't poll
00168      *   the ioqueue often enough, the send() completion will not be
00169      *   reported in timely manner.
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  * This is the worker thread that polls event in the background.
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  * This is the callback that is registered to the ICE stream transport to
00211  * receive notification about incoming data. By "data" it means application
00212  * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
00213  * as STUN connectivity checks or TURN signaling).
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     // Don't do this! It will ruin the packet buffer in case TCP is used!
00228     //((char*)pkt)[size] = '\0';
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  * This is the callback that is registered to the ICE stream transport to
00239  * receive notification about ICE state progression.
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 /* log callback to write to file */
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  * This is the main application initialization function. It is called
00273  * once (and only once) during application initialization sequence by 
00274  * main().
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     /* Initialize the libraries before anything else */
00286     CHECK( pj_init() );
00287     CHECK( pjlib_util_init() );
00288     CHECK( pjnath_init() );
00289 
00290     /* Must create pool factory, where memory allocations come from */
00291     pj_caching_pool_init(&icedemo.cp, NULL, 0);
00292 
00293     /* Init our ICE settings with null values */
00294     pj_ice_strans_cfg_default(&icedemo.ice_cfg);
00295 
00296     icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
00297 
00298     /* Create application memory pool */
00299     icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo", 
00300                                   512, 512, NULL);
00301 
00302     /* Create timer heap for timer stuff */
00303     CHECK( pj_timer_heap_create(icedemo.pool, 100, 
00304                                 &icedemo.ice_cfg.stun_cfg.timer_heap) );
00305 
00306     /* and create ioqueue for network I/O stuff */
00307     CHECK( pj_ioqueue_create(icedemo.pool, 16, 
00308                              &icedemo.ice_cfg.stun_cfg.ioqueue) );
00309 
00310     /* something must poll the timer heap and ioqueue, 
00311      * unless we're on Symbian where the timer heap and ioqueue run
00312      * on themselves.
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     /* Create DNS resolver if nameserver is set */
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     /* -= Start initializing ICE stream transport config =- */
00333 
00334     /* Maximum number of host candidates */
00335     if (icedemo.opt.max_host != -1)
00336         icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
00337 
00338     /* Nomination strategy */
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     /* Configure STUN/srflx candidate resolution */
00345     if (icedemo.opt.stun_srv.slen) {
00346         char *pos;
00347 
00348         /* Command line option may contain port number */
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         /* For this demo app, configure longer STUN keep-alive time
00360          * so that it does't clutter the screen output.
00361          */
00362         icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
00363     }
00364 
00365     /* Configure TURN candidate */
00366     if (icedemo.opt.turn_srv.slen) {
00367         char *pos;
00368 
00369         /* Command line option may contain port number */
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         /* TURN credential */
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         /* Connection type to TURN server */
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         /* For this demo app, configure longer keep-alive time
00393          * so that it does't clutter the screen output.
00394          */
00395         icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
00396     }
00397 
00398     /* -= That's it for now, initialization is complete =- */
00399     return PJ_SUCCESS;
00400 }
00401 
00402 
00403 /*
00404  * Create ICE stream transport instance, invoked from the menu.
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     /* init the callback */
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     /* create the instance */
00422     status = pj_ice_strans_create("icedemo",                /* object name  */
00423                                 &icedemo.ice_cfg,           /* settings     */
00424                                 icedemo.opt.comp_cnt,       /* comp_cnt     */
00425                                 NULL,                       /* user data    */
00426                                 &icecb,                     /* callback     */
00427                                 &icedemo.icest)             /* instance ptr */
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 /* Utility to nullify parsed remote info */
00436 static void reset_rem_info(void)
00437 {
00438     pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
00439 }
00440 
00441 
00442 /*
00443  * Destroy ICE stream transport instance, invoked from the menu.
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  * Create ICE session, invoked from the menu.
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  * Stop/destroy ICE session, invoked from the menu.
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 /* Utility to create a=candidate SDP attribute */
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  * Encode ICE information in SDP.
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     /* Write "dummy" SDP v=, o=, s=, and t= lines */
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     /* Get ufrag and pwd from current session */
00569     pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
00570                                 NULL, NULL);
00571 
00572     /* Write the a=ice-ufrag and a=ice-pwd attributes */
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     /* Write each component */
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         /* Get default candidate for the component */
00587         status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
00588         if (status != PJ_SUCCESS)
00589             return -status;
00590 
00591         /* Write the default address */
00592         if (comp==0) {
00593             /* For component 1, default address is in m= and c= lines */
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             /* For component 2, default address is in a=rtcp line */
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             /* For other components, we'll just invent this.. */
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         /* Enumerate all candidates for this component */
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         /* And encode the candidates as SDP */
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  * Show information contained in the ICE stream transport. This is
00641  * invoked from the menu.
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  * Input and parse SDP from the remote (containing remote's ICE information) 
00712  * and save it to global variables.
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         /* Ignore subsequent media descriptors */
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  * Start ICE negotiation! This function is invoked from the menu.
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  * Send application data to remote agent.
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     if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
00990         PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
00991         return;
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  * Display help for the menu.
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  * Display console menu
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  * Main console loop.
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  * Display program usage.
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  * And here's the main()
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 }

.

 


PJNATH - Open Source NAT traversal helper library supporting STUN, TURN, and ICE
Copyright (C) 2006-2009 Teluu Inc.