pj_status_t create_stun_config(pj_pool_t *pool, pj_stun_config *stun_cfg) { pj_ioqueue_t *ioqueue; pj_timer_heap_t *timer_heap; pj_lock_t *lock; pj_status_t status; status = pj_ioqueue_create(pool, 64, &ioqueue); if (status != PJ_SUCCESS) { app_perror(" pj_ioqueue_create()", status); return status; } status = pj_timer_heap_create(pool, 256, &timer_heap); if (status != PJ_SUCCESS) { app_perror(" pj_timer_heap_create()", status); pj_ioqueue_destroy(ioqueue); return status; } pj_lock_create_recursive_mutex(pool, NULL, &lock); pj_timer_heap_set_lock(timer_heap, lock, PJ_TRUE); pj_stun_config_init(stun_cfg, mem, 0, ioqueue, timer_heap); return PJ_SUCCESS; }
int stun_sock_test(void) { struct pjlib_state pjlib_state; pj_stun_config stun_cfg; pj_ioqueue_t *ioqueue = NULL; pj_timer_heap_t *timer_heap = NULL; pj_pool_t *pool = NULL; pj_status_t status; int ret = 0; pool = pj_pool_create(mem, NULL, 512, 512, NULL); status = pj_ioqueue_create(pool, 12, &ioqueue); if (status != PJ_SUCCESS) { app_perror(" pj_ioqueue_create()", status); ret = -4; goto on_return; } status = pj_timer_heap_create(pool, 100, &timer_heap); if (status != PJ_SUCCESS) { app_perror(" pj_timer_heap_create()", status); ret = -8; goto on_return; } pj_stun_config_init(&stun_cfg, mem, 0, ioqueue, timer_heap); DO_TEST(timeout_test(&stun_cfg, PJ_FALSE)); DO_TEST(timeout_test(&stun_cfg, PJ_TRUE)); DO_TEST(missing_attr_test(&stun_cfg, PJ_FALSE)); DO_TEST(missing_attr_test(&stun_cfg, PJ_TRUE)); DO_TEST(keep_alive_test(&stun_cfg)); on_return: if (timer_heap) pj_timer_heap_destroy(timer_heap); if (ioqueue) pj_ioqueue_destroy(ioqueue); if (pool) pj_pool_release(pool); return ret; }
pj_status_t create_stun_config(pj_pool_t *pool, pj_stun_config *stun_cfg) { pj_ioqueue_t *ioqueue; pj_timer_heap_t *timer_heap; pj_status_t status; status = pj_ioqueue_create(pool, 64, &ioqueue); if (status != PJ_SUCCESS) { app_perror(" pj_ioqueue_create()", status); return status; } status = pj_timer_heap_create(pool, 256, &timer_heap); if (status != PJ_SUCCESS) { app_perror(" pj_timer_heap_create()", status); pj_ioqueue_destroy(ioqueue); return status; } pj_stun_config_init(stun_cfg, mem, 0, ioqueue, timer_heap); return PJ_SUCCESS; }
int sess_auth_test(void) { pj_pool_t *pool; int rc; PJ_LOG(3,(THIS_FILE, " STUN session authentication test")); /* Init STUN config */ pj_stun_config_init(0, &stun_cfg, mem, 0, NULL, NULL); /* Create pool and timer heap */ pool = pj_pool_create(mem, "authtest", 200, 200, NULL); if (pj_timer_heap_create(pool, 20, &stun_cfg.timer_heap)) { pj_pool_release(pool); return -5; } /* Basic retransmission test */ rc = run_client_test("Retransmission", // title PJ_FALSE, // server responding PJ_STUN_AUTH_NONE, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJNATH_ESTUNTIMEDOUT,// expected code NULL, // expected realm NULL, // expected nonce &retransmit_check // more check ); if (rc != 0) { goto done; } /* * Short term credential. * draft-ietf-behave-rfc3489bis-15#section-10.1.2 */ /* * If the message does not contain both a MESSAGE-INTEGRITY and a * USERNAME attribute, If the message is a request, the server MUST * reject the request with an error response. This response MUST * use an error code of 400 (Bad Request). */ rc = run_client_test("Missing MESSAGE-INTEGRITY (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* If the USERNAME does not contain a username value currently valid * within the server: If the message is a request, the server MUST * reject the request with an error response. This response MUST use * an error code of 401 (Unauthorized). */ rc = run_client_test("USERNAME mismatch (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm "anotheruser", // username NULL, // nonce "anotherpass", // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* Using the password associated with the username, compute the value * for the message-integrity as described in Section 15.4. If the * resulting value does not match the contents of the MESSAGE- * INTEGRITY attribute: * * - If the message is a request, the server MUST reject the request * with an error response. This response MUST use an error code * of 401 (Unauthorized). */ rc = run_client_test("MESSAGE-INTEGRITY mismatch (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm USERNAME, // username NULL, // nonce "anotherpass", // password PJ_FALSE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401),// expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* USERNAME is not present, server must respond with 400 (Bad * Request). */ rc = run_client_test("Missing USERNAME (short term)",// title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // realm NULL, // username NULL, // nonce NULL, // password PJ_TRUE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* Successful short term authentication */ rc = run_client_test("Successful scenario (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_SHORT_TERM, // client auth NULL, // realm USERNAME, // username NULL, // nonce PASSWORD, // password PJ_FALSE, // dummy MI PJ_FALSE, // expected error PJ_SUCCESS, // expected code NULL, // expected realm NULL, // expected nonce NULL // more check ); if (rc != 0) { goto done; } /* * (our own) Extended tests for long term credential */ /* When server wants to use short term credential, but request has * REALM, reject with .... 401 ??? */ rc = run_client_test("Unwanted REALM (short term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_SHORT_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // realm USERNAME, // username NULL, // nonce PASSWORD, // password PJ_TRUE, // dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* * Long term credential. * draft-ietf-behave-rfc3489bis-15#section-10.2.2 */ /* If the message does not contain a MESSAGE-INTEGRITY attribute, the * server MUST generate an error response with an error code of 401 * (Unauthorized). This response MUST include a REALM value. It is * RECOMMENDED that the REALM value be the domain name of the * provider of the STUN server. The response MUST include a NONCE, * selected by the server. The response SHOULD NOT contain a * USERNAME or MESSAGE-INTEGRITY attribute. */ rc = run_client_test("Missing M-I (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // client realm NULL, // client username NULL, // client nonce NULL, // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } /* If the message contains a MESSAGE-INTEGRITY attribute, but is * missing the USERNAME, REALM or NONCE attributes, the server MUST * generate an error response with an error code of 400 (Bad * Request). This response SHOULD NOT include a USERNAME, NONCE, * REALM or MESSAGE-INTEGRITY attribute. */ /* Missing USERNAME */ rc = run_client_test("Missing USERNAME (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // client realm NULL, // client username NONCE, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* Missing REALM */ rc = run_client_test("Missing REALM (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth NULL, // client realm USERNAME, // client username NONCE, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* Missing NONCE */ rc = run_client_test("Missing NONCE (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_NONE, // client auth REALM, // client realm USERNAME, // client username NULL, // client nonce PASSWORD, // client password PJ_TRUE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(400), // expected code NULL, // expected realm NULL, // expected nonce &long_term_check2 // more check ); if (rc != 0) { goto done; } /* If the NONCE is no longer valid, the server MUST generate an error * response with an error code of 438 (Stale Nonce). This response * MUST include a NONCE and REALM attribute and SHOULD NOT incude the * USERNAME or MESSAGE-INTEGRITY attribute. Servers can invalidate * nonces in order to provide additional security. See Section 4.3 * of [RFC2617] for guidelines. */ // how?? /* If the username in the USERNAME attribute is not valid, the server * MUST generate an error response with an error code of 401 * (Unauthorized). This response MUST include a REALM value. It is * RECOMMENDED that the REALM value be the domain name of the * provider of the STUN server. The response MUST include a NONCE, * selected by the server. The response SHOULD NOT contain a * USERNAME or MESSAGE-INTEGRITY attribute. */ rc = run_client_test("Invalid username (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth REALM, // client realm "anotheruser", // client username "a nonce", // client nonce "somepassword", // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } /* Successful long term authentication */ rc = run_client_test("Successful scenario (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth REALM, // client realm USERNAME, // client username "anothernonce", // client nonce PASSWORD, // client password PJ_FALSE, // client dummy MI PJ_FALSE, // expected error 0, // expected code NULL, // expected realm NULL, // expected nonce &long_term_check3 // more check ); if (rc != 0) { goto done; } /* * (our own) Extended tests for long term credential */ /* If REALM doesn't match, server must respond with 401 */ #if 0 // STUN session now will just use the realm sent in the // response, so this test will fail because it will // authenticate successfully. rc = run_client_test("Invalid REALM (long term)", // title PJ_TRUE, // server responding PJ_STUN_AUTH_LONG_TERM, // server auth PJ_STUN_AUTH_LONG_TERM, // client auth "anotherrealm", // client realm USERNAME, // client username NONCE, // client nonce PASSWORD, // client password PJ_FALSE, // client dummy MI PJ_TRUE, // expected error PJ_STATUS_FROM_STUN_CODE(401), // expected code REALM, // expected realm NONCE, // expected nonce &long_term_check1 // more check ); if (rc != 0) { goto done; } #endif /* Invalid HMAC */ /* Valid static short term, without NONCE */ /* Valid static short term, WITH NONCE */ /* Valid static long term (with NONCE */ /* Valid dynamic short term (without NONCE) */ /* Valid dynamic short term (with NONCE) */ /* Valid dynamic long term (with NONCE) */ done: pj_timer_heap_destroy(stun_cfg.timer_heap); pj_pool_release(pool); return rc; }
static int stun_destroy_test(void) { enum { LOOP = 500 }; struct stun_test_session test_sess; pj_sockaddr bind_addr; int addr_len; pj_caching_pool cp; pj_pool_t *pool; unsigned i; pj_status_t status; int rc = 0; PJ_LOG(3,(THIS_FILE, " STUN destroy concurrency test")); pj_bzero(&test_sess, sizeof(test_sess)); pj_caching_pool_init(&cp, NULL, 0); pool = pj_pool_create(&cp.factory, "testsess", 512, 512, NULL); pj_stun_config_init(&test_sess.stun_cfg, &cp.factory, 0, NULL, NULL); status = pj_timer_heap_create(pool, 1023, &test_sess.stun_cfg.timer_heap); pj_assert(status == PJ_SUCCESS); status = pj_lock_create_recursive_mutex(pool, NULL, &test_sess.lock); pj_assert(status == PJ_SUCCESS); pj_timer_heap_set_lock(test_sess.stun_cfg.timer_heap, test_sess.lock, PJ_TRUE); pj_assert(status == PJ_SUCCESS); status = pj_ioqueue_create(pool, 512, &test_sess.stun_cfg.ioqueue); pj_assert(status == PJ_SUCCESS); pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &test_sess.server_sock); pj_sockaddr_init(pj_AF_INET(), &bind_addr, NULL, 0); status = pj_sock_bind(test_sess.server_sock, &bind_addr, pj_sockaddr_get_len(&bind_addr)); pj_assert(status == PJ_SUCCESS); addr_len = sizeof(bind_addr); status = pj_sock_getsockname(test_sess.server_sock, &bind_addr, &addr_len); pj_assert(status == PJ_SUCCESS); test_sess.server_port = pj_sockaddr_get_port(&bind_addr); status = pj_event_create(pool, NULL, PJ_TRUE, PJ_FALSE, &test_sess.server_event); pj_assert(status == PJ_SUCCESS); for (i=0; i<SERVER_THREAD_CNT; ++i) { status = pj_thread_create(pool, NULL, &server_thread_proc, &test_sess, 0, 0, &test_sess.server_threads[i]); pj_assert(status == PJ_SUCCESS); } for (i=0; i<WORKER_THREAD_CNT; ++i) { status = pj_thread_create(pool, NULL, &worker_thread_proc, &test_sess, 0, 0, &test_sess.worker_threads[i]); pj_assert(status == PJ_SUCCESS); } /* Test 1: Main thread calls destroy while callback is processing response */ PJ_LOG(3,(THIS_FILE, " Destroy in main thread while callback is running")); for (i=0; i<LOOP; ++i) { int sleep = pj_rand() % 5; PJ_LOG(3,(THIS_FILE, " Try %-3d of %d", i+1, LOOP)); /* Test 1: destroy at the same time when receiving response */ pj_bzero(&test_sess.param, sizeof(test_sess.param)); test_sess.param.client_sleep_after_start = 20; test_sess.param.client_sleep_before_destroy = sleep; test_sess.param.server_wait_for_event = PJ_TRUE; stun_destroy_test_session(&test_sess); PJ_LOG(3,(THIS_FILE, " stun test a: sleep delay:%d: clients with response: %d", sleep, test_sess.param.client_got_response)); /* Test 2: destroy at the same time with STUN retransmit timer */ test_sess.param.server_drop_request = PJ_TRUE; test_sess.param.client_sleep_after_start = 0; test_sess.param.client_sleep_before_destroy = PJ_STUN_RTO_VALUE; test_sess.param.server_wait_for_event = PJ_FALSE; stun_destroy_test_session(&test_sess); PJ_LOG(3,(THIS_FILE, " stun test b: retransmit concurrency")); /* Test 3: destroy at the same time with receiving response * AND STUN retransmit timer */ test_sess.param.client_got_response = 0; test_sess.param.server_drop_request = PJ_FALSE; test_sess.param.client_sleep_after_start = PJ_STUN_RTO_VALUE; test_sess.param.client_sleep_before_destroy = 0; test_sess.param.server_wait_for_event = PJ_TRUE; stun_destroy_test_session(&test_sess); PJ_LOG(3,(THIS_FILE, " stun test c: clients with response: %d", test_sess.param.client_got_response)); pj_thread_sleep(10); ice_one_conc_test(&test_sess.stun_cfg, PJ_FALSE); pj_thread_sleep(10); } /* Avoid compiler warning */ goto on_return; on_return: test_sess.thread_quit_flag = PJ_TRUE; for (i=0; i<SERVER_THREAD_CNT; ++i) { pj_thread_join(test_sess.server_threads[i]); } for (i=0; i<WORKER_THREAD_CNT; ++i) { pj_thread_join(test_sess.worker_threads[i]); } pj_event_destroy(test_sess.server_event); pj_sock_close(test_sess.server_sock); pj_ioqueue_destroy(test_sess.stun_cfg.ioqueue); pj_timer_heap_destroy(test_sess.stun_cfg.timer_heap); pj_pool_release(pool); pj_caching_pool_destroy(&cp); PJ_LOG(3,(THIS_FILE, " Done. rc=%d", rc)); return rc; }
static int init() { int i; pj_status_t status; CHECK( pj_init() ); CHECK( pjlib_util_init() ); CHECK( pjnath_init() ); /* Check that server is specified */ if (!o.srv_addr) { printf("Error: server must be specified\n"); return PJ_EINVAL; } pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0); g.pool = pj_pool_create(&g.cp.factory, "main", 1000, 1000, NULL); /* Init global STUN config */ pj_stun_config_init(&g.stun_config, &g.cp.factory, 0, NULL, NULL); /* Create global timer heap */ CHECK( pj_timer_heap_create(g.pool, 1000, &g.stun_config.timer_heap) ); /* Create global ioqueue */ CHECK( pj_ioqueue_create(g.pool, 16, &g.stun_config.ioqueue) ); /* * Create peers */ for (i=0; i<(int)PJ_ARRAY_SIZE(g.peer); ++i) { pj_stun_sock_cb stun_sock_cb; char name[] = "peer0"; pj_uint16_t port; pj_stun_sock_cfg ss_cfg; pj_str_t server; pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); stun_sock_cb.on_rx_data = &stun_sock_on_rx_data; stun_sock_cb.on_status = &stun_sock_on_status; g.peer[i].mapped_addr.addr.sa_family = pj_AF_INET(); pj_stun_sock_cfg_default(&ss_cfg); #if 1 /* make reading the log easier */ ss_cfg.ka_interval = 300; #endif name[strlen(name)-1] = '0'+i; status = pj_stun_sock_create(&g.stun_config, name, pj_AF_INET(), &stun_sock_cb, &ss_cfg, &g.peer[i], &g.peer[i].stun_sock); if (status != PJ_SUCCESS) { my_perror("pj_stun_sock_create()", status); return status; } if (o.stun_server) { server = pj_str(o.stun_server); port = PJ_STUN_PORT; } else { server = pj_str(o.srv_addr); port = (pj_uint16_t)(o.srv_port?atoi(o.srv_port):PJ_STUN_PORT); } status = pj_stun_sock_start(g.peer[i].stun_sock, &server, port, NULL); if (status != PJ_SUCCESS) { my_perror("pj_stun_sock_start()", status); return status; } } /* Start the worker thread */ CHECK( pj_thread_create(g.pool, "stun", &worker_thread, NULL, 0, 0, &g.thread) ); return PJ_SUCCESS; }
/* * Create server. */ PJ_DEF(pj_status_t) pj_turn_srv_create(pj_pool_factory *pf, pj_turn_srv **p_srv) { pj_pool_t *pool; pj_stun_session_cb sess_cb; pj_turn_srv *srv; unsigned i; pj_status_t status; PJ_ASSERT_RETURN(pf && p_srv, PJ_EINVAL); /* Create server and init core settings */ pool = pj_pool_create(pf, "srv%p", 1000, 1000, NULL); srv = PJ_POOL_ZALLOC_T(pool, pj_turn_srv); srv->obj_name = pool->obj_name; srv->core.pf = pf; srv->core.pool = pool; srv->core.tls_key = srv->core.tls_data = -1; /* Create ioqueue */ status = pj_ioqueue_create(pool, MAX_HANDLES, &srv->core.ioqueue); if (status != PJ_SUCCESS) goto on_error; /* Server mutex */ status = pj_lock_create_recursive_mutex(pool, srv->obj_name, &srv->core.lock); if (status != PJ_SUCCESS) goto on_error; /* Allocate TLS */ status = pj_thread_local_alloc(&srv->core.tls_key); if (status != PJ_SUCCESS) goto on_error; status = pj_thread_local_alloc(&srv->core.tls_data); if (status != PJ_SUCCESS) goto on_error; /* Create timer heap */ status = pj_timer_heap_create(pool, MAX_TIMER, &srv->core.timer_heap); if (status != PJ_SUCCESS) goto on_error; /* Configure lock for the timer heap */ pj_timer_heap_set_lock(srv->core.timer_heap, srv->core.lock, PJ_FALSE); /* Array of listeners */ srv->core.listener = (pj_turn_listener**) pj_pool_calloc(pool, MAX_LISTENERS, sizeof(srv->core.listener[0])); /* Create hash tables */ srv->tables.alloc = pj_hash_create(pool, MAX_CLIENTS); srv->tables.res = pj_hash_create(pool, MAX_CLIENTS); /* Init ports settings */ srv->ports.min_udp = srv->ports.next_udp = MIN_PORT; srv->ports.max_udp = MAX_PORT; srv->ports.min_tcp = srv->ports.next_tcp = MIN_PORT; srv->ports.max_tcp = MAX_PORT; /* Init STUN config */ pj_stun_config_init(&srv->core.stun_cfg, pf, 0, srv->core.ioqueue, srv->core.timer_heap); /* Init STUN credential */ srv->core.cred.type = PJ_STUN_AUTH_CRED_DYNAMIC; srv->core.cred.data.dyn_cred.user_data = srv; srv->core.cred.data.dyn_cred.get_auth = &pj_turn_get_auth; srv->core.cred.data.dyn_cred.get_password = &pj_turn_get_password; srv->core.cred.data.dyn_cred.verify_nonce = &pj_turn_verify_nonce; /* Create STUN session to handle new allocation */ pj_bzero(&sess_cb, sizeof(sess_cb)); sess_cb.on_rx_request = &on_rx_stun_request; sess_cb.on_send_msg = &on_tx_stun_msg; status = pj_stun_session_create(&srv->core.stun_cfg, srv->obj_name, &sess_cb, PJ_FALSE, NULL, &srv->core.stun_sess); if (status != PJ_SUCCESS) { goto on_error; } pj_stun_session_set_user_data(srv->core.stun_sess, srv); pj_stun_session_set_credential(srv->core.stun_sess, PJ_STUN_AUTH_LONG_TERM, &srv->core.cred); /* Array of worker threads */ srv->core.thread_cnt = MAX_THREADS; srv->core.thread = (pj_thread_t**) pj_pool_calloc(pool, srv->core.thread_cnt, sizeof(pj_thread_t*)); /* Start the worker threads */ for (i=0; i<srv->core.thread_cnt; ++i) { status = pj_thread_create(pool, srv->obj_name, &server_thread_proc, srv, 0, 0, &srv->core.thread[i]); if (status != PJ_SUCCESS) goto on_error; } /* We're done. Application should add listeners now */ PJ_LOG(4,(srv->obj_name, "TURN server v%s is running", pj_get_version())); *p_srv = srv; return PJ_SUCCESS; on_error: pj_turn_srv_destroy(srv); return status; }