From 8ec5bd6b3d5bafb1d3ab11236a3adc45ac5f04d8 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 6 Jun 2008 14:47:10 +0000 Subject: Major major modifications related to ticket #485 (support for TURN-07): - Added STUN socket transport pj_stun_sock - Integration of TURN-07 to ICE - Major refactoring in ICE stream transport to make it simpler - Major modification (i.e. API change) in almost everywhere else - Much more elaborate STUN, TURN, and ICE tests in pjnath-test git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1988 74dad513-b988-da41-8d7b-12977e46ad98 --- pjnath/src/pjnath-test/ice_test.c | 1096 +++++++++++++------- pjnath/src/pjnath-test/server.c | 652 ++++++++++++ pjnath/src/pjnath-test/server.h | 108 ++ pjnath/src/pjnath-test/sess_auth.c | 6 + pjnath/src/pjnath-test/stun_sock_test.c | 844 +++++++++++++++ pjnath/src/pjnath-test/test.c | 112 ++ pjnath/src/pjnath-test/test.h | 29 + pjnath/src/pjnath-test/turn_sock_test.c | 515 ++++++++++ pjnath/src/pjnath/errno.c | 2 + pjnath/src/pjnath/ice_session.c | 176 ++-- pjnath/src/pjnath/ice_strans.c | 1708 ++++++++++++++++--------------- pjnath/src/pjnath/stun_msg.c | 18 +- pjnath/src/pjnath/stun_session.c | 253 ++++- pjnath/src/pjnath/stun_sock.c | 829 +++++++++++++++ pjnath/src/pjnath/stun_transaction.c | 39 +- pjnath/src/pjnath/turn_session.c | 158 ++- pjnath/src/pjnath/turn_sock.c | 216 ++-- pjnath/src/pjturn-client/client_main.c | 6 +- pjnath/src/pjturn-srv/auth.c | 3 +- 19 files changed, 5314 insertions(+), 1456 deletions(-) create mode 100644 pjnath/src/pjnath-test/server.c create mode 100644 pjnath/src/pjnath-test/server.h create mode 100644 pjnath/src/pjnath-test/stun_sock_test.c create mode 100644 pjnath/src/pjnath-test/turn_sock_test.c create mode 100644 pjnath/src/pjnath/stun_sock.c (limited to 'pjnath/src') diff --git a/pjnath/src/pjnath-test/ice_test.c b/pjnath/src/pjnath-test/ice_test.c index 38bb6d02..4f644c1d 100644 --- a/pjnath/src/pjnath-test/ice_test.c +++ b/pjnath/src/pjnath-test/ice_test.c @@ -17,506 +17,858 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "test.h" +#include "server.h" -#define THIS_FILE "ice_test.c" +enum +{ + NO = 0, + YES = 1, + SRV = 3, +}; + +#define NODELAY 0xFFFFFFFF +#define SRV_DOMAIN "pjsip.lab.domain" +#define INDENT " " -struct ice_data +/* Client flags */ +enum { - const char *obj_name; - pj_bool_t complete; - pj_status_t err_code; - unsigned rx_rtp_cnt; - unsigned rx_rtcp_cnt; - - unsigned rx_rtp_count; - char last_rx_rtp_data[32]; - unsigned rx_rtcp_count; - char last_rx_rtcp_data[32]; + WRONG_TURN = 1, + DEL_ON_ERR = 2, }; -static pj_stun_config stun_cfg; -static void on_ice_complete(pj_ice_strans *icest, - pj_status_t status) +/* Test results */ +struct test_result { - struct ice_data *id = (struct ice_data*) icest->user_data; - id->complete = PJ_TRUE; - id->err_code = status; - PJ_LOG(3,(THIS_FILE, " ICE %s complete %s", id->obj_name, - (status==PJ_SUCCESS ? "successfully" : "with failure"))); -} + pj_status_t init_status; /* init successful? */ + pj_status_t nego_status; /* negotiation successful? */ + unsigned rx_cnt[4]; /* Number of data received */ +}; -static void on_rx_data(pj_ice_strans *icest, unsigned comp_id, - void *pkt, pj_size_t size, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len) +/* Test session configuration */ +struct test_cfg { - struct ice_data *id = (struct ice_data*) icest->user_data; - - if (comp_id == 1) { - id->rx_rtp_cnt++; - pj_memcpy(id->last_rx_rtp_data, pkt, size); - id->last_rx_rtp_data[size] = '\0'; - } else if (comp_id == 2) { - id->rx_rtcp_cnt++; - pj_memcpy(id->last_rx_rtcp_data, pkt, size); - id->last_rx_rtcp_data[size] = '\0'; - } else { - pj_assert(!"Invalid component ID"); - } + pj_ice_sess_role role; /* Role. */ + unsigned comp_cnt; /* Component count */ + unsigned enable_host; /* Enable host candidates */ + unsigned enable_stun; /* Enable srflx candidates */ + unsigned enable_turn; /* Enable turn candidates */ + unsigned client_flag; /* Client flags */ + + unsigned answer_delay; /* Delay before sending SDP */ + unsigned send_delay; /* Delay before sending data */ + unsigned destroy_delay; /* Delay before destroy() */ + + struct test_result expected;/* Expected result */ +}; - PJ_UNUSED_ARG(src_addr); - PJ_UNUSED_ARG(src_addr_len); -} +/* ICE endpoint state */ +struct ice_ept +{ + struct test_cfg cfg; /* Configuratino. */ + pj_ice_strans *ice; /* ICE stream transport */ + struct test_result result;/* Test result. */ + pj_str_t ufrag; /* username fragment. */ + pj_str_t pass; /* password */ +}; -static void handle_events(unsigned msec_timeout) +/* The test session */ +struct test_sess { - pj_time_val delay; + pj_pool_t *pool; + pj_stun_config *stun_cfg; + pj_dns_resolver *resolver; - pj_timer_heap_poll(stun_cfg.timer_heap, NULL); + test_server *server; - delay.sec = 0; - delay.msec = msec_timeout; - pj_time_val_normalize(&delay); - - pj_ioqueue_poll(stun_cfg.ioqueue, &delay); -} + unsigned server_flag; + struct ice_ept caller; + struct ice_ept callee; +}; -/* Basic create and destroy test */ -static int ice_basic_create_destroy_test() +static void ice_on_rx_data(pj_ice_strans *ice_st, + unsigned comp_id, + void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +static void ice_on_ice_complete(pj_ice_strans *ice_st, + pj_ice_strans_op op, + pj_status_t status); +static void destroy_sess(struct test_sess *sess, unsigned wait_msec); + +/* Create ICE stream transport */ +static int create_ice_strans(struct test_sess *test_sess, + struct ice_ept *ept, + pj_ice_strans **p_ice) { - pj_ice_strans *im; - pj_ice_strans_cb icest_cb; + pj_ice_strans *ice; + pj_ice_strans_cb ice_cb; + pj_ice_strans_cfg ice_cfg; + pj_sockaddr hostip; + char serverip[PJ_INET6_ADDRSTRLEN]; pj_status_t status; - PJ_LOG(3,(THIS_FILE, "...basic create/destroy")); + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + return -1030; + + pj_sockaddr_print(&hostip, serverip, sizeof(serverip), 0); + + /* Init callback structure */ + pj_bzero(&ice_cb, sizeof(ice_cb)); + ice_cb.on_rx_data = &ice_on_rx_data; + ice_cb.on_ice_complete = &ice_on_ice_complete; + + /* Init ICE stream transport configuration structure */ + pj_ice_strans_cfg_default(&ice_cfg); + pj_memcpy(&ice_cfg.stun_cfg, test_sess->stun_cfg, sizeof(pj_stun_config)); + if ((ept->cfg.enable_stun & SRV)==SRV || (ept->cfg.enable_turn & SRV)==SRV) + ice_cfg.resolver = test_sess->resolver; + + if (ept->cfg.enable_stun & YES) { + if ((ept->cfg.enable_stun & SRV) == SRV) { + ice_cfg.stun.server = pj_str(SRV_DOMAIN); + } else { + ice_cfg.stun.server = pj_str(serverip); + } + ice_cfg.stun.port = STUN_SERVER_PORT; + } - pj_bzero(&icest_cb, sizeof(icest_cb)); - icest_cb.on_ice_complete = &on_ice_complete; - icest_cb.on_rx_data = &on_rx_data; + if (ept->cfg.enable_host == 0) { + ice_cfg.stun.no_host_cands = PJ_TRUE; + } else { + ice_cfg.stun.no_host_cands = PJ_FALSE; + ice_cfg.stun.loop_addr = PJ_TRUE; + } - status = pj_ice_strans_create(&stun_cfg, "icetest", 2, NULL, &icest_cb, &im); - if (status != PJ_SUCCESS) - return -10; - pj_ice_strans_destroy(im); + if (ept->cfg.enable_turn & YES) { + if ((ept->cfg.enable_turn & SRV) == SRV) { + ice_cfg.turn.server = pj_str(SRV_DOMAIN); + } else { + ice_cfg.turn.server = pj_str(serverip); + } + ice_cfg.turn.port = TURN_SERVER_PORT; + ice_cfg.turn.conn_type = PJ_TURN_TP_UDP; + ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC; + ice_cfg.turn.auth_cred.data.static_cred.realm = pj_str(SRV_DOMAIN); + if (ept->cfg.client_flag & WRONG_TURN) + ice_cfg.turn.auth_cred.data.static_cred.username = pj_str("xxx"); + else + ice_cfg.turn.auth_cred.data.static_cred.username = pj_str(TURN_USERNAME); + ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; + ice_cfg.turn.auth_cred.data.static_cred.data = pj_str(TURN_PASSWD); + } - return 0; -} + /* Create ICE stream transport */ + status = pj_ice_strans_create(NULL, &ice_cfg, ept->cfg.comp_cnt, + (void*)ept, &ice_cb, + &ice); + if (status != PJ_SUCCESS) { + app_perror(INDENT "err: pj_ice_strans_create()", status); + return status; + } + pj_create_unique_string(test_sess->pool, &ept->ufrag); + pj_create_unique_string(test_sess->pool, &ept->pass); -static pj_status_t start_ice(pj_ice_strans *ist, pj_ice_strans *remote) + /* Looks alright */ + *p_ice = ice; + return PJ_SUCCESS; +} + +/* Create test session */ +static int create_sess(pj_stun_config *stun_cfg, + unsigned server_flag, + struct test_cfg *caller_cfg, + struct test_cfg *callee_cfg, + struct test_sess **p_sess) { - unsigned count; - pj_ice_sess_cand cand[PJ_ICE_MAX_CAND]; + pj_pool_t *pool; + struct test_sess *sess; + pj_str_t ns_ip; + pj_uint16_t ns_port; + unsigned flags; pj_status_t status; - count = PJ_ARRAY_SIZE(cand); - status = pj_ice_strans_enum_cands(remote, &count, cand); - if (status != PJ_SUCCESS) - return status; - - return pj_ice_strans_start_ice(ist, &remote->ice->rx_ufrag, &remote->ice->rx_pass, - count, cand); -} + /* Create session structure */ + pool = pj_pool_create(mem, "testsess", 512, 512, NULL); + sess = PJ_POOL_ZALLOC_T(pool, struct test_sess); + sess->pool = pool; + sess->stun_cfg = stun_cfg; + pj_memcpy(&sess->caller.cfg, caller_cfg, sizeof(*caller_cfg)); + sess->caller.result.init_status = sess->caller.result.nego_status = PJ_EPENDING; -struct dummy_cand -{ - unsigned comp_id; - pj_ice_cand_type type; - const char *addr; - unsigned port; -}; + pj_memcpy(&sess->callee.cfg, callee_cfg, sizeof(*callee_cfg)); + sess->callee.result.init_status = sess->callee.result.nego_status = PJ_EPENDING; -static int init_ice_st(pj_ice_strans *ice_st, - pj_bool_t add_valid_comp, - unsigned dummy_cnt, - struct dummy_cand cand[]) -{ - pj_str_t a; - pj_status_t status; - unsigned i; + /* Create server */ + flags = server_flag; + status = create_test_server(stun_cfg, flags, SRV_DOMAIN, &sess->server); + if (status != PJ_SUCCESS) { + app_perror(INDENT "error: create_test_server()", status); + destroy_sess(sess, 500); + return -10; + } + sess->server->turn_respond_allocate = + sess->server->turn_respond_refresh = PJ_TRUE; - /* Create components */ - for (i=0; icomp_cnt; ++i) { - status = pj_ice_strans_create_comp(ice_st, i+1, PJ_ICE_ST_OPT_DONT_ADD_CAND, NULL); - if (status != PJ_SUCCESS) - return -21; + /* Create resolver */ + status = pj_dns_resolver_create(mem, NULL, 0, stun_cfg->timer_heap, + stun_cfg->ioqueue, &sess->resolver); + if (status != PJ_SUCCESS) { + app_perror(INDENT "error: pj_dns_resolver_create()", status); + destroy_sess(sess, 500); + return -20; } - /* Add dummy candidates */ - for (i=0; iresolver, 1, &ns_ip, &ns_port); + if (status != PJ_SUCCESS) { + app_perror( INDENT "error: pj_dns_resolver_set_ns()", status); + destroy_sess(sess, 500); + return -21; + } - pj_sockaddr_in_init(&addr, pj_cstr(&a, cand[i].addr), (pj_uint16_t)cand[i].port); - status = pj_ice_strans_add_cand(ice_st, cand[i].comp_id, cand[i].type, - 65535, &addr, PJ_FALSE); - if (status != PJ_SUCCESS) - return -22; + /* Create caller ICE stream transport */ + status = create_ice_strans(sess, &sess->caller, &sess->caller.ice); + if (status != PJ_SUCCESS) { + destroy_sess(sess, 500); + return -30; } - /* Add the real candidate */ - if (add_valid_comp) { - for (i=0; icomp_cnt; ++i) { - status = pj_ice_strans_add_cand(ice_st, i+1, PJ_ICE_CAND_TYPE_HOST, 65535, - &ice_st->comp[i]->local_addr.ipv4, PJ_TRUE); - if (status != PJ_SUCCESS) - return -23; - } + /* Create callee ICE stream transport */ + status = create_ice_strans(sess, &sess->callee, &sess->callee.ice); + if (status != PJ_SUCCESS) { + destroy_sess(sess, 500); + return -40; } + *p_sess = sess; return 0; } - -/* When ICE completes, both agents should agree on the same candidate pair. - * Check that the remote address selected by agent1 is equal to the - * local address of selected by agent 2. - */ -static int verify_address(pj_ice_strans *agent1, pj_ice_strans *agent2, - unsigned comp_id) +/* Destroy test session */ +static void destroy_sess(struct test_sess *sess, unsigned wait_msec) { - pj_ice_sess_cand *rcand, *lcand; - int lcand_id; - - if (agent1->ice->comp[comp_id-1].valid_check == NULL) { - PJ_LOG(3,(THIS_FILE, "....error: valid_check not set for comp_id %d", comp_id)); - return -60; + if (sess->caller.ice) { + pj_ice_strans_destroy(sess->caller.ice); + sess->caller.ice = NULL; } - /* Get default remote candidate of agent 1 */ - rcand = agent1->ice->comp[comp_id-1].valid_check->rcand; + if (sess->callee.ice) { + pj_ice_strans_destroy(sess->callee.ice); + sess->callee.ice = NULL; + } - /* Get default local candidate of agent 2 */ - pj_ice_sess_find_default_cand(agent2->ice, comp_id, &lcand_id); - if (lcand_id < 0) - return -62; + poll_events(sess->stun_cfg, wait_msec, PJ_FALSE); - lcand = &agent2->ice->lcand[lcand_id]; + if (sess->resolver) { + pj_dns_resolver_destroy(sess->resolver, PJ_FALSE); + sess->resolver = NULL; + } - if (pj_memcmp(&rcand->addr, &lcand->addr, sizeof(pj_sockaddr_in))!=0) { - PJ_LOG(3,(THIS_FILE, "....error: the selected addresses are incorrect for comp_id %d", comp_id)); - return -64; + if (sess->server) { + destroy_test_server(sess->server); + sess->server = NULL; } - return 0; + if (sess->pool) { + pj_pool_t *pool = sess->pool; + sess->pool = NULL; + pj_pool_release(pool); + } } - -/* Perform ICE test with the following parameters: - * - * - title: The title of the test - * - ocand_cnt, - * ocand Additional candidates to be added to offerer - * - acand_cnt, - * acand Additional candidates to be added to answerer - * - * The additional candidates are normally invalid candidates, meaning - * they won't be reachable by the agents. They are used to "confuse" - * ICE processing. - */ -static int perform_ice_test(const char *title, - pj_bool_t expected_success, - unsigned comp_cnt, - pj_bool_t add_valid_comp, - unsigned wait_before_send, - unsigned max_total_time, - unsigned ocand_cnt, - struct dummy_cand ocand[], - unsigned acand_cnt, - struct dummy_cand acand[]) +static void ice_on_rx_data(pj_ice_strans *ice_st, + unsigned comp_id, + void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len) { - pj_ice_strans *im1, *im2; - pj_ice_strans_cb icest_cb; - struct ice_data *id1, *id2; - pj_timestamp t_start, t_end; - unsigned i; - pj_str_t data_from_offerer, data_from_answerer; - pj_status_t status; + struct ice_ept *ept; -#define CHECK_COMPLETE() if (id1->complete && id2->complete) { \ - if (t_end.u32.lo==0) pj_get_timestamp(&t_end); \ - } else {} + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); - PJ_LOG(3,(THIS_FILE, "...%s", title)); + ept = (struct ice_ept*) pj_ice_strans_get_user_data(ice_st); + ept->result.rx_cnt[comp_id]++; +} - pj_bzero(&t_end, sizeof(t_end)); - pj_bzero(&icest_cb, sizeof(icest_cb)); - icest_cb.on_ice_complete = &on_ice_complete; - icest_cb.on_rx_data = &on_rx_data; +static void ice_on_ice_complete(pj_ice_strans *ice_st, + pj_ice_strans_op op, + pj_status_t status) +{ + struct ice_ept *ept; + + ept = (struct ice_ept*) pj_ice_strans_get_user_data(ice_st); + switch (op) { + case PJ_ICE_STRANS_OP_INIT: + ept->result.init_status = status; + if (status != PJ_SUCCESS && (ept->cfg.client_flag & DEL_ON_ERR)) { + pj_ice_strans_destroy(ice_st); + ept->ice = NULL; + } + break; + case PJ_ICE_STRANS_OP_NEGOTIATION: + ept->result.nego_status = status; + break; + default: + pj_assert(!"Unknown op"); + } +} + - /* Create first ICE */ - status = pj_ice_strans_create(&stun_cfg, "offerer", comp_cnt, NULL, &icest_cb, &im1); - if (status != PJ_SUCCESS) - return -20; +/* Start ICE negotiation on the endpoint, based on parameter from + * the other endpoint. + */ +static pj_status_t start_ice(struct ice_ept *ept, const struct ice_ept *remote) +{ + pj_ice_sess_cand rcand[32]; + unsigned i, rcand_cnt = 0; + pj_status_t status; - id1 = PJ_POOL_ZALLOC_T(im1->pool, struct ice_data); - id1->obj_name = "offerer"; - im1->user_data = id1; + /* Enum remote candidates */ + for (i=0; icfg.comp_cnt; ++i) { + unsigned cnt = PJ_ARRAY_SIZE(rcand) - rcand_cnt; + status = pj_ice_strans_enum_cands(remote->ice, i+1, &cnt, rcand+rcand_cnt); + if (status != PJ_SUCCESS) { + app_perror(INDENT "err: pj_ice_strans_enum_cands()", status); + return status; + } + rcand_cnt += cnt; + } - /* Init components */ - status = init_ice_st(im1, add_valid_comp, ocand_cnt, ocand); - if (status != 0) + status = pj_ice_strans_start_ice(ept->ice, &remote->ufrag, &remote->pass, + rcand_cnt, rcand); + if (status != PJ_SUCCESS) { + app_perror(INDENT "err: pj_ice_strans_start_ice()", status); return status; + } - /* Create second ICE */ - status = pj_ice_strans_create(&stun_cfg, "answerer", comp_cnt, NULL, &icest_cb, &im2); - if (status != PJ_SUCCESS) - return -25; + return PJ_SUCCESS; +} - id2 = PJ_POOL_ZALLOC_T(im2->pool, struct ice_data); - id2->obj_name = "answerer"; - im2->user_data = id2; - /* Init components */ - status = init_ice_st(im2, add_valid_comp, acand_cnt, acand); - if (status != 0) - return status; +/* Check that the pair in both agents are matched */ +static int check_pair(const struct ice_ept *ept1, const struct ice_ept *ept2, + int start_err) +{ + unsigned i, min_cnt, max_cnt; + + if (ept1->cfg.comp_cnt < ept2->cfg.comp_cnt) { + min_cnt = ept1->cfg.comp_cnt; + max_cnt = ept2->cfg.comp_cnt; + } else { + min_cnt = ept2->cfg.comp_cnt; + max_cnt = ept1->cfg.comp_cnt; + } + /* Must have valid pair for common components */ + for (i=0; iice, i+1); + if (c1 == NULL) { + PJ_LOG(3,("", INDENT "err: unable to get valid pair for ice1 " + "component %d", i+1)); + return start_err - 2; + } - /* Init ICE on im2 */ - status = pj_ice_strans_init_ice(im2, PJ_ICE_SESS_ROLE_CONTROLLED, NULL, NULL); - if (status != PJ_SUCCESS) - return -29; + c2 = pj_ice_strans_get_valid_pair(ept2->ice, i+1); + if (c2 == NULL) { + PJ_LOG(3,("", INDENT "err: unable to get valid pair for ice2 " + "component %d", i+1)); + return start_err - 4; + } - /* Start ICE on im2 */ - status = start_ice(im2, im1); - if (status != PJ_SUCCESS) { - app_perror(" error starting ICE", status); - return -30; + if (pj_sockaddr_cmp(&c1->rcand->addr, &c2->lcand->addr) != 0) { + PJ_LOG(3,("", INDENT "err: candidate pair does not match " + "for component %d", i+1)); + return start_err - 6; + } } - /* Start ICE on im1 */ - status = start_ice(im1, im2); - if (status != PJ_SUCCESS) - return -35; + /* Extra components must not have valid pair */ + for (; icfg.comp_cnt>i && + pj_ice_strans_get_valid_pair(ept1->ice, i+1) != NULL) + { + PJ_LOG(3,("", INDENT "err: ice1 shouldn't have valid pair " + "for component %d", i+1)); + return start_err - 8; + } + if (ept2->cfg.comp_cnt>i && + pj_ice_strans_get_valid_pair(ept2->ice, i+1) != NULL) + { + PJ_LOG(3,("", INDENT "err: ice2 shouldn't have valid pair " + "for component %d", i+1)); + return start_err - 9; + } + } - /* Apply delay to let other checks commence */ - pj_thread_sleep(40); + return 0; +} - /* Mark start time */ - pj_get_timestamp(&t_start); - /* Poll for wait_before_send msecs before we send the first data */ - if (expected_success) { - for (;;) { - pj_timestamp t_now; +#define WAIT_UNTIL(timeout,expr, RC) { \ + pj_time_val t0, t; \ + pj_gettimeofday(&t0); \ + RC = -1; \ + for (;;) { \ + poll_events(stun_cfg, 10, PJ_FALSE); \ + pj_gettimeofday(&t); \ + if (expr) { \ + rc = PJ_SUCCESS; \ + break; \ + } \ + if (t.sec - t0.sec > (timeout)) break; \ + } \ + } + + +static int perform_test(const char *title, + pj_stun_config *stun_cfg, + unsigned server_flag, + struct test_cfg *caller_cfg, + struct test_cfg *callee_cfg) +{ + pjlib_state pjlib_state; + struct test_sess *sess; + int rc; - handle_events(1); + PJ_LOG(3,("", INDENT "%s", title)); - CHECK_COMPLETE(); + capture_pjlib_state(stun_cfg, &pjlib_state); - pj_get_timestamp(&t_now); - if (pj_elapsed_msec(&t_start, &t_now) >= wait_before_send) - break; - } + rc = create_sess(stun_cfg, server_flag, caller_cfg, callee_cfg, &sess); + if (rc != 0) + return rc; - /* Send data. It must be successful! */ - data_from_offerer = pj_str("from offerer"); - status = pj_ice_sess_send_data(im1->ice, 1, data_from_offerer.ptr, data_from_offerer.slen); - if (status != PJ_SUCCESS) - return -47; +#define ALL_READY (sess->caller.result.init_status!=PJ_EPENDING && \ + sess->callee.result.init_status!=PJ_EPENDING) - data_from_answerer = pj_str("from answerer"); - status = pj_ice_sess_send_data(im2->ice, 1, data_from_answerer.ptr, data_from_answerer.slen); - if (status != PJ_SUCCESS) { - app_perror(" error sending packet", status); - return -48; - } + /* Wait until both ICE transports are initialized */ + WAIT_UNTIL(30, ALL_READY, rc); - /* Poll to allow data to be received */ - for (;;) { - pj_timestamp t_now; - handle_events(1); - CHECK_COMPLETE(); - pj_get_timestamp(&t_now); - if (pj_elapsed_msec(&t_start, &t_now) >= (wait_before_send + 200)) - break; - } + if (!ALL_READY) { + PJ_LOG(3,("", INDENT "err: init timed-out")); + destroy_sess(sess, 500); + return -100; } - /* Just wait until both completes, or timed out */ - while (!id1->complete || !id2->complete) { - pj_timestamp t_now; + if (sess->caller.result.init_status != sess->caller.cfg.expected.init_status) { + app_perror(INDENT "err: caller init", sess->caller.result.init_status); + destroy_sess(sess, 500); + return -102; + } + if (sess->callee.result.init_status != sess->callee.cfg.expected.init_status) { + app_perror(INDENT "err: callee init", sess->callee.result.init_status); + destroy_sess(sess, 500); + return -104; + } - handle_events(1); + /* Failure condition */ + if (sess->caller.result.init_status != PJ_SUCCESS || + sess->callee.result.init_status != PJ_SUCCESS) + { + rc = 0; + goto on_return; + } - CHECK_COMPLETE(); - pj_get_timestamp(&t_now); - if (pj_elapsed_msec(&t_start, &t_now) >= max_total_time) { - PJ_LOG(3,(THIS_FILE, "....error: timed-out")); - return -50; - } + /* Init ICE on caller */ + rc = pj_ice_strans_init_ice(sess->caller.ice, sess->caller.cfg.role, + &sess->caller.ufrag, &sess->caller.pass); + if (rc != PJ_SUCCESS) { + app_perror(INDENT "err: caller pj_ice_strans_init_ice()", rc); + destroy_sess(sess, 500); + return -100; } - /* Mark end-time */ - CHECK_COMPLETE(); + /* Init ICE on callee */ + rc = pj_ice_strans_init_ice(sess->callee.ice, sess->callee.cfg.role, + &sess->callee.ufrag, &sess->callee.pass); + if (rc != PJ_SUCCESS) { + app_perror(INDENT "err: callee pj_ice_strans_init_ice()", rc); + destroy_sess(sess, 500); + return -110; + } - /* If expected to fail, then just check that both fail */ - if (!expected_success) { - /* Check status */ - if (id1->err_code == PJ_SUCCESS) - return -51; - if (id2->err_code == PJ_SUCCESS) - return -52; - goto on_return; + /* Start ICE on callee */ + rc = start_ice(&sess->callee, &sess->caller); + if (rc != PJ_SUCCESS) { + destroy_sess(sess, 500); + return -120; } - /* Check status */ - if (id1->err_code != PJ_SUCCESS) - return -53; - if (id2->err_code != PJ_SUCCESS) - return -56; + /* Wait for callee's answer_delay */ + poll_events(stun_cfg, sess->callee.cfg.answer_delay, PJ_FALSE); - /* Verify that offerer gets answerer's transport address */ - for (i=0; icaller, &sess->callee); + if (rc != PJ_SUCCESS) { + destroy_sess(sess, 500); + return -130; } - /* And the other way around */ - for (i=0; icaller.result.nego_status!=PJ_EPENDING && \ + sess->callee.result.nego_status!=PJ_EPENDING) + WAIT_UNTIL(30, ALL_DONE, rc); + + if (!ALL_DONE) { + PJ_LOG(3,("", INDENT "err: negotiation timed-out")); + destroy_sess(sess, 500); + return -140; } - /* Check that data is received in offerer */ - if (id1->rx_rtp_cnt != 1) { - PJ_LOG(3,(THIS_FILE, "....error: data not received in offerer")); - return -80; + if (sess->caller.result.nego_status != sess->caller.cfg.expected.nego_status) { + app_perror(INDENT "err: caller negotiation failed", sess->caller.result.nego_status); + destroy_sess(sess, 500); + return -150; } - if (pj_strcmp2(&data_from_answerer, id1->last_rx_rtp_data) != 0) { - PJ_LOG(3,(THIS_FILE, "....error: data mismatch in offerer")); - return -82; + + if (sess->callee.result.nego_status != sess->callee.cfg.expected.nego_status) { + app_perror(INDENT "err: callee negotiation failed", sess->callee.result.nego_status); + destroy_sess(sess, 500); + return -160; } - /* And the same in answerer */ - if (id2->rx_rtp_cnt != 1) { - PJ_LOG(3,(THIS_FILE, "....error: data not received in answerer")); - return -84; + /* Verify that both agents have agreed on the same pair */ + rc = check_pair(&sess->caller, &sess->callee, -170); + if (rc != 0) { + destroy_sess(sess, 500); + return rc; } - if (pj_strcmp2(&data_from_offerer, id2->last_rx_rtp_data) != 0) { - PJ_LOG(3,(THIS_FILE, "....error: data mismatch in answerer")); - return -82; + rc = check_pair(&sess->callee, &sess->caller, -180); + if (rc != 0) { + destroy_sess(sess, 500); + return rc; } + /* Looks like everything is okay */ -on_return: + /* Destroy ICE stream transports first to let it de-allocate + * TURN relay (otherwise there'll be timer/memory leak, unless + * we wait for long time in the last poll_events() below). + */ + if (sess->caller.ice) { + pj_ice_strans_destroy(sess->caller.ice); + sess->caller.ice = NULL; + } + + if (sess->callee.ice) { + pj_ice_strans_destroy(sess->callee.ice); + sess->callee.ice = NULL; + } - /* Done */ - PJ_LOG(3,(THIS_FILE, "....success: ICE completed in %d msec, waiting..", - pj_elapsed_msec(&t_start, &t_end))); +on_return: + /* Wait.. */ + poll_events(stun_cfg, 500, PJ_FALSE); - /* Wait for some more time */ - for (;;) { - pj_timestamp t_now; + /* Now destroy everything */ + destroy_sess(sess, 500); - pj_get_timestamp(&t_now); - if (pj_elapsed_msec(&t_start, &t_now) > max_total_time) - break; + /* Flush events */ + poll_events(stun_cfg, 100, PJ_FALSE); - handle_events(1); + rc = check_pjlib_state(stun_cfg, &pjlib_state); + if (rc != 0) { + return rc; } - - pj_ice_strans_destroy(im1); - pj_ice_strans_destroy(im2); - handle_events(100); return 0; } +#define ROLE1 PJ_ICE_SESS_ROLE_CONTROLLED +#define ROLE2 PJ_ICE_SESS_ROLE_CONTROLLING int ice_test(void) { - int rc = 0; pj_pool_t *pool; - pj_ioqueue_t *ioqueue; - pj_timer_heap_t *timer_heap; - enum { D1=500, D2=5000, D3=15000 }; - struct dummy_cand ocand[] = - { - {1, PJ_ICE_CAND_TYPE_SRFLX, "127.1.1.1", 65534 }, - {2, PJ_ICE_CAND_TYPE_SRFLX, "127.1.1.1", 65535 }, - }; - struct dummy_cand acand[] = + pj_stun_config stun_cfg; + unsigned i; + int rc; + struct sess_cfg_t { + const char *title; + unsigned server_flag; + struct test_cfg ua1; + struct test_cfg ua2; + } sess_cfg[] = { - {1, PJ_ICE_CAND_TYPE_SRFLX, "127.2.2.2", 65534 }, - {2, PJ_ICE_CAND_TYPE_SRFLX, "127.2.2.2", 65535 }, + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + { + "hosts candidates only", + 0xFFFF, + {ROLE1, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }, + { + "host and srflxes", + 0xFFFF, + {ROLE1, 1, YES, YES, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, YES, YES, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }, + { + "host vs relay", + 0xFFFF, + {ROLE1, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }, + { + "relay vs host", + 0xFFFF, + {ROLE1, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }, + { + "relay vs relay", + 0xFFFF, + {ROLE1, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }, + { + "all candidates", + 0xFFFF, + {ROLE1, 1, YES, YES, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, YES, YES, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }, }; - pool = pj_pool_create(mem, NULL, 4000, 4000, NULL); - pj_ioqueue_create(pool, 12, &ioqueue); - pj_timer_heap_create(pool, 100, &timer_heap); - - pj_stun_config_init(&stun_cfg, mem, 0, ioqueue, timer_heap); - -#if 0 - pj_log_set_level(5); -#endif - - //goto test; - - /* Basic create/destroy */ - rc = ice_basic_create_destroy_test(); - if (rc != 0) - goto on_return; - - /* Direct communication */ - rc = perform_ice_test("Simple test (1 component)", PJ_TRUE, 1, PJ_TRUE, D1, D2, 0, NULL, 0, NULL); - if (rc != 0) - goto on_return; - - /* Failure case (all checks fail) */ -#if 0 - /* Cannot just add an SRFLX candidate; it needs a base */ - rc = perform_ice_test("Failure case (all checks fail)", PJ_FALSE, 1, PJ_FALSE, D3, D3, 1, ocand, 1, acand); - if (rc != 0) - goto on_return; -#endif - - /* Direct communication with invalid address */ - rc = perform_ice_test("With 1 unreachable address", PJ_TRUE, 1, PJ_TRUE, D1, D2, 1, ocand, 0, NULL); - if (rc != 0) - goto on_return; + pool = pj_pool_create(mem, NULL, 512, 512, NULL); + rc = create_stun_config(pool, &stun_cfg); + if (rc != PJ_SUCCESS) { + pj_pool_release(pool); + return -7; + } - /* Direct communication with invalid address */ - rc = perform_ice_test("With 2 unreachable addresses (one each)", PJ_TRUE, 1, PJ_TRUE, D1, D2, 1, ocand, 1, acand); - if (rc != 0) - goto on_return; + /* Simple test first with host candidate */ + if (1) { + struct sess_cfg_t cfg = + { + "Basic with host candidates", + 0x0, + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + {ROLE1, 1, YES, NO, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, YES, NO, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }; + + rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + + cfg.ua1.comp_cnt = 4; + cfg.ua2.comp_cnt = 4; + rc = perform_test("Basic with host candidates, 4 components", + &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + } - /* Direct communication with two components */ -//test: - rc = perform_ice_test("With two components (RTP and RTCP)", PJ_TRUE, 2, PJ_TRUE, D1, D2, 0, NULL, 0, NULL); - if (rc != 0) - goto on_return; + /* Simple test first with srflx candidate */ + if (1) { + struct sess_cfg_t cfg = + { + "Basic with srflx candidates", + 0xFFFF, + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + {ROLE1, 1, YES, YES, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, YES, YES, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }; + + rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + + cfg.ua1.comp_cnt = 4; + cfg.ua2.comp_cnt = 4; + + rc = perform_test("Basic with srflx candidates, 4 components", + &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + } - goto on_return; + /* Simple test with relay candidate */ + if (1) { + struct sess_cfg_t cfg = + { + "Basic with relay candidates", + 0xFFFF, + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + {ROLE1, 1, NO, NO, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, + {ROLE2, 1, NO, NO, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} + }; + + rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + + cfg.ua1.comp_cnt = 4; + cfg.ua2.comp_cnt = 4; + + rc = perform_test("Basic with relay candidates, 4 components", + &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + } - /* Direct communication with mismatch number of components */ + /* Failure test with STUN resolution */ + if (1) { + struct sess_cfg_t cfg = + { + "STUN resolution failure", + 0x0, + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + {ROLE1, 2, NO, YES, NO, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, + {ROLE2, 2, NO, YES, NO, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}} + }; + + rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + + cfg.ua1.client_flag |= DEL_ON_ERR; + cfg.ua2.client_flag |= DEL_ON_ERR; + + rc = perform_test("STUN resolution failure with destroy on callback", + &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + } - /* Direct communication with 2 components and 2 invalid address */ - rc = perform_ice_test("With 2 two components and 2 unreachable address", PJ_TRUE, 2, PJ_TRUE, D1, D2, 1, ocand, 1, acand); - if (rc != 0) - goto on_return; + /* Failure test with TURN resolution */ + if (1) { + struct sess_cfg_t cfg = + { + "TURN allocation failure", + 0xFFFF, + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + {ROLE1, 4, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}}, + {ROLE2, 4, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}} + }; + + rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + + cfg.ua1.client_flag |= DEL_ON_ERR; + cfg.ua2.client_flag |= DEL_ON_ERR; + + rc = perform_test("TURN allocation failure with destroy on callback", + &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + } + /* STUN failure, testing TURN deallocation */ + if (1) { + struct sess_cfg_t cfg = + { + "STUN failure, testing TURN deallocation", + 0xFFFF & (~(CREATE_STUN_SERVER)), + /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ + {ROLE1, 2, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, + {ROLE2, 2, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}} + }; + + rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + + cfg.ua1.client_flag |= DEL_ON_ERR; + cfg.ua2.client_flag |= DEL_ON_ERR; + + rc = perform_test("STUN failure, testing TURN deallocation (cb)", + &stun_cfg, cfg.server_flag, + &cfg.ua1, &cfg.ua2); + if (rc != 0) + goto on_return; + } + rc = 0; + /* Iterate each test item */ + for (i=0; ititle)); + + /* For each test item, test with various answer delay */ + for (d=0; dua1.answer_delay = delay[d]; + cfg->ua2.answer_delay = delay[d]; + + /* For each test item, test with role conflict scenarios */ + for (j=0; jua1.role = role[j].ua1; + cfg->ua2.role = role[j].ua2; + + /* For each test item, test with different number of components */ + for (k1=1; k1<=2; ++k1) { + unsigned k2; + + cfg->ua1.comp_cnt = k1; + + for (k2=1; k2<=2; ++k2) { + char title[120]; + + sprintf(title, + "%s/%s, %dms answer delay, %d vs %d components", + pj_ice_sess_role_name(role[j].ua1), + pj_ice_sess_role_name(role[j].ua2), + delay[d], k1, k2); + + cfg->ua2.comp_cnt = k2; + rc = perform_test(title, &stun_cfg, cfg->server_flag, + &cfg->ua1, &cfg->ua2); + if (rc != 0) + goto on_return; + } + } + } + } + } on_return: - pj_log_set_level(3); - pj_ioqueue_destroy(stun_cfg.ioqueue); + destroy_stun_config(&stun_cfg); pj_pool_release(pool); return rc; } diff --git a/pjnath/src/pjnath-test/server.c b/pjnath/src/pjnath-test/server.c new file mode 100644 index 00000000..6d4f2e8b --- /dev/null +++ b/pjnath/src/pjnath-test/server.c @@ -0,0 +1,652 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "server.h" +#include "test.h" + +#define THIS_FILE "server.c" +#define MAX_STUN_PKT 1500 +#define TURN_NONCE "thenonce" + +static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); +static pj_bool_t turn_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); +static pj_bool_t alloc_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); + +pj_status_t create_test_server(pj_stun_config *stun_cfg, + pj_uint32_t flags, + const char *domain, + test_server **p_test_srv) +{ + pj_pool_t *pool; + test_server *test_srv; + pj_sockaddr hostip; + char strbuf[100]; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && domain && p_test_srv, PJ_EINVAL); + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + return status; + + pool = pj_pool_create(mem, THIS_FILE, 512, 512, NULL); + test_srv = (test_server*) PJ_POOL_ZALLOC_T(pool, test_server); + test_srv->pool = pool; + test_srv->flags = flags; + test_srv->stun_cfg = stun_cfg; + + pj_strdup2(pool, &test_srv->domain, domain); + test_srv->username = pj_str(TURN_USERNAME); + test_srv->passwd = pj_str(TURN_PASSWD); + + pj_ioqueue_op_key_init(&test_srv->send_key, sizeof(test_srv->send_key)); + + if (flags & CREATE_DNS_SERVER) { + status = pj_dns_server_create(mem, test_srv->stun_cfg->ioqueue, + pj_AF_INET(), DNS_SERVER_PORT, + 0, &test_srv->dns_server); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + /* Add DNS A record for the domain, for fallback */ + if (flags & CREATE_A_RECORD_FOR_DOMAIN) { + pj_dns_parsed_rr rr; + pj_str_t res_name; + pj_in_addr ip_addr; + + pj_strdup2(pool, &res_name, domain); + ip_addr = hostip.ipv4.sin_addr; + pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + } + + } + + if (flags & CREATE_STUN_SERVER) { + pj_activesock_cb stun_sock_cb; + pj_sockaddr bound_addr; + + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_data_recvfrom = &stun_on_data_recvfrom; + + pj_sockaddr_in_init(&bound_addr.ipv4, NULL, STUN_SERVER_PORT); + + status = pj_activesock_create_udp(pool, &bound_addr, NULL, + test_srv->stun_cfg->ioqueue, + &stun_sock_cb, test_srv, + &test_srv->stun_sock, NULL); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + status = pj_activesock_start_recvfrom(test_srv->stun_sock, pool, + MAX_STUN_PKT, 0); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + if (test_srv->dns_server && (flags & CREATE_STUN_SERVER_DNS_SRV)) { + pj_str_t res_name, target; + pj_dns_parsed_rr rr; + pj_in_addr ip_addr; + + /* Add DNS entries: + * _stun._udp.domain 60 IN SRV 0 0 PORT stun.domain. + * stun.domain IN A 127.0.0.1 + */ + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "_stun._udp.%s", domain); + pj_strdup2(pool, &res_name, strbuf); + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "stun.%s", domain); + pj_strdup2(pool, &target, strbuf); + pj_dns_init_srv_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, 0, 0, + STUN_SERVER_PORT, &target); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + + res_name = target; + ip_addr = hostip.ipv4.sin_addr; + pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + } + + } + + if (flags & CREATE_TURN_SERVER) { + pj_activesock_cb turn_sock_cb; + pj_sockaddr bound_addr; + + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_data_recvfrom = &turn_on_data_recvfrom; + + pj_sockaddr_in_init(&bound_addr.ipv4, NULL, TURN_SERVER_PORT); + + status = pj_activesock_create_udp(pool, &bound_addr, NULL, + test_srv->stun_cfg->ioqueue, + &turn_sock_cb, test_srv, + &test_srv->turn_sock, NULL); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + status = pj_activesock_start_recvfrom(test_srv->turn_sock, pool, + MAX_STUN_PKT, 0); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + if (test_srv->dns_server && (flags & CREATE_TURN_SERVER_DNS_SRV)) { + pj_str_t res_name, target; + pj_dns_parsed_rr rr; + pj_in_addr ip_addr; + + /* Add DNS entries: + * _turn._udp.domain 60 IN SRV 0 0 PORT turn.domain. + * turn.domain IN A 127.0.0.1 + */ + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "_turn._udp.%s", domain); + pj_strdup2(pool, &res_name, strbuf); + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "turn.%s", domain); + pj_strdup2(pool, &target, strbuf); + pj_dns_init_srv_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, 0, 0, + TURN_SERVER_PORT, &target); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + + res_name = target; + ip_addr = hostip.ipv4.sin_addr; + pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + } + } + + *p_test_srv = test_srv; + return PJ_SUCCESS; +} + +void destroy_test_server(test_server *test_srv) +{ + unsigned i; + + PJ_ASSERT_ON_FAIL(test_srv, return); + + for (i=0; iturn_alloc_cnt; ++i) { + pj_activesock_close(test_srv->turn_alloc[i].sock); + pj_pool_release(test_srv->turn_alloc[i].pool); + } + test_srv->turn_alloc_cnt = 0; + + if (test_srv->turn_sock) { + pj_activesock_close(test_srv->turn_sock); + test_srv->turn_sock = NULL; + } + + if (test_srv->stun_sock) { + pj_activesock_close(test_srv->stun_sock); + test_srv->stun_sock = NULL; + } + + if (test_srv->dns_server) { + pj_dns_server_destroy(test_srv->dns_server); + test_srv->dns_server = NULL; + } + + if (test_srv->pool) { + pj_pool_t *pool = test_srv->pool; + test_srv->pool = NULL; + pj_pool_release(pool); + } +} + +static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + test_server *test_srv; + pj_stun_msg *req, *resp = NULL; + pj_pool_t *pool; + pj_ssize_t len; + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + test_srv = (test_server*) pj_activesock_get_user_data(asock); + pool = pj_pool_create(test_srv->stun_cfg->pf, NULL, 512, 512, NULL); + + status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size, + PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET, + &req, NULL, NULL); + if (status != PJ_SUCCESS) + goto on_return; + + if (req->hdr.type != PJ_STUN_BINDING_REQUEST) { + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST, + NULL, &resp); + goto send_pkt; + } + + status = pj_stun_msg_create_response(pool, req, 0, NULL, &resp); + if (status != PJ_SUCCESS) + goto on_return; + + pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_XOR_MAPPED_ADDR, + PJ_TRUE, src_addr, addr_len); + +send_pkt: + status = pj_stun_msg_encode(resp, (pj_uint8_t*)data, MAX_STUN_PKT, + 0, NULL, &size); + if (status != PJ_SUCCESS) + goto on_return; + + len = size; + status = pj_activesock_sendto(asock, &test_srv->send_key, data, &len, + 0, src_addr, addr_len); + +on_return: + pj_pool_release(pool); + return PJ_TRUE; +} + + +static pj_stun_msg* create_success_response(test_server *test_srv, + turn_allocation *alloc, + pj_stun_msg *req, + pj_pool_t *pool, + unsigned lifetime, + pj_str_t *auth_key) +{ + pj_stun_msg *resp; + pj_str_t tmp; + pj_status_t status; + + /* Create response */ + status = pj_stun_msg_create_response(pool, req, 0, NULL, &resp); + if (status != PJ_SUCCESS) { + return NULL; + } + /* Add TURN_NONCE */ + pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, TURN_NONCE)); + /* Add LIFETIME */ + pj_stun_msg_add_uint_attr(pool, resp, PJ_STUN_ATTR_LIFETIME, lifetime); + if (lifetime != 0) { + /* Add RELAY-ADDRESS */ + pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_RELAY_ADDR, PJ_TRUE, &alloc->alloc_addr, + pj_sockaddr_get_len(&alloc->alloc_addr)); + /* Add XOR-MAPPED-ADDRESS */ + pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, &alloc->client_addr, + pj_sockaddr_get_len(&alloc->client_addr)); + } + + /* Add blank MESSAGE-INTEGRITY */ + pj_stun_msg_add_msgint_attr(pool, resp); + + /* Set auth key */ + pj_stun_create_key(pool, auth_key, &test_srv->domain, &test_srv->username, + PJ_STUN_PASSWD_PLAIN, &test_srv->passwd); + + return resp; +} + + +static pj_bool_t turn_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + test_server *test_srv; + pj_pool_t *pool; + turn_allocation *alloc; + pj_stun_msg *req, *resp = NULL; + pj_str_t auth_key = { NULL, 0 }; + char client_info[PJ_INET6_ADDRSTRLEN+10]; + unsigned i; + pj_ssize_t len; + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + pj_sockaddr_print(src_addr, client_info, sizeof(client_info), 3); + + test_srv = (test_server*) pj_activesock_get_user_data(asock); + pool = pj_pool_create(test_srv->stun_cfg->pf, NULL, 512, 512, NULL); + + status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size, + PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET | + PJ_STUN_NO_FINGERPRINT_CHECK, + &req, NULL, NULL); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(1,("", "STUN message decode error from client %s: %s", client_info, errmsg)); + goto on_return; + } + + /* Find the client */ + for (i=0; iturn_alloc_cnt; i++) { + if (pj_sockaddr_cmp(&test_srv->turn_alloc[i].client_addr, src_addr)==0) + break; + } + + if (i==test_srv->turn_alloc_cnt) { + /* New client */ + //pj_str_t ip_addr; + pj_stun_username_attr *uname; + pj_activesock_cb alloc_sock_cb; + turn_allocation *alloc; + + /* Must be Allocate request */ + if (req->hdr.type != PJ_STUN_ALLOCATE_REQUEST) { + PJ_LOG(1,(THIS_FILE, "Invalid %s %s from client %s", + pj_stun_get_method_name(req->hdr.type), + pj_stun_get_class_name(req->hdr.type), + client_info)); + + if (PJ_STUN_IS_REQUEST(req->hdr.type)) + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST, NULL, &resp); + goto send_pkt; + } + + test_srv->turn_stat.rx_allocate_cnt++; + + /* Skip if we're not responding to Allocate request */ + if (!test_srv->turn_respond_allocate) + return PJ_TRUE; + + /* Check if we have too many clients */ + if (test_srv->turn_alloc_cnt == MAX_TURN_ALLOC) { + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_INSUFFICIENT_CAPACITY, NULL, &resp); + goto send_pkt; + } + + /* Get USERNAME attribute */ + uname = (pj_stun_username_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_USERNAME, 0); + + /* Reject if it doesn't have MESSAGE-INTEGRITY or USERNAME attributes or + * the user is incorrect + */ + if (pj_stun_msg_find_attr(req, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0) == NULL || + uname==NULL || pj_stricmp2(&uname->value, TURN_USERNAME) != 0) + { + pj_str_t tmp; + + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_UNAUTHORIZED, NULL, &resp); + pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_REALM, &test_srv->domain); + pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, TURN_NONCE)); + goto send_pkt; + } + + pj_bzero(&alloc_sock_cb, sizeof(alloc_sock_cb)); + alloc_sock_cb.on_data_recvfrom = &alloc_on_data_recvfrom; + + /* Create allocation */ + alloc = &test_srv->turn_alloc[test_srv->turn_alloc_cnt]; + alloc->perm_cnt = 0; + alloc->test_srv = test_srv; + pj_memcpy(&alloc->client_addr, src_addr, addr_len); + pj_ioqueue_op_key_init(&alloc->send_key, sizeof(alloc->send_key)); + + alloc->pool = pj_pool_create(test_srv->stun_cfg->pf, "alloc", 512, 512, NULL); + + /* Create relay socket */ + pj_sockaddr_in_init(&alloc->alloc_addr.ipv4, NULL, 0); + pj_gethostip(pj_AF_INET(), &alloc->alloc_addr); + + status = pj_activesock_create_udp(alloc->pool, &alloc->alloc_addr, NULL, + test_srv->stun_cfg->ioqueue, + &alloc_sock_cb, alloc, + &alloc->sock, &alloc->alloc_addr); + if (status != PJ_SUCCESS) { + pj_pool_release(alloc->pool); + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp); + goto send_pkt; + } + //pj_sockaddr_set_str_addr(pj_AF_INET(), &alloc->alloc_addr, &ip_addr); + + pj_activesock_set_user_data(alloc->sock, alloc); + + status = pj_activesock_start_recvfrom(alloc->sock, alloc->pool, 1500, 0); + if (status != PJ_SUCCESS) { + pj_activesock_close(alloc->sock); + pj_pool_release(alloc->pool); + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp); + goto send_pkt; + } + + /* Create Data indication */ + status = pj_stun_msg_create(alloc->pool, PJ_STUN_DATA_INDICATION, + PJ_STUN_MAGIC, NULL, &alloc->data_ind); + if (status != PJ_SUCCESS) { + pj_activesock_close(alloc->sock); + pj_pool_release(alloc->pool); + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp); + goto send_pkt; + } + pj_stun_msg_add_sockaddr_attr(alloc->pool, alloc->data_ind, + PJ_STUN_ATTR_PEER_ADDR, PJ_TRUE, + &alloc->alloc_addr, + pj_sockaddr_get_len(&alloc->alloc_addr)); + pj_stun_msg_add_binary_attr(alloc->pool, alloc->data_ind, + PJ_STUN_ATTR_DATA, (pj_uint8_t*)"", 1); + + /* Create response */ + resp = create_success_response(test_srv, alloc, req, pool, 600, &auth_key); + if (resp == NULL) { + pj_activesock_close(alloc->sock); + pj_pool_release(alloc->pool); + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_SERVER_ERROR, NULL, &resp); + goto send_pkt; + } + + ++test_srv->turn_alloc_cnt; + + } else { + alloc = &test_srv->turn_alloc[i]; + + if (req->hdr.type == PJ_STUN_ALLOCATE_REQUEST) { + + test_srv->turn_stat.rx_allocate_cnt++; + + /* Skip if we're not responding to Allocate request */ + if (!test_srv->turn_respond_allocate) + return PJ_TRUE; + + resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key); + + } else if (req->hdr.type == PJ_STUN_REFRESH_REQUEST) { + pj_stun_lifetime_attr *lf_attr; + + test_srv->turn_stat.rx_refresh_cnt++; + + /* Skip if we're not responding to Refresh request */ + if (!test_srv->turn_respond_refresh) + return PJ_TRUE; + + lf_attr = (pj_stun_lifetime_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_LIFETIME, 0); + if (lf_attr && lf_attr->value != 0) { + resp = create_success_response(test_srv, alloc, req, pool, 600, &auth_key); + pj_array_erase(test_srv->turn_alloc, sizeof(test_srv->turn_alloc[0]), + test_srv->turn_alloc_cnt, i); + --test_srv->turn_alloc_cnt; + } else + resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key); + } else if (req->hdr.type == PJ_STUN_SEND_INDICATION) { + pj_stun_peer_addr_attr *pa; + pj_stun_data_attr *da; + + test_srv->turn_stat.rx_send_ind_cnt++; + + pa = (pj_stun_peer_addr_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_PEER_ADDR, 0); + da = (pj_stun_data_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_DATA, 0); + if (pa && da) { + unsigned j; + char peer_info[PJ_INET6_ADDRSTRLEN]; + pj_ssize_t sent; + + pj_sockaddr_print(&pa->sockaddr, peer_info, sizeof(peer_info), 3); + + for (j=0; jperm_cnt; ++j) { + if (pj_sockaddr_cmp(&alloc->perm[j], &pa->sockaddr)==0) + break; + } + + if (j==alloc->perm_cnt && alloc->perm_cnt < MAX_TURN_PERM) { + pj_sockaddr_cp(&alloc->perm[alloc->perm_cnt], &pa->sockaddr); + ++alloc->perm_cnt; + + PJ_LOG(5,("", "Permission %s added to client %s, perm_cnt=%d", + peer_info, client_info, alloc->perm_cnt)); + } + + PJ_LOG(5,(THIS_FILE, "Relaying %d bytes data from client %s to peer %s, " + "perm_cnt=%d", + da->length, client_info, peer_info, alloc->perm_cnt)); + + sent = da->length; + pj_activesock_sendto(alloc->sock, &alloc->send_key, + da->data, &sent, 0, + &pa->sockaddr, + pj_sockaddr_get_len(&pa->sockaddr)); + } else { + PJ_LOG(1,(THIS_FILE, "Invalid Send Indication from %s", client_info)); + } + } else if (PJ_STUN_IS_REQUEST(req->hdr.type)) { + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST, NULL, &resp); + } + } + + +send_pkt: + if (resp) { + status = pj_stun_msg_encode(resp, (pj_uint8_t*)data, MAX_STUN_PKT, + 0, &auth_key, &size); + if (status != PJ_SUCCESS) + goto on_return; + + len = size; + status = pj_activesock_sendto(asock, &test_srv->send_key, data, &len, + 0, src_addr, addr_len); + } + +on_return: + pj_pool_release(pool); + return PJ_TRUE; +} + +/* On received data from peer */ +static pj_bool_t alloc_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + turn_allocation *alloc; + pj_stun_peer_addr_attr *pa; + pj_stun_data_attr *da; + char peer_info[PJ_INET6_ADDRSTRLEN+10]; + char client_info[PJ_INET6_ADDRSTRLEN+10]; + pj_uint8_t buffer[1500]; + pj_ssize_t sent; + unsigned i; + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + alloc = (turn_allocation*) pj_activesock_get_user_data(asock); + + pj_sockaddr_print(&alloc->client_addr, client_info, sizeof(client_info), 3); + pj_sockaddr_print(src_addr, peer_info, sizeof(peer_info), 3); + + /* Check that this peer has a permission */ + for (i=0; iperm_cnt; ++i) { + if (pj_sockaddr_get_len(&alloc->perm[i]) == (unsigned)addr_len && + pj_memcmp(pj_sockaddr_get_addr(&alloc->perm[i]), + pj_sockaddr_get_addr(src_addr), + addr_len) == 0) + { + break; + } + } + if (i==alloc->perm_cnt) { + PJ_LOG(5,("", "Client %s received %d bytes unauthorized data from peer %s", + client_info, size, peer_info)); + if (alloc->perm_cnt == 0) + PJ_LOG(5,("", "Client %s has no permission", client_info)); + return PJ_TRUE; + } + + /* Format a Data indication */ + pa = (pj_stun_peer_addr_attr*) + pj_stun_msg_find_attr(alloc->data_ind, PJ_STUN_ATTR_PEER_ADDR, 0); + da = (pj_stun_data_attr*) + pj_stun_msg_find_attr(alloc->data_ind, PJ_STUN_ATTR_DATA, 0); + pj_assert(pa && da); + + pj_sockaddr_cp(&pa->sockaddr, src_addr); + da->data = data; + da->length = size; + + /* Encode Data indication */ + status = pj_stun_msg_encode(alloc->data_ind, buffer, sizeof(buffer), 0, + NULL, &size); + if (status != PJ_SUCCESS) + return PJ_TRUE; + + /* Send */ + sent = size; + PJ_LOG(5,("", "Forwarding %d bytes data from peer %s to client %s", + sent, peer_info, client_info)); + + pj_activesock_sendto(alloc->test_srv->turn_sock, &alloc->send_key, buffer, + &sent, 0, &alloc->client_addr, + pj_sockaddr_get_len(&alloc->client_addr)); + + return PJ_TRUE; +} + diff --git a/pjnath/src/pjnath-test/server.h b/pjnath/src/pjnath-test/server.h new file mode 100644 index 00000000..f7955275 --- /dev/null +++ b/pjnath/src/pjnath-test/server.h @@ -0,0 +1,108 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TEST_SERVER_H__ +#define __PJNATH_TEST_SERVER_H__ + +#include +#include +#include + +#define DNS_SERVER_PORT 55533 +#define STUN_SERVER_PORT 33478 +#define TURN_SERVER_PORT 33479 + +#define TURN_USERNAME "auser" +#define TURN_PASSWD "apass" + +#define MAX_TURN_ALLOC 16 +#define MAX_TURN_PERM 16 + +enum test_server_flags +{ + CREATE_DNS_SERVER = (1 << 0), + CREATE_A_RECORD_FOR_DOMAIN = (1 << 1), + + CREATE_STUN_SERVER = (1 << 5), + CREATE_STUN_SERVER_DNS_SRV = (1 << 6), + + CREATE_TURN_SERVER = (1 << 10), + CREATE_TURN_SERVER_DNS_SRV = (1 << 11), + +}; + +typedef struct test_server test_server; + +/* TURN allocation */ +typedef struct turn_allocation +{ + test_server *test_srv; + pj_pool_t *pool; + pj_activesock_t *sock; + pj_ioqueue_op_key_t send_key; + pj_sockaddr client_addr; + pj_sockaddr alloc_addr; + unsigned perm_cnt; + pj_sockaddr perm[MAX_TURN_PERM]; + pj_stun_msg *data_ind; +} turn_allocation; + +/* + * Server installation for testing. + * This comprises of DNS server, STUN server, and TURN server. + */ +struct test_server +{ + pj_pool_t *pool; + pj_uint32_t flags; + pj_stun_config *stun_cfg; + pj_ioqueue_op_key_t send_key; + + pj_dns_server *dns_server; + + pj_activesock_t *stun_sock; + + pj_activesock_t *turn_sock; + unsigned turn_alloc_cnt; + turn_allocation turn_alloc[MAX_TURN_ALLOC]; + pj_bool_t turn_respond_allocate; + pj_bool_t turn_respond_refresh; + + struct turn_stat { + unsigned rx_allocate_cnt; + unsigned rx_refresh_cnt; + unsigned rx_send_ind_cnt; + } turn_stat; + + pj_str_t domain; + pj_str_t username; + pj_str_t passwd; + +}; + + +pj_status_t create_test_server(pj_stun_config *stun_cfg, + pj_uint32_t flags, + const char *domain, + test_server **p_test_srv); +void destroy_test_server(test_server *test_srv); +void test_server_poll_events(test_server *test_srv); + + +#endif /* __PJNATH_TEST_SERVER_H__ */ + diff --git a/pjnath/src/pjnath-test/sess_auth.c b/pjnath/src/pjnath-test/sess_auth.c index 0505fbe1..7aee423b 100644 --- a/pjnath/src/pjnath-test/sess_auth.c +++ b/pjnath/src/pjnath-test/sess_auth.c @@ -1098,6 +1098,11 @@ int sess_auth_test(void) /* 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 @@ -1116,6 +1121,7 @@ int sess_auth_test(void) if (rc != 0) { goto done; } +#endif /* Invalid HMAC */ diff --git a/pjnath/src/pjnath-test/stun_sock_test.c b/pjnath/src/pjnath-test/stun_sock_test.c new file mode 100644 index 00000000..2c4462d7 --- /dev/null +++ b/pjnath/src/pjnath-test/stun_sock_test.c @@ -0,0 +1,844 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" + +#define THIS_FILE "stun_sock_test.c" + +enum { + RESPOND_STUN = 1, + WITH_MAPPED = 2, + WITH_XOR_MAPPED = 4, + + ECHO = 8 +}; + +/* + * Simple STUN server + */ +struct stun_srv +{ + pj_activesock_t *asock; + unsigned flag; + pj_sockaddr addr; + unsigned rx_cnt; + pj_ioqueue_op_key_t send_key; + pj_str_t ip_to_send; + pj_uint16_t port_to_send; +}; + +static pj_bool_t srv_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + struct stun_srv *srv; + pj_ssize_t sent; + + srv = pj_activesock_get_user_data(asock); + + /* Ignore error */ + if (status != PJ_SUCCESS) + return PJ_TRUE; + + ++srv->rx_cnt; + + /* Ignore if we're not responding */ + if (srv->flag & RESPOND_STUN) { + pj_pool_t *pool; + pj_stun_msg *req_msg, *res_msg; + + pool = pj_pool_create(mem, "stunsrv", 512, 512, NULL); + + /* Parse request */ + status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size, + PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET, + &req_msg, NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" pj_stun_msg_decode()", status); + pj_pool_release(pool); + return PJ_TRUE; + } + + /* Create response */ + status = pj_stun_msg_create(pool, PJ_STUN_BINDING_RESPONSE, PJ_STUN_MAGIC, + req_msg->hdr.tsx_id, &res_msg); + if (status != PJ_SUCCESS) { + app_perror(" pj_stun_msg_create()", status); + pj_pool_release(pool); + return PJ_TRUE; + } + + /* Add MAPPED-ADDRESS or XOR-MAPPED-ADDRESS (or don't add) */ + if (srv->flag & WITH_MAPPED) { + pj_sockaddr_in addr; + + pj_sockaddr_in_init(&addr, &srv->ip_to_send, srv->port_to_send); + pj_stun_msg_add_sockaddr_attr(pool, res_msg, PJ_STUN_ATTR_MAPPED_ADDR, + PJ_FALSE, &addr, sizeof(addr)); + } else if (srv->flag & WITH_XOR_MAPPED) { + pj_sockaddr_in addr; + + pj_sockaddr_in_init(&addr, &srv->ip_to_send, srv->port_to_send); + pj_stun_msg_add_sockaddr_attr(pool, res_msg, + PJ_STUN_ATTR_XOR_MAPPED_ADDR, + PJ_TRUE, &addr, sizeof(addr)); + } + + /* Encode */ + status = pj_stun_msg_encode(res_msg, (pj_uint8_t*)data, 100, 0, + NULL, &size); + if (status != PJ_SUCCESS) { + app_perror(" pj_stun_msg_encode()", status); + pj_pool_release(pool); + return PJ_TRUE; + } + + /* Send back */ + sent = size; + pj_activesock_sendto(asock, &srv->send_key, data, &sent, 0, + src_addr, addr_len); + + pj_pool_release(pool); + + } else if (srv->flag & ECHO) { + /* Send back */ + sent = size; + pj_activesock_sendto(asock, &srv->send_key, data, &sent, 0, + src_addr, addr_len); + + } + + return PJ_TRUE; +} + +static pj_status_t create_server(pj_pool_t *pool, + pj_ioqueue_t *ioqueue, + unsigned flag, + struct stun_srv **p_srv) +{ + struct stun_srv *srv; + pj_activesock_cb activesock_cb; + pj_status_t status; + + srv = PJ_POOL_ZALLOC_T(pool, struct stun_srv); + srv->flag = flag; + srv->ip_to_send = pj_str("1.1.1.1"); + srv->port_to_send = 1000; + + status = pj_sockaddr_in_init(&srv->addr.ipv4, NULL, 0); + if (status != PJ_SUCCESS) + return status; + + pj_bzero(&activesock_cb, sizeof(activesock_cb)); + activesock_cb.on_data_recvfrom = &srv_on_data_recvfrom; + status = pj_activesock_create_udp(pool, &srv->addr, NULL, ioqueue, + &activesock_cb, srv, &srv->asock, + &srv->addr); + if (status != PJ_SUCCESS) + return status; + + pj_ioqueue_op_key_init(&srv->send_key, sizeof(srv->send_key)); + + status = pj_activesock_start_recvfrom(srv->asock, pool, 512, 0); + if (status != PJ_SUCCESS) { + pj_activesock_close(srv->asock); + return status; + } + + *p_srv = srv; + return PJ_SUCCESS; +} + +static void destroy_server(struct stun_srv *srv) +{ + pj_activesock_close(srv->asock); +} + + +struct stun_client +{ + pj_pool_t *pool; + pj_stun_sock *sock; + + pj_ioqueue_op_key_t send_key; + pj_bool_t destroy_on_err; + + unsigned on_status_cnt; + pj_stun_sock_op last_op; + pj_status_t last_status; + + unsigned on_rx_data_cnt; +}; + +static pj_bool_t stun_sock_on_status(pj_stun_sock *stun_sock, + pj_stun_sock_op op, + pj_status_t status) +{ + struct stun_client *client; + + client = pj_stun_sock_get_user_data(stun_sock); + client->on_status_cnt++; + client->last_op = op; + client->last_status = status; + + if (status != PJ_SUCCESS && client->destroy_on_err) { + pj_stun_sock_destroy(client->sock); + client->sock = NULL; + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static pj_bool_t stun_sock_on_rx_data(pj_stun_sock *stun_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *src_addr, + unsigned addr_len) +{ + struct stun_client *client; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(addr_len); + + client = pj_stun_sock_get_user_data(stun_sock); + client->on_rx_data_cnt++; + + return PJ_TRUE; +} + +static pj_status_t create_client(pj_stun_config *cfg, + struct stun_client **p_client, + pj_bool_t destroy_on_err) +{ + pj_pool_t *pool; + struct stun_client *client; + pj_stun_sock_cfg sock_cfg; + pj_stun_sock_cb cb; + pj_status_t status; + + pool = pj_pool_create(mem, "test", 512, 512, NULL); + client = PJ_POOL_ZALLOC_T(pool, struct stun_client); + client->pool = pool; + + pj_stun_sock_cfg_default(&sock_cfg); + + pj_bzero(&cb, sizeof(cb)); + cb.on_status = &stun_sock_on_status; + cb.on_rx_data = &stun_sock_on_rx_data; + status = pj_stun_sock_create(cfg, NULL, pj_AF_INET(), &cb, + &sock_cfg, client, &client->sock); + if (status != PJ_SUCCESS) { + app_perror(" pj_stun_sock_create()", status); + pj_pool_release(pool); + return status; + } + + pj_stun_sock_set_user_data(client->sock, client); + + pj_ioqueue_op_key_init(&client->send_key, sizeof(client->send_key)); + + client->destroy_on_err = destroy_on_err; + + *p_client = client; + + return PJ_SUCCESS; +} + + +static void destroy_client(struct stun_client *client) +{ + if (client->sock) { + pj_stun_sock_destroy(client->sock); + client->sock = NULL; + } + pj_pool_release(client->pool); +} + +static void handle_events(pj_stun_config *cfg, unsigned msec_delay) +{ + pj_time_val delay; + + pj_timer_heap_poll(cfg->timer_heap, NULL); + + delay.sec = 0; + delay.msec = msec_delay; + pj_time_val_normalize(&delay); + + pj_ioqueue_poll(cfg->ioqueue, &delay); +} + +/* + * Timeout test: scenario when no response is received from server + */ +static int timeout_test(pj_stun_config *cfg, pj_bool_t destroy_on_err) +{ + struct stun_srv *srv; + struct stun_client *client; + pj_str_t srv_addr; + pj_time_val timeout, t; + int ret = 0; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " timeout test [%d]", destroy_on_err)); + + status = create_client(cfg, &client, destroy_on_err); + if (status != PJ_SUCCESS) + return -10; + + status = create_server(client->pool, cfg->ioqueue, 0, &srv); + if (status != PJ_SUCCESS) { + destroy_client(client); + return -20; + } + + srv_addr = pj_str("127.0.0.1"); + status = pj_stun_sock_start(client->sock, &srv_addr, + pj_ntohs(srv->addr.ipv4.sin_port), NULL); + if (status != PJ_SUCCESS) { + destroy_server(srv); + destroy_client(client); + return -30; + } + + /* Wait until on_status() callback is called with the failure */ + pj_gettimeofday(&timeout); + timeout.sec += 60; + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); + + /* Check that callback with correct operation is called */ + if (client->last_op != PJ_STUN_SOCK_BINDING_OP) { + PJ_LOG(3,(THIS_FILE, " error: expecting Binding operation status")); + ret = -40; + goto on_return; + } + /* .. and with the correct status */ + if (client->last_status != PJNATH_ESTUNTIMEDOUT) { + PJ_LOG(3,(THIS_FILE, " error: expecting PJNATH_ESTUNTIMEDOUT")); + ret = -50; + goto on_return; + } + /* Check that server received correct retransmissions */ + if (srv->rx_cnt != PJ_STUN_MAX_TRANSMIT_COUNT) { + PJ_LOG(3,(THIS_FILE, " error: expecting %d retransmissions, got %d", + PJ_STUN_MAX_TRANSMIT_COUNT, srv->rx_cnt)); + ret = -60; + goto on_return; + } + /* Check that client doesn't receive anything */ + if (client->on_rx_data_cnt != 0) { + PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); + ret = -70; + goto on_return; + } + +on_return: + destroy_server(srv); + destroy_client(client); + return ret; +} + + +/* + * Invalid response scenario: when server returns no MAPPED-ADDRESS or + * XOR-MAPPED-ADDRESS attribute. + */ +static int missing_attr_test(pj_stun_config *cfg, pj_bool_t destroy_on_err) +{ + struct stun_srv *srv; + struct stun_client *client; + pj_str_t srv_addr; + pj_time_val timeout, t; + int ret = 0; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " missing attribute test [%d]", destroy_on_err)); + + status = create_client(cfg, &client, destroy_on_err); + if (status != PJ_SUCCESS) + return -110; + + status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN, &srv); + if (status != PJ_SUCCESS) { + destroy_client(client); + return -120; + } + + srv_addr = pj_str("127.0.0.1"); + status = pj_stun_sock_start(client->sock, &srv_addr, + pj_ntohs(srv->addr.ipv4.sin_port), NULL); + if (status != PJ_SUCCESS) { + destroy_server(srv); + destroy_client(client); + return -130; + } + + /* Wait until on_status() callback is called with the failure */ + pj_gettimeofday(&timeout); + timeout.sec += 60; + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); + + /* Check that callback with correct operation is called */ + if (client->last_op != PJ_STUN_SOCK_BINDING_OP) { + PJ_LOG(3,(THIS_FILE, " error: expecting Binding operation status")); + ret = -140; + goto on_return; + } + if (client->last_status != PJNATH_ESTUNNOMAPPEDADDR) { + PJ_LOG(3,(THIS_FILE, " error: expecting PJNATH_ESTUNNOMAPPEDADDR")); + ret = -150; + goto on_return; + } + /* Check that client doesn't receive anything */ + if (client->on_rx_data_cnt != 0) { + PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); + ret = -170; + goto on_return; + } + +on_return: + destroy_server(srv); + destroy_client(client); + return ret; +} + +/* + * Keep-alive test. + */ +static int keep_alive_test(pj_stun_config *cfg) +{ + struct stun_srv *srv; + struct stun_client *client; + pj_sockaddr_in mapped_addr; + pj_stun_sock_info info; + pj_str_t srv_addr; + pj_time_val timeout, t; + int ret = 0; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " normal operation")); + + status = create_client(cfg, &client, PJ_TRUE); + if (status != PJ_SUCCESS) + return -310; + + status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN|WITH_XOR_MAPPED, &srv); + if (status != PJ_SUCCESS) { + destroy_client(client); + return -320; + } + + /* + * Part 1: initial Binding resolution. + */ + PJ_LOG(3,(THIS_FILE, " initial Binding request")); + srv_addr = pj_str("127.0.0.1"); + status = pj_stun_sock_start(client->sock, &srv_addr, + pj_ntohs(srv->addr.ipv4.sin_port), NULL); + if (status != PJ_SUCCESS) { + destroy_server(srv); + destroy_client(client); + return -330; + } + + /* Wait until on_status() callback is called with success status */ + pj_gettimeofday(&timeout); + timeout.sec += 60; + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); + + /* Check that callback with correct operation is called */ + if (client->last_op != PJ_STUN_SOCK_BINDING_OP) { + PJ_LOG(3,(THIS_FILE, " error: expecting Binding operation status")); + ret = -340; + goto on_return; + } + if (client->last_status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error: expecting PJ_SUCCESS status")); + ret = -350; + goto on_return; + } + /* Check that client doesn't receive anything */ + if (client->on_rx_data_cnt != 0) { + PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); + ret = -370; + goto on_return; + } + + /* Get info */ + pj_bzero(&info, sizeof(info)); + pj_stun_sock_get_info(client->sock, &info); + + /* Check that we have server address */ + if (!pj_sockaddr_has_addr(&info.srv_addr)) { + PJ_LOG(3,(THIS_FILE, " error: missing server address")); + ret = -380; + goto on_return; + } + /* .. and bound address port must not be zero */ + if (pj_sockaddr_get_port(&info.bound_addr)==0) { + PJ_LOG(3,(THIS_FILE, " error: bound address is zero")); + ret = -381; + goto on_return; + } + /* .. and mapped address */ + if (!pj_sockaddr_has_addr(&info.mapped_addr)) { + PJ_LOG(3,(THIS_FILE, " error: missing mapped address")); + ret = -382; + goto on_return; + } + /* verify the mapped address */ + pj_sockaddr_in_init(&mapped_addr, &srv->ip_to_send, srv->port_to_send); + if (pj_sockaddr_cmp(&info.mapped_addr, &mapped_addr) != 0) { + PJ_LOG(3,(THIS_FILE, " error: mapped address mismatched")); + ret = -383; + goto on_return; + } + + /* .. and at least one alias */ + if (info.alias_cnt == 0) { + PJ_LOG(3,(THIS_FILE, " error: must have at least one alias")); + ret = -384; + goto on_return; + } + if (!pj_sockaddr_has_addr(&info.aliases[0])) { + PJ_LOG(3,(THIS_FILE, " error: missing alias")); + ret = -386; + goto on_return; + } + + + /* + * Part 2: sending and receiving data + */ + PJ_LOG(3,(THIS_FILE, " sending/receiving data")); + + /* Change server operation mode to echo back data */ + srv->flag = ECHO; + + /* Reset server */ + srv->rx_cnt = 0; + + /* Client sending data to echo server */ + { + char txt[100]; + PJ_LOG(3,(THIS_FILE, " sending to %s", pj_sockaddr_print(&info.srv_addr, txt, sizeof(txt), 3))); + } + status = pj_stun_sock_sendto(client->sock, NULL, &ret, sizeof(ret), + 0, &info.srv_addr, + pj_sockaddr_get_len(&info.srv_addr)); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + app_perror(" error: server sending data", status); + ret = -390; + goto on_return; + } + + /* Wait for a short period until client receives data. We can't wait for + * too long otherwise the keep-alive will kick in. + */ + pj_gettimeofday(&timeout); + timeout.sec += 1; + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (client->on_rx_data_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); + + /* Check that data is received in server */ + if (srv->rx_cnt == 0) { + PJ_LOG(3,(THIS_FILE, " error: server didn't receive data")); + ret = -395; + goto on_return; + } + + /* Check that status is still OK */ + if (client->last_status != PJ_SUCCESS) { + app_perror(" error: client has failed", client->last_status); + ret = -400; + goto on_return; + } + /* Check that data has been received */ + if (client->on_rx_data_cnt == 0) { + PJ_LOG(3,(THIS_FILE, " error: client doesn't receive data")); + ret = -410; + goto on_return; + } + + /* + * Part 3: Successful keep-alive, + */ + PJ_LOG(3,(THIS_FILE, " successful keep-alive scenario")); + + /* Change server operation mode to normal mode */ + srv->flag = RESPOND_STUN | WITH_XOR_MAPPED; + + /* Reset server */ + srv->rx_cnt = 0; + + /* Reset client */ + client->on_status_cnt = 0; + client->last_status = PJ_SUCCESS; + client->on_rx_data_cnt = 0; + + /* Wait for keep-alive duration to see if client actually sends the + * keep-alive. + */ + pj_gettimeofday(&timeout); + timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + 1); + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (PJ_TIME_VAL_LT(t, timeout)); + + /* Check that server receives some packets */ + if (srv->rx_cnt == 0) { + PJ_LOG(3, (THIS_FILE, " error: no keep-alive was received")); + ret = -420; + goto on_return; + } + /* Check that client status is still okay and on_status() callback is NOT + * called + */ + if (client->on_status_cnt != 0) { + PJ_LOG(3, (THIS_FILE, " error: on_status() must not be called on successful" + "keep-alive when mapped-address does not change")); + ret = -430; + goto on_return; + } + /* Check that client doesn't receive anything */ + if (client->on_rx_data_cnt != 0) { + PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); + ret = -440; + goto on_return; + } + + + /* + * Part 4: Successful keep-alive with IP address change + */ + PJ_LOG(3,(THIS_FILE, " mapped IP address change")); + + /* Change server operation mode to normal mode */ + srv->flag = RESPOND_STUN | WITH_XOR_MAPPED; + + /* Change mapped address in the response */ + srv->ip_to_send = pj_str("2.2.2.2"); + srv->port_to_send++; + + /* Reset server */ + srv->rx_cnt = 0; + + /* Reset client */ + client->on_status_cnt = 0; + client->last_status = PJ_SUCCESS; + client->on_rx_data_cnt = 0; + + /* Wait for keep-alive duration to see if client actually sends the + * keep-alive. + */ + pj_gettimeofday(&timeout); + timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + 1); + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (PJ_TIME_VAL_LT(t, timeout)); + + /* Check that server receives some packets */ + if (srv->rx_cnt == 0) { + PJ_LOG(3, (THIS_FILE, " error: no keep-alive was received")); + ret = -450; + goto on_return; + } + /* Check that on_status() callback is called (because mapped address + * has changed) + */ + if (client->on_status_cnt != 1) { + PJ_LOG(3, (THIS_FILE, " error: on_status() was not called")); + ret = -460; + goto on_return; + } + /* Check that callback was called with correct operation */ + if (client->last_op != PJ_STUN_SOCK_KEEP_ALIVE_OP) { + PJ_LOG(3,(THIS_FILE, " error: expecting keep-alive operation status")); + ret = -470; + goto on_return; + } + /* Check that last status is still success */ + if (client->last_status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, " error: expecting successful status")); + ret = -480; + goto on_return; + } + /* Check that client doesn't receive anything */ + if (client->on_rx_data_cnt != 0) { + PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); + ret = -490; + goto on_return; + } + + /* Get info */ + pj_bzero(&info, sizeof(info)); + pj_stun_sock_get_info(client->sock, &info); + + /* Check that we have server address */ + if (!pj_sockaddr_has_addr(&info.srv_addr)) { + PJ_LOG(3,(THIS_FILE, " error: missing server address")); + ret = -500; + goto on_return; + } + /* .. and mapped address */ + if (!pj_sockaddr_has_addr(&info.mapped_addr)) { + PJ_LOG(3,(THIS_FILE, " error: missing mapped address")); + ret = -510; + goto on_return; + } + /* verify the mapped address */ + pj_sockaddr_in_init(&mapped_addr, &srv->ip_to_send, srv->port_to_send); + if (pj_sockaddr_cmp(&info.mapped_addr, &mapped_addr) != 0) { + PJ_LOG(3,(THIS_FILE, " error: mapped address mismatched")); + ret = -520; + goto on_return; + } + + /* .. and at least one alias */ + if (info.alias_cnt == 0) { + PJ_LOG(3,(THIS_FILE, " error: must have at least one alias")); + ret = -530; + goto on_return; + } + if (!pj_sockaddr_has_addr(&info.aliases[0])) { + PJ_LOG(3,(THIS_FILE, " error: missing alias")); + ret = -540; + goto on_return; + } + + + /* + * Part 5: Failed keep-alive + */ + PJ_LOG(3,(THIS_FILE, " failed keep-alive scenario")); + + /* Change server operation mode to respond without attribute */ + srv->flag = RESPOND_STUN; + + /* Reset server */ + srv->rx_cnt = 0; + + /* Reset client */ + client->on_status_cnt = 0; + client->last_status = PJ_SUCCESS; + client->on_rx_data_cnt = 0; + + /* Wait until on_status() is called with failure. */ + pj_gettimeofday(&timeout); + timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + PJ_STUN_TIMEOUT_VALUE + 5); + do { + handle_events(cfg, 100); + pj_gettimeofday(&t); + } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout)); + + /* Check that callback with correct operation is called */ + if (client->last_op != PJ_STUN_SOCK_KEEP_ALIVE_OP) { + PJ_LOG(3,(THIS_FILE, " error: expecting keep-alive operation status")); + ret = -600; + goto on_return; + } + if (client->last_status == PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error: expecting failed keep-alive")); + ret = -610; + goto on_return; + } + /* Check that client doesn't receive anything */ + if (client->on_rx_data_cnt != 0) { + PJ_LOG(3,(THIS_FILE, " error: client shouldn't have received anything")); + ret = -620; + goto on_return; + } + + +on_return: + destroy_server(srv); + destroy_client(client); + return ret; +} + + +#define DO_TEST(expr) \ + capture_pjlib_state(&stun_cfg, &pjlib_state); \ + ret = expr; \ + if (ret != 0) goto on_return; \ + ret = check_pjlib_state(&stun_cfg, &pjlib_state); \ + if (ret != 0) goto on_return; + + +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; +} + + diff --git a/pjnath/src/pjnath-test/test.c b/pjnath/src/pjnath-test/test.c index 312c1ea2..2abc1696 100644 --- a/pjnath/src/pjnath-test/test.c +++ b/pjnath/src/pjnath-test/test.c @@ -29,6 +29,109 @@ void app_perror(const char *msg, pj_status_t rc) PJ_LOG(1,("test", "%s: [pj_status_t=%d] %s", msg, rc, errbuf)); } +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; +} + +void destroy_stun_config(pj_stun_config *stun_cfg) +{ + if (stun_cfg->timer_heap) { + pj_timer_heap_destroy(stun_cfg->timer_heap); + stun_cfg->timer_heap = NULL; + } + if (stun_cfg->ioqueue) { + pj_ioqueue_destroy(stun_cfg->ioqueue); + stun_cfg->ioqueue = NULL; + } +} + +void poll_events(pj_stun_config *stun_cfg, unsigned msec, + pj_bool_t first_event_only) +{ + pj_time_val stop_time; + int count = 0; + + pj_gettimeofday(&stop_time); + stop_time.msec += msec; + pj_time_val_normalize(&stop_time); + + /* Process all events for the specified duration. */ + for (;;) { + pj_time_val timeout = {0, 1}, now; + int c; + + c = pj_timer_heap_poll( stun_cfg->timer_heap, NULL ); + if (c > 0) + count += c; + + //timeout.sec = timeout.msec = 0; + c = pj_ioqueue_poll( stun_cfg->ioqueue, &timeout); + if (c > 0) + count += c; + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, stop_time)) + break; + + if (first_event_only && count >= 0) + break; + } +} + +void capture_pjlib_state(pj_stun_config *cfg, struct pjlib_state *st) +{ + pj_caching_pool *cp; + + st->timer_cnt = pj_timer_heap_count(cfg->timer_heap); + + cp = (pj_caching_pool*)mem; + st->pool_used_cnt = cp->used_count; +} + +int check_pjlib_state(pj_stun_config *cfg, + const struct pjlib_state *initial_st) +{ + struct pjlib_state current_state; + int rc = 0; + + capture_pjlib_state(cfg, ¤t_state); + + if (current_state.timer_cnt > initial_st->timer_cnt) { + PJ_LOG(3,("", " error: possibly leaking timer")); + rc |= ERR_TIMER_LEAK; + } + + if (current_state.pool_used_cnt > initial_st->pool_used_cnt) { + PJ_LOG(3,("", " error: possibly leaking memory")); + PJ_LOG(3,("", " dumping memory pool:")); + pj_pool_factory_dump(mem, PJ_TRUE); + rc |= ERR_MEMORY_LEAK; + } + + return rc; +} + + #define DO_TEST(test) do { \ PJ_LOG(3, ("test", "Running %s...", #test)); \ rc = test; \ @@ -64,6 +167,7 @@ static int test_inner(void) pj_dump_config(); pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy, 0 ); + pjlib_util_init(); pjnath_init(); #if INCLUDE_STUN_TEST @@ -75,6 +179,14 @@ static int test_inner(void) DO_TEST(ice_test()); #endif +#if INCLUDE_STUN_SOCK_TEST + DO_TEST(stun_sock_test()); +#endif + +#if INCLUDE_TURN_SOCK_TEST + DO_TEST(turn_sock_test()); +#endif + on_return: return rc; } diff --git a/pjnath/src/pjnath-test/test.h b/pjnath/src/pjnath-test/test.h index 713f3da8..a0187310 100644 --- a/pjnath/src/pjnath-test/test.h +++ b/pjnath/src/pjnath-test/test.h @@ -22,12 +22,41 @@ #define INCLUDE_STUN_TEST 1 #define INCLUDE_ICE_TEST 1 +#define INCLUDE_STUN_SOCK_TEST 1 +#define INCLUDE_TURN_SOCK_TEST 1 int stun_test(void); int sess_auth_test(void); +int stun_sock_test(void); +int turn_sock_test(void); int ice_test(void); int test_main(void); extern void app_perror(const char *title, pj_status_t rc); extern pj_pool_factory *mem; +//////////////////////////////////// +/* + * Utilities + */ +pj_status_t create_stun_config(pj_pool_t *pool, pj_stun_config *stun_cfg); +void destroy_stun_config(pj_stun_config *stun_cfg); + +void poll_events(pj_stun_config *stun_cfg, unsigned msec, + pj_bool_t first_event_only); + +typedef struct pjlib_state +{ + unsigned timer_cnt; /* Number of timer entries */ + unsigned pool_used_cnt; /* Number of app pools */ +} pjlib_state; + + +void capture_pjlib_state(pj_stun_config *cfg, struct pjlib_state *st); +int check_pjlib_state(pj_stun_config *cfg, + const struct pjlib_state *initial_st); + + +#define ERR_MEMORY_LEAK 1 +#define ERR_TIMER_LEAK 2 + diff --git a/pjnath/src/pjnath-test/turn_sock_test.c b/pjnath/src/pjnath-test/turn_sock_test.c new file mode 100644 index 00000000..52d2242f --- /dev/null +++ b/pjnath/src/pjnath-test/turn_sock_test.c @@ -0,0 +1,515 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include "server.h" + +#define SRV_DOMAIN "pjsip.lab.domain" +#define KA_INTERVAL 50 + +struct test_result +{ + unsigned state_called; + unsigned rx_data_cnt; +}; + +struct test_session +{ + pj_pool_t *pool; + pj_stun_config *stun_cfg; + pj_turn_sock *turn_sock; + pj_dns_resolver *resolver; + test_server *test_srv; + + pj_bool_t destroy_called; + int destroy_on_state; + struct test_result result; +}; + +struct test_session_cfg +{ + struct { + pj_bool_t enable_dns_srv; + int destroy_on_state; + } client; + + struct { + pj_uint32_t flags; + pj_bool_t respond_allocate; + pj_bool_t respond_refresh; + } srv; +}; + +static void turn_on_rx_data(pj_turn_sock *turn_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_state(pj_turn_sock *turn_sock, + pj_turn_state_t old_state, + pj_turn_state_t new_state); + +static void destroy_session(struct test_session *sess) +{ + if (sess->resolver) { + pj_dns_resolver_destroy(sess->resolver, PJ_TRUE); + sess->resolver = NULL; + } + + if (sess->turn_sock) { + if (!sess->destroy_called) { + sess->destroy_called = PJ_TRUE; + pj_turn_sock_destroy(sess->turn_sock); + } + sess->turn_sock = NULL; + } + + if (sess->test_srv) { + destroy_test_server(sess->test_srv); + sess->test_srv = NULL; + } + + if (sess->pool) { + pj_pool_release(sess->pool); + } +} + + + +static int create_test_session(pj_stun_config *stun_cfg, + const struct test_session_cfg *cfg, + struct test_session **p_sess) +{ + struct test_session *sess; + pj_pool_t *pool; + pj_turn_sock_cb turn_sock_cb; + pj_turn_alloc_param alloc_param; + pj_stun_auth_cred cred; + pj_status_t status; + + /* Create client */ + pool = pj_pool_create(mem, "turnclient", 512, 512, NULL); + sess = PJ_POOL_ZALLOC_T(pool, struct test_session); + sess->pool = pool; + sess->stun_cfg = stun_cfg; + sess->destroy_on_state = cfg->client.destroy_on_state; + + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_rx_data = &turn_on_rx_data; + turn_sock_cb.on_state = &turn_on_state; + status = pj_turn_sock_create(sess->stun_cfg, pj_AF_INET(), PJ_TURN_TP_UDP, + &turn_sock_cb, 0, sess, &sess->turn_sock); + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -20; + } + + /* Create test server */ + status = create_test_server(sess->stun_cfg, cfg->srv.flags, + SRV_DOMAIN, &sess->test_srv); + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -30; + } + + sess->test_srv->turn_respond_allocate = cfg->srv.respond_allocate; + sess->test_srv->turn_respond_refresh = cfg->srv.respond_refresh; + + /* Create client resolver */ + status = pj_dns_resolver_create(mem, "resolver", 0, sess->stun_cfg->timer_heap, + sess->stun_cfg->ioqueue, &sess->resolver); + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -40; + + } else { + pj_str_t dns_srv = pj_str("127.0.0.1"); + pj_uint16_t dns_srv_port = (pj_uint16_t) DNS_SERVER_PORT; + status = pj_dns_resolver_set_ns(sess->resolver, 1, &dns_srv, &dns_srv_port); + + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -50; + } + } + + /* Init TURN credential */ + pj_bzero(&cred, sizeof(cred)); + cred.type = PJ_STUN_AUTH_CRED_STATIC; + cred.data.static_cred.realm = pj_str(SRV_DOMAIN); + cred.data.static_cred.username = pj_str(TURN_USERNAME); + cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; + cred.data.static_cred.data = pj_str(TURN_PASSWD); + + /* Init TURN allocate parameter */ + pj_turn_alloc_param_default(&alloc_param); + alloc_param.ka_interval = KA_INTERVAL; + + /* Start the client */ + if (cfg->client.enable_dns_srv) { + /* Use DNS SRV to resolve server, may fallback to DNS A */ + pj_str_t domain = pj_str(SRV_DOMAIN); + status = pj_turn_sock_alloc(sess->turn_sock, &domain, TURN_SERVER_PORT, + sess->resolver, &cred, &alloc_param); + + } else { + /* Explicitly specify server address */ + pj_str_t host = pj_str("127.0.0.1"); + status = pj_turn_sock_alloc(sess->turn_sock, &host, TURN_SERVER_PORT, + NULL, &cred, &alloc_param); + + } + + if (status != PJ_SUCCESS) { + if (cfg->client.destroy_on_state >= PJ_TURN_STATE_READY) { + destroy_session(sess); + return -70; + } + } + + *p_sess = sess; + return 0; +} + + +static void turn_on_rx_data(pj_turn_sock *turn_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + struct test_session *sess; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(peer_addr); + PJ_UNUSED_ARG(addr_len); + + sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock); + if (sess == NULL) + return; + + sess->result.rx_data_cnt++; +} + + +static void turn_on_state(pj_turn_sock *turn_sock, + pj_turn_state_t old_state, + pj_turn_state_t new_state) +{ + struct test_session *sess; + unsigned i, mask; + + PJ_UNUSED_ARG(old_state); + + sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock); + if (sess == NULL) + return; + + /* This state must not be called before */ + pj_assert((sess->result.state_called & (1< old_state); + + /* must not call any greater state before */ + mask = 0; + for (i=new_state+1; i<31; ++i) mask |= (1 << i); + + pj_assert((sess->result.state_called & mask) == 0); + + sess->result.state_called |= (1 << new_state); + + if (new_state >= sess->destroy_on_state && !sess->destroy_called) { + sess->destroy_called = PJ_TRUE; + pj_turn_sock_destroy(turn_sock); + } + + if (new_state >= PJ_TURN_STATE_DESTROYING) { + pj_turn_sock_set_user_data(sess->turn_sock, NULL); + sess->turn_sock = NULL; + } +} + + +///////////////////////////////////////////////////////////////////// + +static int state_progression_test(pj_stun_config *stun_cfg) +{ + struct test_session_cfg test_cfg = + { + { /* Client cfg */ + /* DNS SRV */ /* Destroy on state */ + PJ_TRUE, 0xFFFF + }, + { /* Server cfg */ + 0xFFFFFFFF, /* flags */ + PJ_TRUE, /* respond to allocate */ + PJ_TRUE /* respond to refresh */ + } + }; + struct test_session *sess; + unsigned i; + int rc; + + PJ_LOG(3,("", " state progression tests")); + + for (i=0; i<=1; ++i) { + enum { TIMEOUT = 60 }; + pjlib_state pjlib_state; + pj_turn_session_info info; + struct test_result result; + pj_time_val tstart; + + PJ_LOG(3,("", " %s DNS SRV resolution", + (i==0? "without" : "with"))); + + capture_pjlib_state(stun_cfg, &pjlib_state); + + test_cfg.client.enable_dns_srv = i; + + rc = create_test_session(stun_cfg, &test_cfg, &sess); + if (rc != 0) + return rc; + + pj_bzero(&info, sizeof(info)); + + /* Wait until state is READY */ + pj_gettimeofday(&tstart); + while (sess->turn_sock) { + pj_time_val now; + + poll_events(stun_cfg, 10, PJ_FALSE); + rc = pj_turn_sock_get_info(sess->turn_sock, &info); + if (rc!=PJ_SUCCESS) + break; + + if (info.state >= PJ_TURN_STATE_READY) + break; + + pj_gettimeofday(&now); + if (now.sec - tstart.sec > TIMEOUT) { + PJ_LOG(3,("", " timed-out")); + break; + } + } + + if (info.state != PJ_TURN_STATE_READY) { + PJ_LOG(3,("", " error: state is not READY")); + destroy_session(sess); + return -130; + } + + /* Deallocate */ + pj_turn_sock_destroy(sess->turn_sock); + + /* Wait for couple of seconds. + * We can't poll the session info since the session may have + * been destroyed + */ + poll_events(stun_cfg, 2000, PJ_FALSE); + sess->turn_sock = NULL; + pj_memcpy(&result, &sess->result, sizeof(result)); + destroy_session(sess); + + /* Check the result */ + if ((result.state_called & (1<turn_sock) { + pj_time_val now; + + poll_events(stun_cfg, 100, PJ_FALSE); + + pj_gettimeofday(&now); + if (now.sec - tstart.sec > TIMEOUT) { + rc = -7; + break; + } + } + + } else { + pj_gettimeofday(&tstart); + rc = 0; + while (sess->turn_sock) { + pj_time_val now; + + poll_events(stun_cfg, 1, PJ_FALSE); + + pj_turn_sock_get_info(sess->turn_sock, &info); + + if (info.state >= target_state) { + pj_turn_sock_destroy(sess->turn_sock); + break; + } + + pj_gettimeofday(&now); + if (now.sec - tstart.sec > TIMEOUT) { + rc = -8; + break; + } + } + } + + + if (rc != 0) { + PJ_LOG(3,("", " error: timeout")); + return rc; + } + + poll_events(stun_cfg, 1000, PJ_FALSE); + destroy_session(sess); + + rc = check_pjlib_state(stun_cfg, &pjlib_state); + if (rc != 0) { + PJ_LOG(3,("", " error: memory/timer-heap leak detected")); + return rc; + } + } + + return 0; +} + + +///////////////////////////////////////////////////////////////////// + +int turn_sock_test(void) +{ + pj_pool_t *pool; + pj_stun_config stun_cfg; + int i, rc = 0; + + pool = pj_pool_create(mem, "turntest", 512, 512, NULL); + rc = create_stun_config(pool, &stun_cfg); + if (rc != PJ_SUCCESS) { + pj_pool_release(pool); + return -2; + } + + rc = state_progression_test(&stun_cfg); + if (rc != 0) + goto on_return; + + for (i=0; i<=1; ++i) { + int j; + for (j=0; j<=1; ++j) { + rc = destroy_test(&stun_cfg, i, j); + if (rc != 0) + goto on_return; + } + } + +on_return: + destroy_stun_config(&stun_cfg); + pj_pool_release(pool); + return rc; +} + diff --git a/pjnath/src/pjnath/errno.c b/pjnath/src/pjnath/errno.c index a075a0c0..e71fa634 100644 --- a/pjnath/src/pjnath/errno.c +++ b/pjnath/src/pjnath/errno.c @@ -52,6 +52,8 @@ static const struct PJ_BUILD_ERR( PJNATH_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"), PJ_BUILD_ERR( PJNATH_ESTUNINSERVER, "Invalid STUN server or server not configured"), + PJ_BUILD_ERR( PJNATH_ESTUNDESTROYED, "STUN object has been destoyed"), + /* ICE related errors */ PJ_BUILD_ERR( PJNATH_ENOICE, "ICE session not available"), PJ_BUILD_ERR( PJNATH_EICEINPROGRESS, "ICE check is in progress"), diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c index 8b0538d8..ccf545c5 100644 --- a/pjnath/src/pjnath/ice_session.c +++ b/pjnath/src/pjnath/ice_session.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -101,6 +102,11 @@ typedef struct timer_data } timer_data; +/* This is the data that will be attached as token to outgoing + * STUN messages. + */ + + /* Forward declarations */ static void destroy_ice(pj_ice_sess *ice, pj_status_t reason); @@ -169,6 +175,21 @@ PJ_DEF(const char*) pj_ice_get_cand_type_name(pj_ice_cand_type type) } +PJ_DEF(const char*) pj_ice_sess_role_name(pj_ice_sess_role role) +{ + switch (role) { + case PJ_ICE_SESS_ROLE_UNKNOWN: + return "Unknown"; + case PJ_ICE_SESS_ROLE_CONTROLLED: + return "Controlled"; + case PJ_ICE_SESS_ROLE_CONTROLLING: + return "Controlling"; + default: + return "??"; + } +} + + /* Get the prefix for the foundation */ static int get_type_prefix(pj_ice_cand_type type) { @@ -183,17 +204,28 @@ static int get_type_prefix(pj_ice_cand_type type) } } -/* Calculate foundation */ +/* Calculate foundation: + * Two candidates have the same foundation when they are "similar" - of + * the same type and obtained from the same host candidate and STUN + * server using the same protocol. Otherwise, their foundation is + * different. + */ PJ_DEF(void) pj_ice_calc_foundation(pj_pool_t *pool, pj_str_t *foundation, pj_ice_cand_type type, const pj_sockaddr *base_addr) { char buf[64]; + pj_uint32_t val; + if (base_addr->addr.sa_family == pj_AF_INET()) { + val = pj_ntohl(base_addr->ipv4.sin_addr.s_addr); + } else { + val = pj_hash_calc(0, pj_sockaddr_get_addr(base_addr), + pj_sockaddr_get_addr_len(base_addr)); + } pj_ansi_snprintf(buf, sizeof(buf), "%c%x", - get_type_prefix(type), - (int)pj_ntohl(base_addr->ipv4.sin_addr.s_addr)); + get_type_prefix(type), val); pj_strdup2(pool, foundation, buf); } @@ -263,7 +295,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, PJ_ASSERT_RETURN(stun_cfg && cb && p_ice, PJ_EINVAL); if (name == NULL) - name = "ice%p"; + name = "icess%p"; pool = pj_pool_create(stun_cfg->pf, name, PJNATH_POOL_LEN_ICE_SESS, PJNATH_POOL_INC_ICE_SESS, NULL); @@ -300,6 +332,12 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg, } } + /* Initialize transport datas */ + for (i=0; itp_data); ++i) { + ice->tp_data[i].transport_id = i; + ice->tp_data[i].has_req_data = PJ_FALSE; + } + if (local_ufrag == NULL) { ice->rx_ufrag.ptr = (char*) pj_pool_alloc(ice->pool, PJ_ICE_UFRAG_LEN); pj_create_random_string(ice->rx_ufrag.ptr, PJ_ICE_UFRAG_LEN); @@ -551,6 +589,7 @@ static pj_uint32_t CALC_CAND_PRIO(pj_ice_sess *ice, */ PJ_DEF(pj_status_t) pj_ice_sess_add_cand(pj_ice_sess *ice, unsigned comp_id, + unsigned transport_id, pj_ice_cand_type type, pj_uint16_t local_pref, const pj_str_t *foundation, @@ -576,17 +615,14 @@ PJ_DEF(pj_status_t) pj_ice_sess_add_cand(pj_ice_sess *ice, } lcand = &ice->lcand[ice->lcand_cnt]; - lcand->comp_id = comp_id; + lcand->comp_id = (pj_uint8_t)comp_id; + lcand->transport_id = (pj_uint8_t)transport_id; lcand->type = type; pj_strdup(ice->pool, &lcand->foundation, foundation); lcand->prio = CALC_CAND_PRIO(ice, type, local_pref, lcand->comp_id); pj_memcpy(&lcand->addr, addr, addr_len); pj_memcpy(&lcand->base_addr, base_addr, addr_len); - if (rel_addr) - pj_memcpy(&lcand->rel_addr, rel_addr, addr_len); - else - pj_bzero(&lcand->rel_addr, sizeof(lcand->rel_addr)); - + pj_memcpy(&lcand->rel_addr, rel_addr, addr_len); pj_ansi_strcpy(ice->tmp.txt, pj_inet_ntoa(lcand->addr.ipv4.sin_addr)); LOG4((ice->obj_name, @@ -1322,9 +1358,13 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list( } /* Disable our components which don't have matching component */ - if (ice->comp_cnt==2 && highest_comp==1) { - ice->comp_cnt = 1; + for (i=highest_comp; icomp_cnt; ++i) { + if (ice->comp[i].stun_sess) { + pj_stun_session_destroy(ice->comp[i].stun_sess); + pj_bzero(&ice->comp[i], sizeof(ice->comp[i])); + } } + ice->comp_cnt = highest_comp; /* Init timer entry in the checklist. Initially the timer ID is FALSE * because timer is not running. @@ -1345,26 +1385,13 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list( return PJ_SUCCESS; } - -/* This is the data that will be attached as user data to outgoing - * STUN requests, and it will be given back when we receive completion - * status of the request. - */ -struct req_data -{ - pj_ice_sess *ice; - pj_ice_sess_checklist *clist; - unsigned ckid; -}; - - /* Perform check on the specified candidate pair */ static pj_status_t perform_check(pj_ice_sess *ice, pj_ice_sess_checklist *clist, unsigned check_id) { pj_ice_sess_comp *comp; - struct req_data *rd; + pj_ice_msg_data *msg_data; pj_ice_sess_check *check; const pj_ice_sess_cand *lcand; const pj_ice_sess_cand *rcand; @@ -1392,10 +1419,12 @@ static pj_status_t perform_check(pj_ice_sess *ice, /* Attach data to be retrieved later when STUN request transaction * completes and on_stun_request_complete() callback is called. */ - rd = PJ_POOL_ZALLOC_T(check->tdata->pool, struct req_data); - rd->ice = ice; - rd->clist = clist; - rd->ckid = check_id; + msg_data = PJ_POOL_ZALLOC_T(check->tdata->pool, pj_ice_msg_data); + msg_data->transport_id = lcand->transport_id; + msg_data->has_req_data = PJ_TRUE; + msg_data->data.req.ice = ice; + msg_data->data.req.clist = clist; + msg_data->data.req.ckid = check_id; /* Add PRIORITY */ prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535, @@ -1427,7 +1456,7 @@ static pj_status_t perform_check(pj_ice_sess *ice, */ /* Initiate STUN transaction to send the request */ - status = pj_stun_session_send_msg(comp->stun_sess, (void*)rd, PJ_FALSE, + status = pj_stun_session_send_msg(comp->stun_sess, msg_data, PJ_FALSE, PJ_TRUE, &rcand->addr, sizeof(pj_sockaddr_in), check->tdata); if (status != PJ_SUCCESS) { @@ -1655,12 +1684,10 @@ static pj_status_t on_stun_send_msg(pj_stun_session *sess, { stun_data *sd = (stun_data*) pj_stun_session_get_user_data(sess); pj_ice_sess *ice = sd->ice; - - PJ_UNUSED_ARG(token); - - return (*ice->cb.on_tx_pkt)(ice, sd->comp_id, - pkt, pkt_size, - dst_addr, addr_len); + pj_ice_msg_data *msg_data = (pj_ice_msg_data*) token; + + return (*ice->cb.on_tx_pkt)(ice, sd->comp_id, msg_data->transport_id, + pkt, pkt_size, dst_addr, addr_len); } @@ -1673,7 +1700,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { - struct req_data *rd = (struct req_data*) token; + pj_ice_msg_data *msg_data = (pj_ice_msg_data*) token; pj_ice_sess *ice; pj_ice_sess_check *check, *new_check; pj_ice_sess_cand *lcand; @@ -1684,9 +1711,12 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, PJ_UNUSED_ARG(stun_sess); PJ_UNUSED_ARG(src_addr_len); - ice = rd->ice; - check = &rd->clist->checks[rd->ckid]; - clist = rd->clist; + pj_assert(msg_data->has_req_data); + + ice = msg_data->data.req.ice; + clist = msg_data->data.req.clist; + check = &clist->checks[msg_data->data.req.ckid]; + /* Mark STUN transaction as complete */ pj_assert(tdata == check->tdata); @@ -1739,7 +1769,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, /* Resend request */ LOG4((ice->obj_name, "Resending check because of role conflict")); check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_WAITING, 0); - perform_check(ice, clist, rd->ckid); + perform_check(ice, clist, msg_data->data.req.ckid); pj_mutex_unlock(ice->mutex); return; } @@ -1846,6 +1876,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess, /* Add new peer reflexive candidate */ status = pj_ice_sess_add_cand(ice, check->lcand->comp_id, + msg_data->transport_id, PJ_ICE_CAND_TYPE_PRFLX, 65535, &foundation, &xaddr->sockaddr, @@ -1919,6 +1950,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, { stun_data *sd; const pj_stun_msg *msg = rdata->msg; + pj_ice_msg_data *msg_data; pj_ice_sess *ice; pj_stun_priority_attr *prio_attr; pj_stun_use_candidate_attr *uc_attr; @@ -1929,12 +1961,11 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, PJ_UNUSED_ARG(pkt); PJ_UNUSED_ARG(pkt_len); - PJ_UNUSED_ARG(token); - + /* Reject any requests except Binding request */ if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) { pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST, - NULL, NULL, PJ_TRUE, + NULL, token, PJ_TRUE, src_addr, src_addr_len); return PJ_SUCCESS; } @@ -2001,7 +2032,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, } else { /* Generate 487 response */ pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, - NULL, NULL, PJ_TRUE, + NULL, token, PJ_TRUE, src_addr, src_addr_len); pj_mutex_unlock(ice->mutex); return PJ_SUCCESS; @@ -2013,7 +2044,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) { /* Generate 487 response */ pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT, - NULL, NULL, PJ_TRUE, + NULL, token, PJ_TRUE, src_addr, src_addr_len); pj_mutex_unlock(ice->mutex); return PJ_SUCCESS; @@ -2034,11 +2065,18 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, return status; } + /* Add XOR-MAPPED-ADDRESS attribute */ status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, src_addr, src_addr_len); - status = pj_stun_session_send_msg(sess, NULL, PJ_TRUE, PJ_TRUE, + /* Create a msg_data to be associated with this response */ + msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data); + msg_data->transport_id = ((pj_ice_msg_data*)token)->transport_id; + msg_data->has_req_data = PJ_FALSE; + + /* Send the response */ + status = pj_stun_session_send_msg(sess, msg_data, PJ_TRUE, PJ_TRUE, src_addr, src_addr_len, tdata); @@ -2058,6 +2096,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess, /* Init rcheck */ rcheck->comp_id = sd->comp_id; + rcheck->transport_id = ((pj_ice_msg_data*)token)->transport_id; rcheck->src_addr_len = src_addr_len; pj_memcpy(&rcheck->src_addr, src_addr, src_addr_len); rcheck->use_candidate = (uc_attr != NULL); @@ -2090,7 +2129,6 @@ static void handle_incoming_check(pj_ice_sess *ice, pj_ice_sess_cand *lcand = NULL; pj_ice_sess_cand *rcand; unsigned i; - pj_bool_t is_relayed; comp = find_comp(ice, rcheck->comp_id); @@ -2109,7 +2147,7 @@ static void handle_incoming_check(pj_ice_sess *ice, */ if (i == ice->rcand_cnt) { rcand = &ice->rcand[ice->rcand_cnt++]; - rcand->comp_id = rcheck->comp_id; + rcand->comp_id = (pj_uint8_t)rcheck->comp_id; rcand->type = PJ_ICE_CAND_TYPE_PRFLX; rcand->prio = rcheck->priority; pj_memcpy(&rcand->addr, &rcheck->src_addr, rcheck->src_addr_len); @@ -2147,12 +2185,14 @@ static void handle_incoming_check(pj_ice_sess *ice, } } #else - /* Just get candidate with the highest priority for the specified - * component ID in the checklist. + /* Just get candidate with the highest priority and same transport ID + * for the specified component ID in the checklist. */ for (i=0; iclist.count; ++i) { pj_ice_sess_check *c = &ice->clist.checks[i]; - if (c->lcand->comp_id == rcheck->comp_id) { + if (c->lcand->comp_id == rcheck->comp_id && + c->lcand->transport_id == rcheck->transport_id) + { lcand = c->lcand; break; } @@ -2170,11 +2210,6 @@ static void handle_incoming_check(pj_ice_sess *ice, /* * Create candidate pair for this request. */ - /* First check if the source address is the source address of the - * STUN relay, to determine if local candidate is relayed candidate. - */ - PJ_TODO(DETERMINE_IF_REQUEST_COMES_FROM_RELAYED_CANDIDATE); - is_relayed = PJ_FALSE; /* * 7.2.1.4. Triggered Checks @@ -2309,6 +2344,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice, { pj_status_t status = PJ_SUCCESS; pj_ice_sess_comp *comp; + pj_ice_sess_cand *cand; PJ_ASSERT_RETURN(ice && comp_id, PJ_EINVAL); @@ -2332,7 +2368,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice, goto on_return; } - status = (*ice->cb.on_tx_pkt)(ice, comp_id, data, data_len, + cand = comp->valid_check->lcand; + status = (*ice->cb.on_tx_pkt)(ice, comp_id, cand->transport_id, + data, data_len, &comp->valid_check->rcand->addr, sizeof(pj_sockaddr_in)); @@ -2344,6 +2382,7 @@ on_return: PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, unsigned comp_id, + unsigned transport_id, void *pkt, pj_size_t pkt_size, const pj_sockaddr_t *src_addr, @@ -2351,6 +2390,8 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, { pj_status_t status = PJ_SUCCESS; pj_ice_sess_comp *comp; + pj_ice_msg_data *msg_data = NULL; + unsigned i; pj_status_t stun_status; PJ_ASSERT_RETURN(ice, PJ_EINVAL); @@ -2363,11 +2404,24 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, goto on_return; } + /* Find transport */ + for (i=0; itp_data); ++i) { + if (ice->tp_data[i].transport_id == transport_id) { + msg_data = &ice->tp_data[i]; + break; + } + } + if (msg_data == NULL) { + pj_assert(!"Invalid transport ID"); + status = PJ_EINVAL; + goto on_return; + } + stun_status = pj_stun_msg_check((const pj_uint8_t*)pkt, pkt_size, PJ_STUN_IS_DATAGRAM); if (stun_status == PJ_SUCCESS) { status = pj_stun_session_on_rx_pkt(comp->stun_sess, pkt, pkt_size, - PJ_STUN_IS_DATAGRAM, NULL, + PJ_STUN_IS_DATAGRAM, msg_data, NULL, src_addr, src_addr_len); if (status != PJ_SUCCESS) { pj_strerror(status, ice->tmp.errmsg, sizeof(ice->tmp.errmsg)); @@ -2375,7 +2429,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice, ice->tmp.errmsg)); } } else { - (*ice->cb.on_rx_data)(ice, comp_id, pkt, pkt_size, + (*ice->cb.on_rx_data)(ice, comp_id, transport_id, pkt, pkt_size, src_addr, src_addr_len); } diff --git a/pjnath/src/pjnath/ice_strans.c b/pjnath/src/pjnath/ice_strans.c index 7266e079..34b74f0d 100644 --- a/pjnath/src/pjnath/ice_strans.c +++ b/pjnath/src/pjnath/ice_strans.c @@ -19,9 +19,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -35,792 +37,571 @@ #endif +/* Transport IDs */ +enum tp_type +{ + TP_NONE, + TP_STUN, + TP_TURN +}; + +/* Candidate preference default values */ +#define SRFLX_PREF 65535 +#define HOST_PREF 65530 +#define RELAY_PREF 65525 + /* ICE callbacks */ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status); static pj_status_t ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, + unsigned transport_id, const void *pkt, pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len); static void ice_rx_data(pj_ice_sess *ice, - unsigned comp_id, - void *pkt, pj_size_t size, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len); - -/* Ioqueue callback */ -static void on_read_complete(pj_ioqueue_key_t *key, - pj_ioqueue_op_key_t *op_key, - pj_ssize_t bytes_read); - -static void destroy_component(pj_ice_strans_comp *comp); -static void destroy_ice_st(pj_ice_strans *ice_st, pj_status_t reason); - - -/* STUN session callback */ -static pj_status_t stun_on_send_msg(pj_stun_session *sess, - void *token, - const void *pkt, - pj_size_t pkt_size, - const pj_sockaddr_t *dst_addr, - unsigned addr_len); -static void stun_on_request_complete(pj_stun_session *sess, - pj_status_t status, - void *token, - pj_stun_tx_data *tdata, - const pj_stun_msg *response, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len); - -/* Keep-alive timer */ -static void start_ka_timer(pj_ice_strans *ice_st); -static void stop_ka_timer(pj_ice_strans *ice_st); - -/* Utility: print error */ + unsigned comp_id, + unsigned transport_id, + void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + + +/* STUN socket callbacks */ +/* Notification when incoming packet has been received. */ +static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *src_addr, + unsigned addr_len); +/* Notifification when asynchronous send operation has completed. */ +static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock, + pj_ioqueue_op_key_t *send_key, + pj_ssize_t sent); +/* Notification when the status of the STUN transport has changed. */ +static pj_bool_t stun_on_status(pj_stun_sock *stun_sock, + pj_stun_sock_op op, + pj_status_t status); + + +/* TURN callbacks */ +static void turn_on_rx_data(pj_turn_sock *turn_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, + pj_turn_state_t new_state); + + + +/* Forward decls */ +static void destroy_ice_st(pj_ice_strans *ice_st); #define ice_st_perror(ice_st,msg,rc) pjnath_perror(ice_st->obj_name,msg,rc) - -/* - * Create ICE stream transport +static void sess_init_update(pj_ice_strans *ice_st); + +static void sess_add_ref(pj_ice_strans *ice_st); +static pj_bool_t sess_dec_ref(pj_ice_strans *ice_st); + +/** + * This structure describes an ICE stream transport component. A component + * in ICE stream transport typically corresponds to a single socket created + * for this component, and bound to a specific transport address. This + * component may have multiple alias addresses, for example one alias + * address for each interfaces in multi-homed host, another for server + * reflexive alias, and another for relayed alias. For each transport + * address alias, an ICE stream transport candidate (#pj_ice_sess_cand) will + * be created, and these candidates will eventually registered to the ICE + * session. */ -PJ_DEF(pj_status_t) pj_ice_strans_create( pj_stun_config *stun_cfg, - const char *name, - unsigned comp_cnt, - void *user_data, - const pj_ice_strans_cb *cb, - pj_ice_strans **p_ice_st) +typedef struct pj_ice_strans_comp { - pj_pool_t *pool; - pj_ice_strans *ice_st; + pj_ice_strans *ice_st; /**< ICE stream transport. */ + unsigned comp_id; /**< Component ID. */ - PJ_ASSERT_RETURN(stun_cfg && comp_cnt && cb && p_ice_st, PJ_EINVAL); - PJ_ASSERT_RETURN(stun_cfg->ioqueue && stun_cfg->timer_heap, PJ_EINVAL); + pj_stun_sock *stun_sock; /**< STUN transport. */ + pj_turn_sock *turn_sock; /**< TURN relay transport. */ - if (name == NULL) - name = "icstr%p"; + unsigned cand_cnt; /**< # of candidates/aliaes. */ + pj_ice_sess_cand cand_list[PJ_ICE_ST_MAX_CAND]; /**< Cand array */ - pool = pj_pool_create(stun_cfg->pf, name, PJNATH_POOL_LEN_ICE_STRANS, - PJNATH_POOL_INC_ICE_STRANS, NULL); - ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans); - ice_st->pool = pool; - pj_memcpy(ice_st->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME); - ice_st->user_data = user_data; - - ice_st->comp_cnt = comp_cnt; - ice_st->comp = (pj_ice_strans_comp**) pj_pool_calloc(pool, comp_cnt, - sizeof(void*)); + unsigned default_cand; /**< Default candidate. */ - pj_memcpy(&ice_st->cb, cb, sizeof(*cb)); - pj_memcpy(&ice_st->stun_cfg, stun_cfg, sizeof(*stun_cfg)); +} pj_ice_strans_comp; - PJ_LOG(4,(ice_st->obj_name, "ICE stream transport created")); +/** + * This structure represents the ICE stream transport. + */ +struct pj_ice_strans +{ + char *obj_name; /**< Log ID. */ + pj_pool_t *pool; /**< Pool used by this object. */ + void *user_data; /**< Application data. */ + pj_ice_strans_cfg cfg; /**< Configuration. */ + pj_ice_strans_cb cb; /**< Application callback. */ - *p_ice_st = ice_st; - return PJ_SUCCESS; -} + pj_ice_sess *ice; /**< ICE session. */ + pj_time_val start_time;/**< Time when ICE was started */ -/* Destroy ICE */ -static void destroy_ice_st(pj_ice_strans *ice_st, pj_status_t reason) -{ - unsigned i; - char obj_name[PJ_MAX_OBJ_NAME]; + unsigned comp_cnt; /**< Number of components. */ + pj_ice_strans_comp **comp; /**< Components array. */ - if (reason == PJ_SUCCESS) { - pj_memcpy(obj_name, ice_st->obj_name, PJ_MAX_OBJ_NAME); - PJ_LOG(4,(obj_name, "ICE stream transport shutting down")); - } + pj_timer_entry ka_timer; /**< STUN keep-alive timer. */ - /* Kill keep-alive timer, if any */ - stop_ka_timer(ice_st); + pj_atomic_t *busy_cnt; /**< To prevent destroy */ + pj_bool_t destroy_req;/**< Destroy has been called? */ + pj_bool_t cb_called; /**< Init error callback called?*/ +}; - /* Destroy ICE if we have ICE */ - if (ice_st->ice) { - pj_ice_sess_destroy(ice_st->ice); - ice_st->ice = NULL; - } - /* Destroy all components */ - for (i=0; icomp_cnt; ++i) { - if (ice_st->comp[i]) { - destroy_component(ice_st->comp[i]); - ice_st->comp[i] = NULL; - } - } - ice_st->comp_cnt = 0; +/* Validate configuration */ +static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) +{ + pj_status_t status; - /* Done */ - pj_pool_release(ice_st->pool); + status = pj_stun_config_check_valid(&cfg->stun_cfg); + if (!status) + return status; - if (reason == PJ_SUCCESS) { - PJ_LOG(4,(obj_name, "ICE stream transport destroyed")); - } + return PJ_SUCCESS; } + /* - * Destroy ICE stream transport. + * Initialize ICE transport configuration with default values. */ -PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st) +PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg) { - destroy_ice_st(ice_st, PJ_SUCCESS); - return PJ_SUCCESS; + pj_bzero(cfg, sizeof(*cfg)); + + pj_stun_config_init(&cfg->stun_cfg, NULL, 0, NULL, NULL); + pj_stun_sock_cfg_default(&cfg->stun.cfg); + pj_turn_alloc_param_default(&cfg->turn.alloc_param); + + cfg->af = pj_AF_INET(); + cfg->stun.port = PJ_STUN_PORT; + cfg->turn.conn_type = PJ_TURN_TP_UDP; } + /* - * Resolve STUN server + * Copy configuration. */ -PJ_DEF(pj_status_t) pj_ice_strans_set_stun_domain(pj_ice_strans *ice_st, - pj_dns_resolver *resolver, - const pj_str_t *domain) +PJ_DEF(void) pj_ice_strans_cfg_copy( pj_pool_t *pool, + pj_ice_strans_cfg *dst, + const pj_ice_strans_cfg *src) { - /* Yeah, TODO */ - PJ_UNUSED_ARG(ice_st); - PJ_UNUSED_ARG(resolver); - PJ_UNUSED_ARG(domain); - return -1; + pj_memcpy(dst, src, sizeof(*src)); + + if (src->stun.server.slen) + pj_strdup(pool, &dst->stun.server, &src->stun.server); + if (src->turn.server.slen) + pj_strdup(pool, &dst->turn.server, &src->turn.server); + pj_stun_auth_cred_dup(pool, &dst->turn.auth_cred, + &src->turn.auth_cred); } /* - * Set STUN server address. + * Create the component. */ -PJ_DEF(pj_status_t) pj_ice_strans_set_stun_srv( pj_ice_strans *ice_st, - const pj_sockaddr_in *stun_srv, - const pj_sockaddr_in *turn_srv) +static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id) { - PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); - /* Must not have pending resolver job */ - PJ_ASSERT_RETURN(ice_st->has_rjob==PJ_FALSE, PJ_EINVALIDOP); - - if (stun_srv) { - pj_memcpy(&ice_st->stun_srv, stun_srv, sizeof(pj_sockaddr_in)); - } else { - pj_bzero(&ice_st->stun_srv, sizeof(pj_sockaddr_in)); - } - - if (turn_srv) { - pj_memcpy(&ice_st->turn_srv, turn_srv, sizeof(pj_sockaddr_in)); - } else { - pj_bzero(&ice_st->turn_srv, sizeof(pj_sockaddr_in)); - } - - return PJ_SUCCESS; -} - -/* Add new candidate */ -static pj_status_t add_cand( pj_ice_strans *ice_st, - pj_ice_strans_comp *comp, - unsigned comp_id, - pj_ice_cand_type type, - pj_uint16_t local_pref, - const pj_sockaddr_in *addr, - pj_bool_t set_default) -{ - pj_ice_strans_cand *cand; - unsigned i; - - PJ_ASSERT_RETURN(ice_st && comp && addr, PJ_EINVAL); - PJ_ASSERT_RETURN(comp->cand_cnt < PJ_ICE_ST_MAX_CAND, PJ_ETOOMANY); - - /* Check that we don't have candidate with the same - * address. - */ - for (i=0; icand_cnt; ++i) { - if (pj_memcmp(addr, &comp->cand_list[i].addr, - sizeof(pj_sockaddr_in))==0) - { - /* Duplicate */ - PJ_LOG(5,(ice_st->obj_name, "Duplicate candidate not added")); - return PJ_SUCCESS; - } - } + pj_ice_strans_comp *comp = NULL; + pj_status_t status; - cand = &comp->cand_list[comp->cand_cnt]; - - pj_bzero(cand, sizeof(*cand)); - cand->type = type; - cand->status = PJ_SUCCESS; - pj_memcpy(&cand->addr, addr, sizeof(pj_sockaddr_in)); - cand->ice_cand_id = -1; - cand->local_pref = local_pref; - pj_ice_calc_foundation(ice_st->pool, &cand->foundation, type, - &comp->local_addr); - - if (set_default) - comp->default_cand = comp->cand_cnt; - - PJ_LOG(5,(ice_st->obj_name, - "Candidate %s:%d (type=%s) added to component %d", - pj_inet_ntoa(addr->sin_addr), - (int)pj_ntohs(addr->sin_port), - pj_ice_get_cand_type_name(type), - comp_id)); - - comp->cand_cnt++; - return PJ_SUCCESS; -} + /* Verify arguments */ + PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL); -/* Create new component (i.e. socket) */ -static pj_status_t create_component(pj_ice_strans *ice_st, - unsigned comp_id, - pj_uint32_t options, - const pj_sockaddr_in *addr, - pj_ice_strans_comp **p_comp) -{ - enum { MAX_RETRY=100, PORT_INC=2 }; - pj_ioqueue_callback ioqueue_cb; - pj_ice_strans_comp *comp; - int retry, addr_len; - struct { - pj_uint32_t a1, a2, a3; - } tsx_id; - pj_status_t status; + /* Check that component ID present */ + PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID); + /* Create component */ comp = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_strans_comp); comp->ice_st = ice_st; comp->comp_id = comp_id; - comp->options = options; - comp->sock = PJ_INVALID_SOCKET; - comp->last_status = PJ_SUCCESS; - - /* Create transaction ID for STUN keep alives */ - tsx_id.a1 = 0; - tsx_id.a2 = comp_id; - tsx_id.a3 = (pj_uint32_t) (unsigned long) ice_st; - pj_memcpy(comp->ka_tsx_id, &tsx_id, sizeof(comp->ka_tsx_id)); - - /* Create socket */ - status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &comp->sock); - if (status != PJ_SUCCESS) - return status; - - /* Init address */ - if (addr) - pj_memcpy(&comp->local_addr, addr, sizeof(pj_sockaddr_in)); - else - pj_sockaddr_in_init(&comp->local_addr.ipv4, NULL, 0); - - /* Retry binding socket */ - for (retry=0; retrysock, &comp->local_addr, - sizeof(pj_sockaddr_in)); - if (status == PJ_SUCCESS) - break; - - if (options & PJ_ICE_ST_OPT_NO_PORT_RETRY) - goto on_error; - - port = pj_ntohs(comp->local_addr.ipv4.sin_port); - port += PORT_INC; - comp->local_addr.ipv4.sin_port = pj_htons(port); - } - - /* Get the actual port where the socket is bound to. - * (don't care about the address, it will be retrieved later) - */ - addr_len = sizeof(comp->local_addr); - status = pj_sock_getsockname(comp->sock, &comp->local_addr, &addr_len); - if (status != PJ_SUCCESS) - goto on_error; - - /* Register to ioqueue */ - pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb)); - ioqueue_cb.on_read_complete = &on_read_complete; - status = pj_ioqueue_register_sock(ice_st->pool, ice_st->stun_cfg.ioqueue, - comp->sock, comp, &ioqueue_cb, - &comp->key); - if (status != PJ_SUCCESS) - goto on_error; - - /* Disable concurrency */ - status = pj_ioqueue_set_concurrency(comp->key, PJ_FALSE); - if (status != PJ_SUCCESS) - goto on_error; - - pj_ioqueue_op_key_init(&comp->read_op, sizeof(comp->read_op)); - pj_ioqueue_op_key_init(&comp->write_op, sizeof(comp->write_op)); - - /* Kick start reading the socket */ - on_read_complete(comp->key, &comp->read_op, 0); - /* If the socket is bound to INADDR_ANY, then lookup all interfaces in - * the host and add them into cand_list. Otherwise if the socket is bound - * to a specific interface, then only add that specific interface to - * cand_list. - */ - if (((options & PJ_ICE_ST_OPT_DONT_ADD_CAND)==0) && - comp->local_addr.ipv4.sin_addr.s_addr == 0) - { - /* Socket is bound to INADDR_ANY */ - unsigned i, ifs_cnt; - pj_sockaddr ifs[PJ_ICE_ST_MAX_CAND-2]; - - /* Reset default candidate */ - comp->default_cand = -1; - - /* Enum all IP interfaces in the host */ - ifs_cnt = PJ_ARRAY_SIZE(ifs); - status = pj_enum_ip_interface(pj_AF_INET(), &ifs_cnt, ifs); - if (status != PJ_SUCCESS) - goto on_error; + ice_st->comp[comp_id-1] = comp; - /* Set default IP interface as the base address */ - status = pj_gethostip(pj_AF_INET(), &comp->local_addr); + /* Initialize default candidate */ + comp->default_cand = 0; + + /* Create STUN transport if configured */ + if (ice_st->cfg.stun.server.slen || !ice_st->cfg.stun.no_host_cands) { + pj_stun_sock_cb stun_sock_cb; + pj_ice_sess_cand *cand; + + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_rx_data = &stun_on_rx_data; + stun_sock_cb.on_status = &stun_on_status; + stun_sock_cb.on_data_sent = &stun_on_data_sent; + + /* Create the STUN transport */ + status = pj_stun_sock_create(&ice_st->cfg.stun_cfg, NULL, + ice_st->cfg.af, &stun_sock_cb, + &ice_st->cfg.stun.cfg, + comp, &comp->stun_sock); if (status != PJ_SUCCESS) - goto on_error; - - /* Add candidate entry for each interface */ - for (i=0; i> 24)==127) - continue; - - pj_memcpy(&cand_addr, &comp->local_addr, sizeof(pj_sockaddr_in)); - cand_addr.sin_addr.s_addr = ifs[i].ipv4.sin_addr.s_addr; + return status; + /* Start STUN Binding resolution and add srflx candidate + * only if server is set + */ + if (ice_st->cfg.stun.server.slen) { + pj_stun_sock_info stun_sock_info; + + /* Add pending job */ + ///sess_add_ref(ice_st); + + /* Start Binding resolution */ + status = pj_stun_sock_start(comp->stun_sock, + &ice_st->cfg.stun.server, + ice_st->cfg.stun.port, + ice_st->cfg.resolver); + if (status != PJ_SUCCESS) { + ///sess_dec_ref(ice_st); + return status; + } - /* If the IP address is equal to local address, assign it - * as default candidate. - */ - if (ifs[i].ipv4.sin_addr.s_addr == comp->local_addr.ipv4.sin_addr.s_addr) { - set_default = PJ_TRUE; - local_pref = 65535; - } else { - set_default = PJ_FALSE; - local_pref = 0; + /* Enumerate addresses */ + status = pj_stun_sock_get_info(comp->stun_sock, &stun_sock_info); + if (status != PJ_SUCCESS) { + ///sess_dec_ref(ice_st); + return status; } - status = add_cand(ice_st, comp, comp_id, - PJ_ICE_CAND_TYPE_HOST, - local_pref, &cand_addr, set_default); - if (status != PJ_SUCCESS) - goto on_error; + /* Add srflx candidate with pending status */ + cand = &comp->cand_list[comp->cand_cnt++]; + cand->type = PJ_ICE_CAND_TYPE_SRFLX; + cand->status = PJ_EPENDING; + cand->local_pref = SRFLX_PREF; + cand->transport_id = TP_STUN; + cand->comp_id = (pj_uint8_t) comp_id; + pj_sockaddr_cp(&cand->base_addr, &stun_sock_info.aliases[0]); + pj_sockaddr_cp(&cand->rel_addr, &cand->base_addr); + pj_ice_calc_foundation(ice_st->pool, &cand->foundation, + cand->type, &cand->base_addr); + PJ_LOG(4,(ice_st->obj_name, + "Comp %d: srflx candidate starts Binding discovery", + comp_id)); + + /* Set default candidate to srflx */ + comp->default_cand = cand - comp->cand_list; } - - } else if ((options & PJ_ICE_ST_OPT_DONT_ADD_CAND)==0) { - /* Socket is bound to specific address. - * In this case only add that address as a single entry in the - * cand_list table. + /* Add local addresses to host candidates, unless no_host_cands + * flag is set. */ - status = add_cand(ice_st, comp, comp_id, - PJ_ICE_CAND_TYPE_HOST, - 65535, &comp->local_addr.ipv4, - PJ_TRUE); - if (status != PJ_SUCCESS) - goto on_error; + if (ice_st->cfg.stun.no_host_cands == PJ_FALSE) { + pj_stun_sock_info stun_sock_info; + unsigned i; - } else if (options & PJ_ICE_ST_OPT_DONT_ADD_CAND) { - /* If application doesn't want to add candidate, just fix local_addr - * in case its value is zero. - */ - if (comp->local_addr.ipv4.sin_addr.s_addr == 0) { - status = pj_gethostip(pj_AF_INET(), &comp->local_addr); + /* Enumerate addresses */ + status = pj_stun_sock_get_info(comp->stun_sock, &stun_sock_info); if (status != PJ_SUCCESS) return status; + + for (i=0; icand_cnt >= PJ_ICE_ST_MAX_CAND-1) { + PJ_LOG(4,(ice_st->obj_name, "Too many host candidates")); + break; + } + + /* Ignore loopback addresses unless cfg->stun.loop_addr + * is set + */ + if ((pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==127) { + if (ice_st->cfg.stun.loop_addr==PJ_FALSE) + continue; + } + + cand = &comp->cand_list[comp->cand_cnt++]; + + cand->type = PJ_ICE_CAND_TYPE_HOST; + cand->status = PJ_SUCCESS; + cand->local_pref = HOST_PREF; + cand->transport_id = TP_STUN; + cand->comp_id = (pj_uint8_t) comp_id; + pj_sockaddr_cp(&cand->addr, addr); + pj_sockaddr_cp(&cand->base_addr, addr); + pj_bzero(&cand->rel_addr, sizeof(cand->rel_addr)); + pj_ice_calc_foundation(ice_st->pool, &cand->foundation, + cand->type, &cand->base_addr); + + PJ_LOG(4,(ice_st->obj_name, + "Comp %d: host candidate %s added", + comp_id, pj_sockaddr_print(&cand->addr, addrinfo, + sizeof(addrinfo), 3))); + } } } + /* Create TURN relay if configured. */ + if (ice_st->cfg.turn.server.slen) { + pj_turn_sock_cb turn_sock_cb; + pj_ice_sess_cand *cand; - /* Done */ - if (p_comp) - *p_comp = comp; - - return PJ_SUCCESS; - -on_error: - destroy_component(comp); - return status; -} + /* Init TURN socket */ + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_rx_data = &turn_on_rx_data; + turn_sock_cb.on_state = &turn_on_state; -/* - * This is callback called by ioqueue on incoming packet - */ -static void on_read_complete(pj_ioqueue_key_t *key, - pj_ioqueue_op_key_t *op_key, - pj_ssize_t bytes_read) -{ - pj_ice_strans_comp *comp = (pj_ice_strans_comp*) - pj_ioqueue_get_user_data(key); - pj_ice_strans *ice_st = comp->ice_st; - pj_ssize_t pkt_size; - enum { RETRY = 10 }; - unsigned retry; - pj_status_t status; + status = pj_turn_sock_create(&ice_st->cfg.stun_cfg, ice_st->cfg.af, + ice_st->cfg.turn.conn_type, + &turn_sock_cb, 0, comp, + &comp->turn_sock); + if (status != PJ_SUCCESS) { + return status; + } - if (bytes_read > 0) { - /* - * Okay, we got a packet from the socket for the component. There is - * a bit of situation here, since this packet could be one of these: - * - * 1) this could be the response of STUN binding request sent by - * this component to a) an initial request to get the STUN mapped - * address of this component, or b) subsequent request to keep - * the binding alive. - * - * 2) this could be a packet (STUN or not STUN) sent from the STUN - * relay server. In this case, still there are few options to do - * for this packet: a) process this locally if this packet is - * related to TURN session management (e.g. Allocate response), - * b) forward this packet to ICE if this is related to ICE - * discovery process. - * - * 3) this could be a STUN request or response sent as part of ICE - * discovery process. - * - * 4) this could be application's packet, e.g. when ICE processing - * is done and agents start sending RTP/RTCP packets to each - * other, or when ICE processing is not done and this ICE stream - * transport decides to allow sending data. - * - * So far we don't have good solution for this. - * The process below is just a workaround. - */ - status = pj_stun_msg_check(comp->pkt, bytes_read, - PJ_STUN_IS_DATAGRAM); + /* Add pending job */ + ///sess_add_ref(ice_st); - if (status == PJ_SUCCESS) { - if (comp->stun_sess && - PJ_STUN_IS_RESPONSE(((pj_stun_msg_hdr*)comp->pkt)->type) && - pj_memcmp(comp->pkt+8, comp->ka_tsx_id, 12) == 0) - { - status = pj_stun_session_on_rx_pkt(comp->stun_sess, comp->pkt, - bytes_read, - PJ_STUN_IS_DATAGRAM, NULL, - NULL, &comp->src_addr, - comp->src_addr_len); - } else if (ice_st->ice) { - PJ_TODO(DISTINGUISH_BETWEEN_LOCAL_AND_RELAY); - - TRACE_PKT((comp->ice_st->obj_name, - "Component %d RX packet from %s:%d", - comp->comp_id, - pj_inet_ntoa(comp->src_addr.ipv4.sin_addr), - (int)pj_ntohs(comp->src_addr.ipv4.sin_port))); - - status = pj_ice_sess_on_rx_pkt(ice_st->ice, comp->comp_id, - comp->pkt, bytes_read, - &comp->src_addr, - comp->src_addr_len); - } else { - /* This must have been a very late STUN reponse, - * or an early STUN Binding Request when our local - * ICE has not been created yet. */ - } - } else { - (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id, - comp->pkt, bytes_read, - &comp->src_addr, comp->src_addr_len); + /* Start allocation */ + status=pj_turn_sock_alloc(comp->turn_sock, + &ice_st->cfg.turn.server, + ice_st->cfg.turn.port, + ice_st->cfg.resolver, + &ice_st->cfg.turn.auth_cred, + &ice_st->cfg.turn.alloc_param); + if (status != PJ_SUCCESS) { + ///sess_dec_ref(ice_st); + return status; } - } else if (bytes_read < 0) { - ice_st_perror(comp->ice_st, "ioqueue read callback error", - -bytes_read); - } + /* Add relayed candidate with pending status */ + cand = &comp->cand_list[comp->cand_cnt++]; + cand->type = PJ_ICE_CAND_TYPE_RELAYED; + cand->status = PJ_EPENDING; + cand->local_pref = RELAY_PREF; + cand->transport_id = TP_TURN; + cand->comp_id = (pj_uint8_t) comp_id; - /* Read next packet */ - for (retry=0; retrypkt); - comp->src_addr_len = sizeof(comp->src_addr); - status = pj_ioqueue_recvfrom(key, op_key, comp->pkt, &pkt_size, - PJ_IOQUEUE_ALWAYS_ASYNC, - &comp->src_addr, &comp->src_addr_len); - if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) || - status == PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) || - status == PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) - { - ice_st_perror(comp->ice_st, "ioqueue recvfrom() error", status); - ++retry; - continue; - } else if (status != PJ_SUCCESS && status != PJ_EPENDING) { - retry += 2; - ice_st_perror(comp->ice_st, "ioqueue recvfrom() error", status); - } else { - break; - } - } -} + PJ_LOG(4,(ice_st->obj_name, + "Comp %d: TURN relay candidate waiting for allocation", + comp_id)); -/* - * Destroy a component - */ -static void destroy_component(pj_ice_strans_comp *comp) -{ - if (comp->stun_sess) { - pj_stun_session_destroy(comp->stun_sess); - comp->stun_sess = NULL; + /* Set default candidate to relay */ + comp->default_cand = cand - comp->cand_list; } - if (comp->key) { - pj_ioqueue_unregister(comp->key); - comp->key = NULL; - comp->sock = PJ_INVALID_SOCKET; - } else if (comp->sock != PJ_INVALID_SOCKET && comp->sock != 0) { - pj_sock_close(comp->sock); - comp->sock = PJ_INVALID_SOCKET; - } + return PJ_SUCCESS; } -/* STUN keep-alive timer callback */ -static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te) +/* + * Create ICE stream transport + */ +PJ_DEF(pj_status_t) pj_ice_strans_create( const char *name, + const pj_ice_strans_cfg *cfg, + unsigned comp_cnt, + void *user_data, + const pj_ice_strans_cb *cb, + pj_ice_strans **p_ice_st) { - pj_ice_strans *ice_st = (pj_ice_strans*)te->user_data; + pj_pool_t *pool; + pj_ice_strans *ice_st; unsigned i; pj_status_t status; - PJ_UNUSED_ARG(th); + status = pj_ice_strans_cfg_check_valid(cfg); + if (status != PJ_SUCCESS) + return status; - ice_st->ka_timer.id = PJ_FALSE; + PJ_ASSERT_RETURN(comp_cnt && cb && p_ice_st, PJ_EINVAL); - for (i=0; icomp_cnt; ++i) { - pj_ice_strans_comp *comp = ice_st->comp[i]; - pj_stun_tx_data *tdata; - unsigned j; + if (name == NULL) + name = "ice%p"; - /* Does this component have STUN server reflexive candidate? */ - for (j=0; jcand_cnt; ++j) { - if (comp->cand_list[j].type == PJ_ICE_CAND_TYPE_SRFLX) - break; - } - if (j == comp->cand_cnt) - continue; - - /* Create STUN binding request */ - status = pj_stun_session_create_req(comp->stun_sess, - PJ_STUN_BINDING_REQUEST, - PJ_STUN_MAGIC, - comp->ka_tsx_id, &tdata); - if (status != PJ_SUCCESS) - continue; + pool = pj_pool_create(cfg->stun_cfg.pf, name, PJNATH_POOL_LEN_ICE_STRANS, + PJNATH_POOL_INC_ICE_STRANS, NULL); + ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans); + ice_st->pool = pool; + ice_st->obj_name = pool->obj_name; + ice_st->user_data = user_data; - /* tdata->user_data is NULL for keep-alive */ - //tdata->user_data = NULL; + PJ_LOG(4,(ice_st->obj_name, + "Creating ICE stream transport with %d component(s)", + comp_cnt)); - ++comp->pending_cnt; + pj_ice_strans_cfg_copy(pool, &ice_st->cfg, cfg); + pj_memcpy(&ice_st->cb, cb, sizeof(*cb)); + + status = pj_atomic_create(pool, 0, &ice_st->busy_cnt); + if (status != PJ_SUCCESS) { + destroy_ice_st(ice_st); + return status; + } + ice_st->comp_cnt = comp_cnt; + ice_st->comp = (pj_ice_strans_comp**) + pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*)); - /* Send STUN binding request */ - PJ_LOG(5,(ice_st->obj_name, "Sending STUN keep-alive from %s;%d", - pj_inet_ntoa(comp->local_addr.ipv4.sin_addr), - pj_ntohs(comp->local_addr.ipv4.sin_port))); - status = pj_stun_session_send_msg(comp->stun_sess, &comp->cand_list[j], - PJ_FALSE, PJ_TRUE, &ice_st->stun_srv, - sizeof(pj_sockaddr_in), tdata); + for (i=0; ipending_cnt; + destroy_ice_st(ice_st); + return status; } } - /* Start next timer */ - start_ka_timer(ice_st); -} + /* Check if all candidates are ready (this may call callback) */ + sess_init_update(ice_st); -/* Start STUN keep-alive timer */ -static void start_ka_timer(pj_ice_strans *ice_st) -{ - pj_time_val delay; + PJ_LOG(4,(ice_st->obj_name, "ICE stream transport created")); - /* Skip if timer is already running */ - if (ice_st->ka_timer.id != PJ_FALSE) - return; + *p_ice_st = ice_st; + return PJ_SUCCESS; +} - delay.sec = PJ_ICE_ST_KEEP_ALIVE_MIN; - delay.msec = pj_rand() % (PJ_ICE_ST_KEEP_ALIVE_MAX_RAND * 1000); - pj_time_val_normalize(&delay); +/* Destroy ICE */ +static void destroy_ice_st(pj_ice_strans *ice_st) +{ + unsigned i; - ice_st->ka_timer.cb = &ka_timer_cb; - ice_st->ka_timer.user_data = ice_st; - - if (pj_timer_heap_schedule(ice_st->stun_cfg.timer_heap, - &ice_st->ka_timer, &delay)==PJ_SUCCESS) - { - ice_st->ka_timer.id = PJ_TRUE; + /* Destroy ICE if we have ICE */ + if (ice_st->ice) { + pj_ice_sess_destroy(ice_st->ice); + ice_st->ice = NULL; } -} + /* Destroy all components */ + for (i=0; icomp_cnt; ++i) { + if (ice_st->comp[i]) { + if (ice_st->comp[i]->stun_sock) { + pj_stun_sock_set_user_data(ice_st->comp[i]->stun_sock, NULL); + pj_stun_sock_destroy(ice_st->comp[i]->stun_sock); + ice_st->comp[i]->stun_sock = NULL; + } + if (ice_st->comp[i]->turn_sock) { + pj_turn_sock_set_user_data(ice_st->comp[i]->turn_sock, NULL); + pj_turn_sock_destroy(ice_st->comp[i]->turn_sock); + ice_st->comp[i]->turn_sock = NULL; + } + } + } + ice_st->comp_cnt = 0; -/* Stop STUN keep-alive timer */ -static void stop_ka_timer(pj_ice_strans *ice_st) -{ - /* Skip if timer is already stop */ - if (ice_st->ka_timer.id == PJ_FALSE) - return; + /* Destroy reference counter */ + if (ice_st->busy_cnt) { + pj_assert(pj_atomic_get(ice_st->busy_cnt)==0); + pj_atomic_destroy(ice_st->busy_cnt); + ice_st->busy_cnt = NULL; + } - pj_timer_heap_cancel(ice_st->stun_cfg.timer_heap, &ice_st->ka_timer); - ice_st->ka_timer.id = PJ_FALSE; + /* Done */ + pj_pool_release(ice_st->pool); } - -/* - * Add STUN mapping to a component. - */ -static pj_status_t get_stun_mapped_addr(pj_ice_strans *ice_st, - pj_ice_strans_comp *comp) +/* Notification about failure */ +static void sess_fail(pj_ice_strans *ice_st, pj_ice_strans_op op, + const char *title, pj_status_t status) { - pj_ice_strans_cand *cand; - pj_stun_session_cb sess_cb; - pj_stun_tx_data *tdata; - pj_status_t status; + char errmsg[PJ_ERR_MSG_SIZE]; - PJ_ASSERT_RETURN(ice_st && comp, PJ_EINVAL); - - /* Bail out if STUN server is still being resolved */ - if (ice_st->has_rjob) - return PJ_EBUSY; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(ice_st->obj_name, "%s: %s", title, errmsg)); + + if (op==PJ_ICE_STRANS_OP_INIT && ice_st->cb_called) + return; - /* Just return (successfully) if STUN server is not configured */ - if (ice_st->stun_srv.sin_family == 0) - return PJ_SUCCESS; + ice_st->cb_called = PJ_TRUE; + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, op, status); +} - /* Create STUN session for this component */ - pj_bzero(&sess_cb, sizeof(sess_cb)); - sess_cb.on_request_complete = &stun_on_request_complete; - sess_cb.on_send_msg = &stun_on_send_msg; - status = pj_stun_session_create(&ice_st->stun_cfg, ice_st->obj_name, - &sess_cb, PJ_FALSE, &comp->stun_sess); - if (status != PJ_SUCCESS) - return status; +/* Update initialization status */ +static void sess_init_update(pj_ice_strans *ice_st) +{ + unsigned i; - /* Associate component with STUN session */ - pj_stun_session_set_user_data(comp->stun_sess, (void*)comp); + /* Ignore if init callback has been called */ + if (ice_st->cb_called) + return; - /* Create STUN binding request */ - status = pj_stun_session_create_req(comp->stun_sess, - PJ_STUN_BINDING_REQUEST, - PJ_STUN_MAGIC, - comp->ka_tsx_id, - &tdata); - if (status != PJ_SUCCESS) - return status; + /* Notify application when all candidates have been gathered */ + for (i=0; icomp_cnt; ++i) { + unsigned j; + pj_ice_strans_comp *comp = ice_st->comp[i]; - /* Will be attached to tdata in send_msg() */ - cand = &comp->cand_list[comp->cand_cnt]; + for (j=0; jcand_cnt; ++j) { + pj_ice_sess_cand *cand = &comp->cand_list[j]; - /* Add pending count first, since stun_on_request_complete() - * may be called before this function completes - */ - comp->pending_cnt++; - - /* Add new alias to this component */ - cand->type = PJ_ICE_CAND_TYPE_SRFLX; - cand->status = PJ_EPENDING; - cand->ice_cand_id = -1; - cand->local_pref = 65535; - pj_ice_calc_foundation(ice_st->pool, &cand->foundation, - PJ_ICE_CAND_TYPE_SRFLX, &comp->local_addr); - - ++comp->cand_cnt; - - /* Send STUN binding request */ - status = pj_stun_session_send_msg(comp->stun_sess, (void*)cand, PJ_FALSE, - PJ_TRUE, &ice_st->stun_srv, - sizeof(pj_sockaddr_in), tdata); - if (status != PJ_SUCCESS) { - --comp->pending_cnt; - --comp->cand_cnt; - return status; + if (cand->status == PJ_EPENDING) + return; + } } - return PJ_SUCCESS; + /* All candidates have been gathered */ + ice_st->cb_called = PJ_TRUE; + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, + PJ_SUCCESS); } - /* - * Create the component. + * Destroy ICE stream transport. */ -PJ_DEF(pj_status_t) pj_ice_strans_create_comp(pj_ice_strans *ice_st, - unsigned comp_id, - pj_uint32_t options, - const pj_sockaddr_in *addr) +PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st) { - pj_ice_strans_comp *comp = NULL; - pj_status_t status; - - /* Verify arguments */ - PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL); - - /* Check that component ID present */ - PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID); - - /* Can't add new component while ICE is running */ - PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EBUSY); - - /* Can't add new component while resolver is running */ - PJ_ASSERT_RETURN(ice_st->has_rjob == PJ_FALSE, PJ_EBUSY); - + char obj_name[PJ_MAX_OBJ_NAME]; - /* Create component */ - status = create_component(ice_st, comp_id, options, addr, &comp); - if (status != PJ_SUCCESS) - return status; + PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); - if ((options & PJ_ICE_ST_OPT_DISABLE_STUN) == 0) { - status = get_stun_mapped_addr(ice_st, comp); - if (status != PJ_SUCCESS) { - destroy_component(comp); - return status; - } + ice_st->destroy_req = PJ_TRUE; + if (pj_atomic_get(ice_st->busy_cnt) > 0) { + PJ_LOG(5,(ice_st->obj_name, + "ICE strans object is busy, will destroy later")); + return PJ_EPENDING; } + + pj_memcpy(obj_name, ice_st->obj_name, PJ_MAX_OBJ_NAME); + destroy_ice_st(ice_st); - /* Store this component */ - ice_st->comp[comp_id-1] = comp; - + PJ_LOG(4,(obj_name, "ICE stream transport destroyed")); return PJ_SUCCESS; } -PJ_DEF(pj_status_t) pj_ice_strans_add_cand( pj_ice_strans *ice_st, - unsigned comp_id, - pj_ice_cand_type type, - pj_uint16_t local_pref, - const pj_sockaddr_in *addr, - pj_bool_t set_default) +/* + * Increment busy counter. + */ +static void sess_add_ref(pj_ice_strans *ice_st) { - pj_ice_strans_comp *comp; - - - PJ_ASSERT_RETURN(ice_st && comp_id && addr, PJ_EINVAL); - PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJ_EINVAL); - PJ_ASSERT_RETURN(ice_st->comp[comp_id-1] != NULL, PJ_EINVALIDOP); - - comp = ice_st->comp[comp_id-1]; - return add_cand(ice_st, comp, comp_id, type, local_pref, addr, - set_default); + pj_atomic_inc(ice_st->busy_cnt); } - -PJ_DEF(pj_status_t) pj_ice_strans_get_comps_status(pj_ice_strans *ice_st) +/* + * Decrement busy counter. If the counter has reached zero and destroy + * has been requested, destroy the object and return FALSE. + */ +static pj_bool_t sess_dec_ref(pj_ice_strans *ice_st) { - unsigned i; - pj_status_t worst = PJ_SUCCESS; - - for (i=0; icomp_cnt; ++i) { - pj_ice_strans_comp *comp = ice_st->comp[i]; - - if (comp->last_status == PJ_SUCCESS) { - /* okay */ - } else if (comp->pending_cnt && worst==PJ_SUCCESS) { - worst = PJ_EPENDING; - break; - } else if (comp->last_status != PJ_SUCCESS) { - worst = comp->last_status; - break; - } - - if (worst != PJ_SUCCESS) - break; + int count = pj_atomic_dec_and_get(ice_st->busy_cnt); + pj_assert(count >= 0); + if (count==0 && ice_st->destroy_req) { + pj_ice_strans_destroy(ice_st); + return PJ_FALSE; + } else { + return PJ_TRUE; } +} - return worst; +/* + * Get user data + */ +PJ_DEF(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st, NULL); + return ice_st->user_data; } + /* * Create ICE! */ @@ -832,7 +613,7 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st, pj_status_t status; unsigned i; pj_ice_sess_cb ice_cb; - const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 }; + //const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 }; /* Check arguments */ PJ_ASSERT_RETURN(ice_st, PJ_EINVAL); @@ -848,7 +629,7 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st, ice_cb.on_tx_pkt = &ice_tx_pkt; /* Create! */ - status = pj_ice_sess_create(&ice_st->stun_cfg, ice_st->obj_name, role, + status = pj_ice_sess_create(&ice_st->cfg.stun_cfg, ice_st->obj_name, role, ice_st->comp_cnt, &ice_cb, local_ufrag, local_passwd, &ice_st->ice); if (status != PJ_SUCCESS) @@ -857,6 +638,7 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st, /* Associate user data */ ice_st->ice->user_data = (void*)ice_st; +#if 0 /* If default candidate for components are SRFLX one, upload a custom * type priority to ICE session so that SRFLX candidates will get * checked first. @@ -867,30 +649,44 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st, { pj_ice_sess_set_prefs(ice_st->ice, srflx_prio); } +#endif - - /* Add candidates */ + /* Add components/candidates */ for (i=0; icomp_cnt; ++i) { unsigned j; - pj_ice_strans_comp *comp= ice_st->comp[i]; + pj_ice_strans_comp *comp = ice_st->comp[i]; + + /* Re-enable logging for Send/Data indications */ + if (comp->turn_sock) { + PJ_LOG(5,(ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", i+1)); + pj_turn_sock_set_log(comp->turn_sock, 0xFFFF); + } for (j=0; jcand_cnt; ++j) { - pj_ice_strans_cand *cand = &comp->cand_list[j]; + pj_ice_sess_cand *cand = &comp->cand_list[j]; + unsigned ice_cand_id; /* Skip if candidate is not ready */ if (cand->status != PJ_SUCCESS) { PJ_LOG(5,(ice_st->obj_name, - "Candidate %d in component %d is not added", + "Candidate %d of comp %d is not added (pending)", j, i)); continue; } + /* Must have address */ + pj_assert(pj_sockaddr_has_addr(&cand->addr)); + + /* Add the candidate */ status = pj_ice_sess_add_cand(ice_st->ice, comp->comp_id, - cand->type, cand->local_pref, + cand->transport_id, cand->type, + cand->local_pref, &cand->foundation, &cand->addr, - &comp->local_addr, NULL, - sizeof(pj_sockaddr_in), - (unsigned*)&cand->ice_cand_id); + &cand->base_addr, &cand->rel_addr, + pj_sockaddr_get_len(&cand->addr), + (unsigned*)&ice_cand_id); if (status != PJ_SUCCESS) goto on_error; } @@ -907,28 +703,71 @@ on_error: * Enum candidates */ PJ_DEF(pj_status_t) pj_ice_strans_enum_cands(pj_ice_strans *ice_st, - unsigned *count, - pj_ice_sess_cand cand[]) + unsigned comp_id, + unsigned *count, + pj_ice_sess_cand cand[]) { unsigned i, cnt; - pj_ice_sess_cand *pcand; + pj_ice_strans_comp *comp; - PJ_ASSERT_RETURN(ice_st && count && cand, PJ_EINVAL); - PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP); + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt && + count && cand, PJ_EINVAL); - cnt = ice_st->ice->lcand_cnt; + comp = ice_st->comp[comp_id - 1]; + cnt = comp->cand_cnt; cnt = (cnt > *count) ? *count : cnt; - *count = 0; for (i=0; iice->lcand[i]; - pj_memcpy(&cand[i], pcand, sizeof(pj_ice_sess_cand)); + pj_memcpy(&cand[i], &comp->cand_list[i], sizeof(pj_ice_sess_cand)); } *count = cnt; return PJ_SUCCESS; } +/* + * Get default candidate. + */ +PJ_DEF(pj_status_t) pj_ice_strans_get_def_cand( pj_ice_strans *ice_st, + unsigned comp_id, + pj_ice_sess_cand *cand) +{ + const pj_ice_sess_check *valid_pair; + + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt && + cand, PJ_EINVAL); + + valid_pair = pj_ice_strans_get_valid_pair(ice_st, comp_id); + if (valid_pair) { + pj_memcpy(cand, valid_pair->lcand, sizeof(pj_ice_sess_cand)); + } else { + pj_ice_strans_comp *comp = ice_st->comp[comp_id - 1]; + pj_assert(comp->default_cand>=0 && comp->default_candcand_cnt); + pj_memcpy(cand, &comp->cand_list[comp->default_cand], + sizeof(pj_ice_sess_cand)); + } + return PJ_SUCCESS; +} + +/* + * Get the current ICE role. + */ +PJ_DEF(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_ICE_SESS_ROLE_UNKNOWN); + return ice_st->ice->role; +} + +/* + * Change session role. + */ +PJ_DEF(pj_status_t) pj_ice_strans_change_role( pj_ice_strans *ice_st, + pj_ice_sess_role new_role) +{ + PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP); + return pj_ice_sess_change_role(ice_st->ice, new_role); +} + /* * Start ICE processing ! */ @@ -940,11 +779,19 @@ PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st, { pj_status_t status; + PJ_ASSERT_RETURN(ice_st && rem_ufrag && rem_passwd && + rem_cand_cnt && rem_cand, PJ_EINVAL); + + /* Mark start time */ + pj_gettimeofday(&ice_st->start_time); + + /* Build check list */ status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd, rem_cand_cnt, rem_cand); if (status != PJ_SUCCESS) return status; + /* Start ICE negotiation! */ status = pj_ice_sess_start_check(ice_st->ice); if (status != PJ_SUCCESS) { pj_ice_strans_stop_ice(ice_st); @@ -953,31 +800,37 @@ PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st, return status; } +/* + * Get valid pair. + */ +PJ_DEF(const pj_ice_sess_check*) +pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st, + unsigned comp_id) +{ + PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt, + NULL); + + if (ice_st->ice == NULL) + return NULL; + + return ice_st->ice->comp[comp_id-1].valid_check; +} + /* * Stop ICE! */ PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) { - unsigned i; - if (ice_st->ice) { pj_ice_sess_destroy(ice_st->ice); ice_st->ice = NULL; } - /* Invalidate all candidate Ids */ - for (i=0; icomp_cnt; ++i) { - unsigned j; - for (j=0; jcomp[i]->cand_cnt; ++j) { - ice_st->comp[i]->cand_list[j].ice_cand_id = -1; - } - } - return PJ_SUCCESS; } /* - * Send packet using non-ICE means (e.g. when ICE was not negotiated). + * Application wants to send outgoing packet. */ PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st, unsigned comp_id, @@ -997,18 +850,24 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st, /* If ICE is available, send data with ICE */ if (ice_st->ice) { - return pj_ice_sess_send_data(ice_st->ice, comp_id, data, data_len); - } + if (comp->turn_sock) { + pj_turn_sock_lock(comp->turn_sock); + } + status = pj_ice_sess_send_data(ice_st->ice, comp_id, data, data_len); + if (comp->turn_sock) { + pj_turn_sock_unlock(comp->turn_sock); + } + return status; - /* Otherwise send direcly with the socket. This is for compatibility - * with remote that doesn't support ICE. - */ - pkt_size = data_len; - status = pj_ioqueue_sendto(comp->key, &comp->write_op, - data, &pkt_size, 0, - dst_addr, dst_addr_len); - - return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status; + } else if (comp->stun_sock) { + + pkt_size = data_len; + status = pj_stun_sock_sendto(comp->stun_sock, NULL, data, data_len, + 0, dst_addr, dst_addr_len); + return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status; + + } else + return PJ_EINVALIDOP; } /* @@ -1018,9 +877,79 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st, static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) { pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data; + pj_time_val t; + unsigned msec; + + sess_add_ref(ice_st); + + pj_gettimeofday(&t); + PJ_TIME_VAL_SUB(t, ice_st->start_time); + msec = PJ_TIME_VAL_MSEC(t); + if (ice_st->cb.on_ice_complete) { - (*ice_st->cb.on_ice_complete)(ice_st, status); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(ice_st->obj_name, + "ICE negotiation failed after %ds:%03d: %s", + msec/1000, msec%1000, errmsg)); + } else { + unsigned i; + enum { + msg_disable_ind = 0xFFFF & + ~(PJ_STUN_SESS_LOG_TX_IND| + PJ_STUN_SESS_LOG_RX_IND) + }; + + PJ_LOG(4,(ice_st->obj_name, + "ICE negotiation success after %ds:%03d", + msec/1000, msec%1000)); + + for (i=0; icomp_cnt; ++i) { + const pj_ice_sess_check *check; + + check = pj_ice_strans_get_valid_pair(ice_st, i+1); + if (check) { + char lip[PJ_INET6_ADDRSTRLEN+10]; + char rip[PJ_INET6_ADDRSTRLEN+10]; + + pj_sockaddr_print(&check->lcand->addr, lip, + sizeof(lip), 3); + pj_sockaddr_print(&check->rcand->addr, rip, + sizeof(rip), 3); + + if (check->lcand->transport_id == TP_TURN) { + /* Disable logging for Send/Data indications */ + PJ_LOG(5,(ice_st->obj_name, + "Disabling STUN Indication logging for " + "component %d", i+1)); + pj_turn_sock_set_log(ice_st->comp[i]->turn_sock, + msg_disable_ind); + } + + PJ_LOG(4,(ice_st->obj_name, " Comp %d: " + "sending from %s candidate %s to " + "%s candidate %s", + i+1, + pj_ice_get_cand_type_name(check->lcand->type), + lip, + pj_ice_get_cand_type_name(check->rcand->type), + rip)); + + } else { + PJ_LOG(4,(ice_st->obj_name, + "Comp %d: disabled", i+1)); + } + } + } + + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, + status); + + } + + sess_dec_ref(ice_st); } /* @@ -1028,30 +957,42 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status) */ static pj_status_t ice_tx_pkt(pj_ice_sess *ice, unsigned comp_id, + unsigned transport_id, const void *pkt, pj_size_t size, const pj_sockaddr_t *dst_addr, unsigned dst_addr_len) { pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data; - pj_ice_strans_comp *comp = NULL; - pj_ssize_t pkt_size; + pj_ice_strans_comp *comp; pj_status_t status; - PJ_TODO(TX_TO_RELAY); - PJ_ASSERT_RETURN(comp_id && comp_id <= ice_st->comp_cnt, PJ_EINVAL); + comp = ice_st->comp[comp_id-1]; TRACE_PKT((comp->ice_st->obj_name, - "Component %d TX packet to %s:%d", - comp_id, + "Component %d TX packet to %s:%d with transport %d", + comp_id, pj_inet_ntoa(((pj_sockaddr_in*)dst_addr)->sin_addr), - (int)pj_ntohs(((pj_sockaddr_in*)dst_addr)->sin_port))); - - pkt_size = size; - status = pj_ioqueue_sendto(comp->key, &comp->write_op, - pkt, &pkt_size, 0, - dst_addr, dst_addr_len); + (int)pj_ntohs(((pj_sockaddr_in*)dst_addr)->sin_port), + transport_id)); + + if (transport_id == TP_TURN) { + if (comp->turn_sock) { + status = pj_turn_sock_sendto(comp->turn_sock, + (const pj_uint8_t*)pkt, size, + dst_addr, dst_addr_len); + } else { + status = PJ_EINVALIDOP; + } + } else if (transport_id == TP_STUN) { + status = pj_stun_sock_sendto(comp->stun_sock, NULL, + pkt, size, 0, + dst_addr, dst_addr_len); + } else { + pj_assert(!"Invalid transport ID"); + status = PJ_EINVALIDOP; + } return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status; } @@ -1061,148 +1002,295 @@ static pj_status_t ice_tx_pkt(pj_ice_sess *ice, */ static void ice_rx_data(pj_ice_sess *ice, unsigned comp_id, + unsigned transport_id, void *pkt, pj_size_t size, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data; + PJ_UNUSED_ARG(transport_id); + if (ice_st->cb.on_rx_data) { (*ice_st->cb.on_rx_data)(ice_st, comp_id, pkt, size, src_addr, src_addr_len); } } -/* - * Callback called by STUN session to send outgoing packet. +/* Notification when incoming packet has been received from + * the STUN socket. */ -static pj_status_t stun_on_send_msg(pj_stun_session *sess, - void *token, - const void *pkt, - pj_size_t size, - const pj_sockaddr_t *dst_addr, - unsigned dst_addr_len) +static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *src_addr, + unsigned addr_len) { pj_ice_strans_comp *comp; - pj_ssize_t pkt_size; + pj_ice_strans *ice_st; pj_status_t status; - PJ_UNUSED_ARG(token); + comp = (pj_ice_strans_comp*) pj_stun_sock_get_user_data(stun_sock); + ice_st = comp->ice_st; - comp = (pj_ice_strans_comp*) pj_stun_session_get_user_data(sess); - pkt_size = size; - status = pj_ioqueue_sendto(comp->key, &comp->write_op, - pkt, &pkt_size, 0, - dst_addr, dst_addr_len); - - return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status; + sess_add_ref(ice_st); + + if (ice_st->ice == NULL) { + /* The ICE session is gone, but we're still receiving packets. + * This could also happen if remote doesn't do ICE. So just + * report this to application. + */ + if (ice_st->cb.on_rx_data) { + (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id, pkt, pkt_len, + src_addr, addr_len); + } + + } else { + + /* Hand over the packet to ICE session */ + status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id, + TP_STUN, pkt, pkt_len, + src_addr, addr_len); + + if (status != PJ_SUCCESS) { + ice_st_perror(comp->ice_st, "Error processing packet", + status); + } + } + + return sess_dec_ref(ice_st); } -/* - * Callback sent by STUN session when outgoing STUN request has - * completed. +/* Notifification when asynchronous send operation to the STUN socket + * has completed. */ -static void stun_on_request_complete(pj_stun_session *sess, - pj_status_t status, - void *token, - pj_stun_tx_data *tdata, - const pj_stun_msg *response, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len) +static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock, + pj_ioqueue_op_key_t *send_key, + pj_ssize_t sent) +{ + PJ_UNUSED_ARG(stun_sock); + PJ_UNUSED_ARG(send_key); + PJ_UNUSED_ARG(sent); + return PJ_TRUE; +} + +/* Notification when the status of the STUN transport has changed. */ +static pj_bool_t stun_on_status(pj_stun_sock *stun_sock, + pj_stun_sock_op op, + pj_status_t status) { pj_ice_strans_comp *comp; - pj_ice_strans_cand *cand = NULL; - pj_stun_xor_mapped_addr_attr *xa; - pj_stun_mapped_addr_attr *ma; - pj_sockaddr *mapped_addr; - char ip[20]; - - comp = (pj_ice_strans_comp*) pj_stun_session_get_user_data(sess); - cand = (pj_ice_strans_cand*) token; - - PJ_UNUSED_ARG(token); - PJ_UNUSED_ARG(tdata); - PJ_UNUSED_ARG(src_addr); - PJ_UNUSED_ARG(src_addr_len); - - if (cand == NULL) { - /* This is keep-alive */ + pj_ice_strans *ice_st; + pj_ice_sess_cand *cand = NULL; + unsigned i; + + comp = (pj_ice_strans_comp*) pj_stun_sock_get_user_data(stun_sock); + ice_st = comp->ice_st; + + sess_add_ref(ice_st); + + /* Find the srflx cancidate */ + for (i=0; icand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX) { + cand = &comp->cand_list[i]; + break; + } + } + + pj_assert(status != PJ_EPENDING); + + switch (op) { + case PJ_STUN_SOCK_DNS_OP: if (status != PJ_SUCCESS) { - ice_st_perror(comp->ice_st, "STUN keep-alive request failed", - status); + /* May not have cand, e.g. when error during init */ + if (cand) + cand->status = status; + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "DNS resolution failed", + status); + } + break; + case PJ_STUN_SOCK_BINDING_OP: + if (status == PJ_SUCCESS) { + pj_stun_sock_info info; + + status = pj_stun_sock_get_info(stun_sock, &info); + if (status == PJ_SUCCESS) { + char ipaddr[PJ_INET6_ADDRSTRLEN+10]; + pj_bool_t dup = PJ_FALSE; + + /* Eliminate the srflx candidate if the address is + * equal to other (host) candidates. + */ + for (i=0; icand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_HOST && + pj_sockaddr_cmp(&comp->cand_list[i].addr, + &info.mapped_addr) == 0) + { + dup = PJ_TRUE; + break; + } + } + + if (dup) { + /* Duplicate found, remove the srflx candidate */ + pj_array_erase(comp->cand_list, sizeof(comp->cand_list[0]), + comp->cand_cnt, cand - comp->cand_list); + --comp->cand_cnt; + } else { + /* Otherwise update the address */ + pj_sockaddr_cp(&cand->addr, &info.mapped_addr); + cand->status = PJ_SUCCESS; + } + + PJ_LOG(4,(comp->ice_st->obj_name, + "Comp %d: Binding discovery complete, " + "srflx address is %s", + comp->comp_id, + pj_sockaddr_print(&info.mapped_addr, ipaddr, + sizeof(ipaddr), 3))); + + sess_init_update(ice_st); + } + } + + if (status != PJ_SUCCESS) { + /* May not have cand, e.g. when error during init */ + if (cand) + cand->status = status; + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, + "STUN binding request failed", status); } + break; + case PJ_STUN_SOCK_KEEP_ALIVE_OP: + if (status != PJ_SUCCESS) { + pj_assert(cand != NULL); + cand->status = status; + sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, + "STUN keep-alive failed", status); + } + break; + } + + return sess_dec_ref(ice_st); +} + +/* Callback when TURN socket has received a packet */ +static void turn_on_rx_data(pj_turn_sock *turn_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + pj_ice_strans_comp *comp; + pj_status_t status; + + comp = (pj_ice_strans_comp*) pj_turn_sock_get_user_data(turn_sock); + if (comp == NULL) { + /* We have disassociated ourselves from the TURN socket */ return; } - /* Decrement pending count for this component */ - pj_assert(comp->pending_cnt > 0); - comp->pending_cnt--; + sess_add_ref(comp->ice_st); - if (status == PJNATH_ESTUNTIMEDOUT) { + if (comp->ice_st->ice == NULL) { + /* The ICE session is gone, but we're still receiving packets. + * This could also happen if remote doesn't do ICE and application + * specifies TURN as the default address in SDP. + * So in this case just give the packet to application. + */ + if (comp->ice_st->cb.on_rx_data) { + (*comp->ice_st->cb.on_rx_data)(comp->ice_st, comp->comp_id, pkt, + pkt_len, peer_addr, addr_len); + } - PJ_LOG(4,(comp->ice_st->obj_name, - "STUN Binding request has timed-out, will retry " - "again alter")); + } else { - /* Restart keep-alive timer */ - start_ka_timer(comp->ice_st); - return; + /* Hand over the packet to ICE */ + status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id, + TP_TURN, pkt, pkt_len, + peer_addr, addr_len); - } else if (status != PJ_SUCCESS) { - comp->last_status = cand->status = status; - ice_st_perror(comp->ice_st, "STUN Binding request failed", - cand->status); - return; + if (status != PJ_SUCCESS) { + ice_st_perror(comp->ice_st, + "Error processing packet from TURN relay", + status); + } } - xa = (pj_stun_xor_mapped_addr_attr*) - pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); - ma = (pj_stun_mapped_addr_attr*) - pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); - - if (xa) - mapped_addr = &xa->sockaddr; - else if (ma) - mapped_addr = &ma->sockaddr; - else { - cand->status = PJNATH_ESTUNNOMAPPEDADDR; - ice_st_perror(comp->ice_st, "STUN Binding request failed", - cand->status); + sess_dec_ref(comp->ice_st); +} + + +/* Callback when TURN client state has changed */ +static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state, + pj_turn_state_t new_state) +{ + pj_ice_strans_comp *comp; + + comp = (pj_ice_strans_comp*) pj_turn_sock_get_user_data(turn_sock); + if (comp == NULL) { + /* Not interested in further state notification once the relay is + * disconnecting. + */ return; } - /* Save IP address for logging */ - pj_ansi_strcpy(ip, pj_inet_ntoa(comp->local_addr.ipv4.sin_addr)); + PJ_LOG(5,(comp->ice_st->obj_name, "TURN client state changed %s --> %s", + pj_turn_state_name(old_state), pj_turn_state_name(new_state))); + + sess_add_ref(comp->ice_st); + + if (new_state == PJ_TURN_STATE_READY) { + pj_turn_session_info rel_info; + char ipaddr[PJ_INET6_ADDRSTRLEN+8]; + pj_ice_sess_cand *cand = NULL; + unsigned i; + + /* Get allocation info */ + pj_turn_sock_get_info(turn_sock, &rel_info); + + /* Find relayed candidate in the component */ + for (i=0; icand_cnt; ++i) { + if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED) { + cand = &comp->cand_list[i]; + break; + } + } + pj_assert(cand != NULL); + + /* Update candidate */ + pj_sockaddr_cp(&cand->addr, &rel_info.relay_addr); + pj_sockaddr_cp(&cand->base_addr, &rel_info.relay_addr); + pj_sockaddr_cp(&cand->rel_addr, &rel_info.mapped_addr); + pj_ice_calc_foundation(comp->ice_st->pool, &cand->foundation, + PJ_ICE_CAND_TYPE_RELAYED, + &rel_info.relay_addr); + cand->status = PJ_SUCCESS; - /* Ignore response if it reports the same address */ - if (comp->local_addr.ipv4.sin_addr.s_addr == mapped_addr->ipv4.sin_addr.s_addr && - comp->local_addr.ipv4.sin_port == mapped_addr->ipv4.sin_port) - { PJ_LOG(4,(comp->ice_st->obj_name, - "Candidate %s:%d is directly connected to Internet, " - "STUN mapped address is ignored", - ip, pj_ntohs(comp->local_addr.ipv4.sin_port))); - return; + "Comp %d: TURN allocation complete, relay address is %s", + comp->comp_id, + pj_sockaddr_print(&rel_info.relay_addr, ipaddr, + sizeof(ipaddr), 3))); + + sess_init_update(comp->ice_st); + + } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) { + pj_turn_session_info info; + + pj_turn_sock_get_info(turn_sock, &info); + + /* Unregister ourself from the TURN relay */ + pj_turn_sock_set_user_data(turn_sock, NULL); + comp->turn_sock = NULL; + + /* Set session to fail if we're still initializing */ + if (old_state < PJ_TURN_STATE_READY) { + sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT, + "TURN relay failed", info.last_status); + } } - PJ_LOG(5,(comp->ice_st->obj_name, - "STUN mapped address for %s:%d is %s:%d", - ip, (int)pj_ntohs(comp->local_addr.ipv4.sin_port), - pj_inet_ntoa(mapped_addr->ipv4.sin_addr), - (int)pj_ntohs(mapped_addr->ipv4.sin_port))); - pj_memcpy(&cand->addr, mapped_addr, sizeof(pj_sockaddr_in)); - cand->status = PJ_SUCCESS; - - /* Set this candidate as the default candidate */ - comp->default_cand = (cand - comp->cand_list); - comp->last_status = PJ_SUCCESS; - - /* We have STUN, so we must start the keep-alive timer */ - start_ka_timer(comp->ice_st); - - /* Notify app that STUN address has changed. */ - if (comp->ice_st->cb.on_addr_change) - (*comp->ice_st->cb.on_addr_change)(comp->ice_st, comp->comp_id, - (cand - comp->cand_list)); + sess_dec_ref(comp->ice_st); } diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c index 21315f3b..97aaeb27 100644 --- a/pjnath/src/pjnath/stun_msg.c +++ b/pjnath/src/pjnath/stun_msg.c @@ -1557,7 +1557,7 @@ PJ_DEF(pj_status_t) pj_stun_unknown_attr_create(pj_pool_t *pool, /* Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. */ PJ_DEF(pj_status_t) pj_stun_msg_add_unknown_attr(pj_pool_t *pool, pj_stun_msg *msg, - unsigned attr_cnt, + pj_size_t attr_cnt, const pj_uint16_t attr_types[]) { pj_stun_unknown_attr *attr = NULL; @@ -1646,7 +1646,7 @@ static void* clone_unknown_attr(pj_pool_t *pool, const void *src) PJ_DEF(pj_status_t) pj_stun_binary_attr_create(pj_pool_t *pool, int attr_type, const pj_uint8_t *data, - unsigned length, + pj_size_t length, pj_stun_binary_attr **p_attr) { pj_stun_binary_attr *attr; @@ -1673,7 +1673,7 @@ PJ_DEF(pj_status_t) pj_stun_msg_add_binary_attr(pj_pool_t *pool, pj_stun_msg *msg, int attr_type, const pj_uint8_t *data, - unsigned length) + pj_size_t length) { pj_stun_binary_attr *attr = NULL; pj_status_t status; @@ -1833,10 +1833,10 @@ PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg, /* * Check that the PDU is potentially a valid STUN message. */ -PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, unsigned pdu_len, +PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len, unsigned options) { - unsigned msg_len; + pj_size_t msg_len; PJ_ASSERT_RETURN(pdu, PJ_EINVAL); @@ -1938,10 +1938,10 @@ PJ_DEF(pj_status_t) pj_stun_msg_create_response(pj_pool_t *pool, */ PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool, const pj_uint8_t *pdu, - unsigned pdu_len, + pj_size_t pdu_len, unsigned options, pj_stun_msg **p_msg, - unsigned *p_parsed_len, + pj_size_t *p_parsed_len, pj_stun_msg **p_response) { @@ -2190,9 +2190,9 @@ static char *print_binary(const pj_uint8_t *data, unsigned data_len) */ PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg, pj_uint8_t *buf, unsigned buf_size, - unsigned options, + pj_size_t options, const pj_str_t *key, - unsigned *p_msg_len) + pj_size_t *p_msg_len) { pj_uint8_t *start = buf; pj_stun_msgint_attr *amsgint = NULL; diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c index 6545c750..6e7d4ef3 100644 --- a/pjnath/src/pjnath/stun_session.c +++ b/pjnath/src/pjnath/stun_session.c @@ -29,14 +29,23 @@ struct pj_stun_session pj_stun_session_cb cb; void *user_data; + pj_atomic_t *busy; + pj_bool_t destroy_request; + pj_bool_t use_fingerprint; + pj_pool_t *rx_pool; + +#if PJ_LOG_MAX_LEVEL >= 5 char dump_buf[1000]; +#endif + unsigned log_flag; pj_stun_auth_type auth_type; pj_stun_auth_cred cred; int auth_retry; pj_str_t next_nonce; + pj_str_t server_realm; pj_str_t srv_name; @@ -79,7 +88,7 @@ static pj_stun_tsx_cb tsx_cb = static pj_status_t tsx_add(pj_stun_session *sess, pj_stun_tx_data *tdata) { - pj_list_push_back(&sess->pending_request_list, tdata); + pj_list_push_front(&sess->pending_request_list, tdata); return PJ_SUCCESS; } @@ -138,11 +147,13 @@ static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx) pj_stun_tx_data *tdata; tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx); + tsx_erase(tdata->sess, tdata); + pj_stun_client_tsx_destroy(tsx); pj_pool_release(tdata->pool); } -static void destroy_tdata(pj_stun_tx_data *tdata) +static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force) { if (tdata->res_timer.id != PJ_FALSE) { pj_timer_heap_cancel(tdata->sess->cfg->timer_heap, @@ -151,14 +162,21 @@ static void destroy_tdata(pj_stun_tx_data *tdata) pj_list_erase(tdata); } - if (tdata->client_tsx) { - pj_time_val delay = {2, 0}; - tsx_erase(tdata->sess, tdata); - pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay); - tdata->client_tsx = NULL; + if (force) { + if (tdata->client_tsx) { + tsx_erase(tdata->sess, tdata); + pj_stun_client_tsx_destroy(tdata->client_tsx); + } + pj_pool_release(tdata->pool); } else { - pj_pool_release(tdata->pool); + if (tdata->client_tsx) { + pj_time_val delay = {2, 0}; + pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay); + + } else { + pj_pool_release(tdata->pool); + } } } @@ -169,7 +187,7 @@ PJ_DEF(void) pj_stun_msg_destroy_tdata( pj_stun_session *sess, pj_stun_tx_data *tdata) { PJ_UNUSED_ARG(sess); - destroy_tdata(tdata); + destroy_tdata(tdata, PJ_FALSE); } @@ -289,6 +307,7 @@ static pj_status_t handle_auth_challenge(pj_stun_session *sess, ea->err_code == PJ_STUN_SC_STALE_NONCE) { const pj_stun_nonce_attr *anonce; + const pj_stun_realm_attr *arealm; pj_stun_tx_data *tdata; unsigned i; pj_status_t status; @@ -316,6 +335,13 @@ static pj_status_t handle_auth_challenge(pj_stun_session *sess, /* Save next_nonce */ pj_strdup(sess->pool, &sess->next_nonce, &anonce->value); + /* Copy the realm from the response */ + arealm = (pj_stun_realm_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_REALM, 0); + if (arealm) { + pj_strdup(sess->pool, &sess->server_realm, &arealm->value); + } + /* Create new request */ status = pj_stun_session_create_req(sess, request->msg->hdr.type, request->msg->hdr.magic, @@ -324,7 +350,8 @@ static pj_status_t handle_auth_challenge(pj_stun_session *sess, return status; /* Duplicate all the attributes in the old request, except - * USERNAME, REALM, M-I, and NONCE + * USERNAME, REALM, M-I, and NONCE, which will be filled in + * later. */ for (i=0; imsg->attr_count; ++i) { const pj_stun_attr_hdr *asrc = request->msg->attr[i]; @@ -373,6 +400,10 @@ static void stun_tsx_on_complete(pj_stun_client_tsx *tsx, tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx); sess = tdata->sess; + /* Lock the session and prevent user from destroying us in the callback */ + pj_atomic_inc(sess->busy); + pj_lock_acquire(sess->lock); + /* Handle authentication challenge */ handle_auth_challenge(sess, tdata, response, src_addr, src_addr_len, ¬ify_user); @@ -387,6 +418,13 @@ static void stun_tsx_on_complete(pj_stun_client_tsx *tsx, */ pj_stun_msg_destroy_tdata(sess, tdata); tdata = NULL; + + pj_lock_release(sess->lock); + + if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) { + pj_stun_session_destroy(sess); + return; + } } static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx, @@ -394,12 +432,27 @@ static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx, pj_size_t pkt_size) { pj_stun_tx_data *tdata; + pj_stun_session *sess; + pj_status_t status; tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx); + sess = tdata->sess; + + /* Lock the session and prevent user from destroying us in the callback */ + pj_atomic_inc(sess->busy); + pj_lock_acquire(sess->lock); + + status = sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt, + pkt_size, tdata->dst_addr, + tdata->addr_len); + pj_lock_release(sess->lock); - return tdata->sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt, - pkt_size, tdata->dst_addr, - tdata->addr_len); + if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) { + pj_stun_session_destroy(sess); + return PJNATH_ESTUNDESTROYED; + } else { + return status; + } } /* **************************************************************************/ @@ -428,11 +481,16 @@ PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_config *cfg, sess->pool = pool; pj_memcpy(&sess->cb, cb, sizeof(*cb)); sess->use_fingerprint = fingerprint; + sess->log_flag = 0xFFFF; sess->srv_name.ptr = (char*) pj_pool_alloc(pool, 32); sess->srv_name.slen = pj_ansi_snprintf(sess->srv_name.ptr, 32, "pj_stun-%s", pj_get_version()); + sess->rx_pool = pj_pool_create(sess->cfg->pf, "name", + PJNATH_POOL_LEN_STUN_TDATA, + PJNATH_POOL_INC_STUN_TDATA, NULL); + pj_list_init(&sess->pending_request_list); pj_list_init(&sess->cached_response_list); @@ -443,6 +501,13 @@ PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_config *cfg, } sess->delete_lock = PJ_TRUE; + status = pj_atomic_create(pool, 0, &sess->busy); + if (status != PJ_SUCCESS) { + pj_lock_destroy(sess->lock); + pj_pool_release(pool); + return status; + } + *p_sess = sess; return PJ_SUCCESS; @@ -453,13 +518,22 @@ PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess) PJ_ASSERT_RETURN(sess, PJ_EINVAL); pj_lock_acquire(sess->lock); + + /* Can't destroy if we're in a callback */ + sess->destroy_request = PJ_TRUE; + if (pj_atomic_get(sess->busy)) { + pj_lock_release(sess->lock); + return PJ_EPENDING; + } + while (!pj_list_empty(&sess->pending_request_list)) { pj_stun_tx_data *tdata = sess->pending_request_list.next; - destroy_tdata(tdata); + destroy_tdata(tdata, PJ_TRUE); } + while (!pj_list_empty(&sess->cached_response_list)) { pj_stun_tx_data *tdata = sess->cached_response_list.next; - destroy_tdata(tdata); + destroy_tdata(tdata, PJ_TRUE); } pj_lock_release(sess->lock); @@ -467,6 +541,11 @@ PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess) pj_lock_destroy(sess->lock); } + if (sess->rx_pool) { + pj_pool_release(sess->rx_pool); + sess->rx_pool = NULL; + } + pj_pool_release(sess->pool); return PJ_SUCCESS; @@ -538,12 +617,19 @@ PJ_DEF(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess, return PJ_SUCCESS; } +PJ_DEF(void) pj_stun_session_set_log( pj_stun_session *sess, + unsigned flags) +{ + PJ_ASSERT_ON_FAIL(sess, return); + sess->log_flag = flags; +} static pj_status_t get_auth(pj_stun_session *sess, pj_stun_tx_data *tdata) { if (sess->cred.type == PJ_STUN_AUTH_CRED_STATIC) { - tdata->auth_info.realm = sess->cred.data.static_cred.realm; + //tdata->auth_info.realm = sess->cred.data.static_cred.realm; + tdata->auth_info.realm = sess->server_realm; tdata->auth_info.username = sess->cred.data.static_cred.username; tdata->auth_info.nonce = sess->cred.data.static_cred.nonce; @@ -633,6 +719,7 @@ PJ_DEF(pj_status_t) pj_stun_session_create_req(pj_stun_session *sess, return status; } tdata->auth_info.nonce = sess->next_nonce; + tdata->auth_info.realm = sess->server_realm; } } else { @@ -714,8 +801,18 @@ PJ_DEF(pj_status_t) pj_stun_session_create_res( pj_stun_session *sess, static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg, unsigned pkt_size, const pj_sockaddr_t *addr) { - char dst_name[80]; + char dst_name[PJ_INET6_ADDRSTRLEN+10]; + if ((PJ_STUN_IS_REQUEST(msg->hdr.type) && + (sess->log_flag & PJ_STUN_SESS_LOG_TX_REQ)==0) || + (PJ_STUN_IS_RESPONSE(msg->hdr.type) && + (sess->log_flag & PJ_STUN_SESS_LOG_TX_RES)==0) || + (PJ_STUN_IS_INDICATION(msg->hdr.type) && + (sess->log_flag & PJ_STUN_SESS_LOG_TX_IND)==0)) + { + return; + } + pj_sockaddr_print(addr, dst_name, sizeof(dst_name), 3); PJ_LOG(5,(SNAME(sess), @@ -749,7 +846,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, tdata->token = token; tdata->retransmit = retransmit; - /* Start locking the session now */ + /* Lock the session and prevent user from destroying us in the callback */ + pj_atomic_inc(sess->busy); pj_lock_acquire(sess->lock); /* Apply options */ @@ -757,9 +855,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, tdata->msg); if (status != PJ_SUCCESS) { pj_stun_msg_destroy_tdata(sess, tdata); - pj_lock_release(sess->lock); LOG_ERR_(sess, "Error applying options", status); - return status; + goto on_return; } /* Encode message */ @@ -769,9 +866,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, &tdata->pkt_size); if (status != PJ_SUCCESS) { pj_stun_msg_destroy_tdata(sess, tdata); - pj_lock_release(sess->lock); LOG_ERR_(sess, "STUN encode() error", status); - return status; + goto on_return; } /* Dump packet */ @@ -797,9 +893,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, tdata->pkt, tdata->pkt_size); if (status != PJ_SUCCESS && status != PJ_EPENDING) { pj_stun_msg_destroy_tdata(sess, tdata); - pj_lock_release(sess->lock); LOG_ERR_(sess, "Error sending STUN request", status); - return status; + goto on_return; } /* Add to pending request list */ @@ -824,10 +919,10 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, &tdata->res_timer, &timeout); if (status != PJ_SUCCESS) { + tdata->res_timer.id = PJ_FALSE; pj_stun_msg_destroy_tdata(sess, tdata); - pj_lock_release(sess->lock); LOG_ERR_(sess, "Error scheduling response timer", status); - return status; + goto on_return; } pj_list_push_back(&sess->cached_response_list, tdata); @@ -838,7 +933,9 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, tdata->pkt_size, server, addr_len); if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pj_stun_msg_destroy_tdata(sess, tdata); LOG_ERR_(sess, "Error sending STUN request", status); + goto on_return; } /* Destroy only when response is not cached*/ @@ -847,8 +944,15 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, } } - +on_return: pj_lock_release(sess->lock); + + /* Check if application has called destroy() in the callback */ + if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) { + pj_stun_session_destroy(sess); + return PJNATH_ESTUNDESTROYED; + } + return status; } @@ -892,6 +996,8 @@ PJ_DEF(pj_status_t) pj_stun_session_cancel_req( pj_stun_session *sess, PJ_ASSERT_RETURN(!notify || notify_status!=PJ_SUCCESS, PJ_EINVAL); PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + /* Lock the session and prevent user from destroying us in the callback */ + pj_atomic_inc(sess->busy); pj_lock_acquire(sess->lock); if (notify) { @@ -903,6 +1009,12 @@ PJ_DEF(pj_status_t) pj_stun_session_cancel_req( pj_stun_session *sess, pj_stun_msg_destroy_tdata(sess, tdata); pj_lock_release(sess->lock); + + if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) { + pj_stun_session_destroy(sess); + return PJNATH_ESTUNDESTROYED; + } + return PJ_SUCCESS; } @@ -917,12 +1029,19 @@ PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess, PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL); + /* Lock the session and prevent user from destroying us in the callback */ + pj_atomic_inc(sess->busy); pj_lock_acquire(sess->lock); status = pj_stun_client_tsx_retransmit(tdata->client_tsx); pj_lock_release(sess->lock); + if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) { + pj_stun_session_destroy(sess); + return PJNATH_ESTUNDESTROYED; + } + return status; } @@ -1165,6 +1284,36 @@ static pj_status_t on_incoming_indication(pj_stun_session *sess, } +/* Print outgoing message to log */ +static void dump_rx_msg(pj_stun_session *sess, const pj_stun_msg *msg, + unsigned pkt_size, const pj_sockaddr_t *addr) +{ + char src_info[PJ_INET6_ADDRSTRLEN+10]; + + if ((PJ_STUN_IS_REQUEST(msg->hdr.type) && + (sess->log_flag & PJ_STUN_SESS_LOG_RX_REQ)==0) || + (PJ_STUN_IS_RESPONSE(msg->hdr.type) && + (sess->log_flag & PJ_STUN_SESS_LOG_RX_RES)==0) || + (PJ_STUN_IS_INDICATION(msg->hdr.type) && + (sess->log_flag & PJ_STUN_SESS_LOG_RX_IND)==0)) + { + return; + } + + pj_sockaddr_print(addr, src_info, sizeof(src_info), 3); + + PJ_LOG(5,(SNAME(sess), + "RX %d bytes STUN message from %s:\n" + "--- begin STUN message ---\n" + "%s" + "--- end of STUN message ---\n", + pkt_size, src_info, + pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf), + NULL))); + +} + +/* Incoming packet */ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess, const void *packet, pj_size_t pkt_size, @@ -1175,47 +1324,34 @@ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess, unsigned src_addr_len) { pj_stun_msg *msg, *response; - pj_pool_t *tmp_pool; - char *dump; pj_status_t status; PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL); - tmp_pool = pj_pool_create(sess->cfg->pf, "tmpstun", - PJNATH_POOL_LEN_STUN_TDATA, - PJNATH_POOL_INC_STUN_TDATA, NULL); - if (!tmp_pool) - return PJ_ENOMEM; + /* Lock the session and prevent user from destroying us in the callback */ + pj_atomic_inc(sess->busy); + pj_lock_acquire(sess->lock); + + /* Reset pool */ + pj_pool_reset(sess->rx_pool); /* Try to parse the message */ - status = pj_stun_msg_decode(tmp_pool, (const pj_uint8_t*)packet, + status = pj_stun_msg_decode(sess->rx_pool, (const pj_uint8_t*)packet, pkt_size, options, &msg, parsed_len, &response); if (status != PJ_SUCCESS) { LOG_ERR_(sess, "STUN msg_decode() error", status); if (response) { - send_response(sess, token, tmp_pool, response, NULL, + send_response(sess, token, sess->rx_pool, response, NULL, PJ_FALSE, src_addr, src_addr_len); } - pj_pool_release(tmp_pool); - return status; + goto on_return; } - dump = (char*) pj_pool_alloc(tmp_pool, PJ_STUN_MAX_PKT_LEN); - - PJ_LOG(5,(SNAME(sess), - "RX STUN message from %s:%d:\n" - "--- begin STUN message ---\n" - "%s" - "--- end of STUN message ---\n", - pj_inet_ntoa(((pj_sockaddr_in*)src_addr)->sin_addr), - pj_ntohs(((pj_sockaddr_in*)src_addr)->sin_port), - pj_stun_msg_dump(msg, dump, PJ_STUN_MAX_PKT_LEN, NULL))); - - pj_lock_acquire(sess->lock); + dump_rx_msg(sess, msg, pkt_size, src_addr); /* For requests, check if we have cached response */ - status = check_cached_response(sess, tmp_pool, msg, + status = check_cached_response(sess, sess->rx_pool, msg, src_addr, src_addr_len); if (status == PJ_SUCCESS) { goto on_return; @@ -1231,13 +1367,13 @@ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess, } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) { - status = on_incoming_request(sess, options, token, tmp_pool, + status = on_incoming_request(sess, options, token, sess->rx_pool, (const pj_uint8_t*) packet, pkt_size, msg, src_addr, src_addr_len); } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) { - status = on_incoming_indication(sess, token, tmp_pool, + status = on_incoming_indication(sess, token, sess->rx_pool, (const pj_uint8_t*) packet, pkt_size, msg, src_addr, src_addr_len); @@ -1249,9 +1385,14 @@ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess, on_return: pj_lock_release(sess->lock); - pj_pool_release(tmp_pool); + /* If we've received destroy request while we're on the callback, + * destroy the session now. + */ + if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) { + pj_stun_session_destroy(sess); + return PJNATH_ESTUNDESTROYED; + } + return status; } - - diff --git a/pjnath/src/pjnath/stun_sock.c b/pjnath/src/pjnath/stun_sock.c new file mode 100644 index 00000000..8ff5a1d6 --- /dev/null +++ b/pjnath/src/pjnath/stun_sock.c @@ -0,0 +1,829 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct pj_stun_sock +{ + char *obj_name; /* Log identification */ + pj_pool_t *pool; /* Pool */ + void *user_data; /* Application user data */ + + int af; /* Address family */ + pj_stun_config stun_cfg; /* STUN config (ioqueue etc)*/ + pj_stun_sock_cb cb; /* Application callbacks */ + + int ka_interval; /* Keep alive interval */ + pj_timer_entry ka_timer; /* Keep alive timer. */ + + pj_sockaddr srv_addr; /* Resolved server addr */ + pj_sockaddr mapped_addr; /* Our public address */ + + pj_dns_async_query *q; /* Pending DNS query */ + pj_sock_t sock_fd; /* Socket descriptor */ + pj_activesock_t *active_sock; /* Active socket object */ + pj_ioqueue_op_key_t send_key; /* Default send key for app */ + pj_ioqueue_op_key_t int_send_key; /* Send key for internal */ + + pj_uint16_t tsx_id[6]; /* .. to match STUN msg */ + pj_stun_session *stun_sess; /* STUN session */ + +}; + +/* + * Prototypes for static functions + */ + +/* This callback is called by the STUN session to send packet */ +static pj_status_t sess_on_send_msg(pj_stun_session *sess, + void *token, + const void *pkt, + pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len); + +/* This callback is called by the STUN session when outgoing transaction + * is complete + */ +static void sess_on_request_complete(pj_stun_session *sess, + pj_status_t status, + void *token, + pj_stun_tx_data *tdata, + const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len); +/* DNS resolver callback */ +static void dns_srv_resolver_cb(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec); + +/* Start sending STUN Binding request */ +static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock); + +/* Callback from active socket when incoming packet is received */ +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); + +/* Callback from active socket about send status */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, + pj_ioqueue_op_key_t *send_key, + pj_ssize_t sent); + +/* Schedule keep-alive timer */ +static void start_ka_timer(pj_stun_sock *stun_sock); + +/* Keep-alive timer callback */ +static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te); + +#define INTERNAL_MSG_TOKEN (void*)1 + + +/* + * Retrieve the name representing the specified operation. + */ +PJ_DEF(const char*) pj_stun_sock_op_name(pj_stun_sock_op op) +{ + const char *names[] = { + "?", + "DNS resolution", + "STUN Binding request", + "Keep-alive" + }; + + return op <= PJ_STUN_SOCK_KEEP_ALIVE_OP ? names[op] : "?"; +}; + + +/* + * Initialize the STUN transport setting with its default values. + */ +PJ_DEF(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->max_pkt_size = PJ_STUN_SOCK_PKT_LEN; + cfg->async_cnt = 1; + cfg->ka_interval = PJ_STUN_KEEP_ALIVE_SEC; +} + + +/* Check that configuration setting is valid */ +static pj_bool_t pj_stun_sock_cfg_is_valid(const pj_stun_sock_cfg *cfg) +{ + return cfg->max_pkt_size > 1 && cfg->async_cnt >= 1; +} + +/* + * Create the STUN transport using the specified configuration. + */ +PJ_DEF(pj_status_t) pj_stun_sock_create( pj_stun_config *stun_cfg, + const char *name, + int af, + const pj_stun_sock_cb *cb, + const pj_stun_sock_cfg *cfg, + void *user_data, + pj_stun_sock **p_stun_sock) +{ + pj_pool_t *pool; + pj_stun_sock *stun_sock; + pj_stun_sock_cfg default_cfg; + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && cb && p_stun_sock, PJ_EINVAL); + PJ_ASSERT_RETURN(af==pj_AF_INET()||af==pj_AF_INET6(), PJ_EAFNOTSUP); + PJ_ASSERT_RETURN(!cfg || pj_stun_sock_cfg_is_valid(cfg), PJ_EINVAL); + PJ_ASSERT_RETURN(cb->on_status, PJ_EINVAL); + + status = pj_stun_config_check_valid(stun_cfg); + if (status != PJ_SUCCESS) + return status; + + if (name == NULL) + name = "stuntp%p"; + + if (cfg == NULL) { + pj_stun_sock_cfg_default(&default_cfg); + cfg = &default_cfg; + } + + + /* Create structure */ + pool = pj_pool_create(stun_cfg->pf, name, 256, 512, NULL); + stun_sock = PJ_POOL_ZALLOC_T(pool, pj_stun_sock); + stun_sock->pool = pool; + stun_sock->obj_name = pool->obj_name; + stun_sock->user_data = user_data; + stun_sock->af = af; + stun_sock->sock_fd = PJ_INVALID_SOCKET; + pj_memcpy(&stun_sock->stun_cfg, stun_cfg, sizeof(*stun_cfg)); + pj_memcpy(&stun_sock->cb, cb, sizeof(*cb)); + + stun_sock->ka_interval = cfg->ka_interval; + if (stun_sock->ka_interval == 0) + stun_sock->ka_interval = PJ_STUN_KEEP_ALIVE_SEC; + + /* Create socket and bind socket */ + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &stun_sock->sock_fd); + if (status != PJ_SUCCESS) + goto on_error; + + if (pj_sockaddr_has_addr(&cfg->bound_addr)) { + status = pj_sock_bind(stun_sock->sock_fd, &cfg->bound_addr, + pj_sockaddr_get_len(&cfg->bound_addr)); + } else { + pj_sockaddr bound_addr; + + pj_sockaddr_init(af, &bound_addr, NULL, 0); + status = pj_sock_bind(stun_sock->sock_fd, &bound_addr, + pj_sockaddr_get_len(&bound_addr)); + } + + if (status != PJ_SUCCESS) + goto on_error; + + /* Create more useful information string about this transport */ +#if 0 + { + pj_sockaddr bound_addr; + int addr_len = sizeof(bound_addr); + + status = pj_sock_getsockname(stun_sock->sock_fd, &bound_addr, + &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + stun_sock->info = pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+10); + pj_sockaddr_print(&bound_addr, stun_sock->info, + PJ_INET6_ADDRSTRLEN, 3); + } +#endif + + /* Init active socket configuration */ + { + pj_activesock_cfg activesock_cfg; + pj_activesock_cb activesock_cb; + + pj_activesock_cfg_default(&activesock_cfg); + activesock_cfg.async_cnt = cfg->async_cnt; + activesock_cfg.concurrency = 0; + + /* Create the active socket */ + pj_bzero(&activesock_cb, sizeof(activesock_cb)); + activesock_cb.on_data_recvfrom = &on_data_recvfrom; + activesock_cb.on_data_sent = &on_data_sent; + status = pj_activesock_create(pool, stun_sock->sock_fd, + pj_SOCK_DGRAM(), + &activesock_cfg, stun_cfg->ioqueue, + &activesock_cb, stun_sock, + &stun_sock->active_sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start asynchronous read operations */ + status = pj_activesock_start_recvfrom(stun_sock->active_sock, pool, + cfg->max_pkt_size, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init send keys */ + pj_ioqueue_op_key_init(&stun_sock->send_key, + sizeof(stun_sock->send_key)); + pj_ioqueue_op_key_init(&stun_sock->int_send_key, + sizeof(stun_sock->int_send_key)); + } + + /* Create STUN session */ + { + pj_stun_session_cb sess_cb; + + pj_bzero(&sess_cb, sizeof(sess_cb)); + sess_cb.on_request_complete = &sess_on_request_complete; + sess_cb.on_send_msg = &sess_on_send_msg; + status = pj_stun_session_create(&stun_sock->stun_cfg, + stun_sock->obj_name, + &sess_cb, PJ_FALSE, + &stun_sock->stun_sess); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Associate us with the STUN session */ + pj_stun_session_set_user_data(stun_sock->stun_sess, stun_sock); + + /* Initialize random numbers to be used as STUN transaction ID for + * outgoing Binding request. We use the 80bit number to distinguish + * STUN messages we sent with STUN messages that the application sends. + * The last 16bit value in the array is a counter. + */ + for (i=0; itsx_id); ++i) { + stun_sock->tsx_id[i] = (pj_uint16_t) pj_rand(); + } + stun_sock->tsx_id[5] = 0; + + + /* Init timer entry */ + stun_sock->ka_timer.cb = &ka_timer_cb; + stun_sock->ka_timer.user_data = stun_sock; + + /* Done */ + *p_stun_sock = stun_sock; + return PJ_SUCCESS; + +on_error: + pj_stun_sock_destroy(stun_sock); + return status; +} + +/* Start socket. */ +PJ_DEF(pj_status_t) pj_stun_sock_start( pj_stun_sock *stun_sock, + const pj_str_t *domain, + pj_uint16_t default_port, + pj_dns_resolver *resolver) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && domain && default_port, PJ_EINVAL); + + /* Check whether the domain contains IP address */ + stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)stun_sock->af; + status = pj_inet_pton(stun_sock->af, domain, + pj_sockaddr_get_addr(&stun_sock->srv_addr)); + if (status != PJ_SUCCESS) { + stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)0; + } + + /* If resolver is set, try to resolve with DNS SRV first. It + * will fallback to DNS A/AAAA when no SRV record is found. + */ + if (status != PJ_SUCCESS && resolver) { + const pj_str_t res_name = pj_str("_stun._udp."); + unsigned opt; + + pj_assert(stun_sock->q == NULL); + + opt = PJ_DNS_SRV_FALLBACK_A; + if (stun_sock->af == pj_AF_INET6()) { + opt |= (PJ_DNS_SRV_RESOLVE_AAAA | PJ_DNS_SRV_FALLBACK_AAAA); + } + + status = pj_dns_srv_resolve(domain, &res_name, default_port, + stun_sock->pool, resolver, opt, + stun_sock, &dns_srv_resolver_cb, + &stun_sock->q); + + /* Processing will resume when the DNS SRV callback is called */ + return status; + + } else { + + if (status != PJ_SUCCESS) { + pj_addrinfo ai; + unsigned cnt = 1; + + status = pj_getaddrinfo(stun_sock->af, domain, &cnt, &ai); + if (status != PJ_SUCCESS) + return status; + + pj_sockaddr_cp(&stun_sock->srv_addr, &ai.ai_addr); + } + + pj_sockaddr_set_port(&stun_sock->srv_addr, (pj_uint16_t)default_port); + + /* Start sending Binding request */ + return get_mapped_addr(stun_sock); + } +} + +/* Destroy */ +PJ_DEF(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *stun_sock) +{ + if (stun_sock->q) { + pj_dns_resolver_cancel_query(stun_sock->q, PJ_FALSE); + stun_sock->q = NULL; + } + + /* Destroy the active socket first just in case we'll get + * stray callback. + */ + if (stun_sock->active_sock != NULL) { + pj_activesock_close(stun_sock->active_sock); + stun_sock->active_sock = NULL; + stun_sock->sock_fd = PJ_INVALID_SOCKET; + } else if (stun_sock->sock_fd != PJ_INVALID_SOCKET) { + pj_sock_close(stun_sock->sock_fd); + stun_sock->sock_fd = PJ_INVALID_SOCKET; + } + + if (stun_sock->ka_timer.id != 0) { + pj_timer_heap_cancel(stun_sock->stun_cfg.timer_heap, + &stun_sock->ka_timer); + stun_sock->ka_timer.id = 0; + } + + if (stun_sock->stun_sess) { + pj_stun_session_destroy(stun_sock->stun_sess); + stun_sock->stun_sess = NULL; + } + + if (stun_sock->pool) { + pj_pool_t *pool = stun_sock->pool; + stun_sock->pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + +/* Associate user data */ +PJ_DEF(pj_status_t) pj_stun_sock_set_user_data( pj_stun_sock *stun_sock, + void *user_data) +{ + PJ_ASSERT_RETURN(stun_sock, PJ_EINVAL); + stun_sock->user_data = user_data; + return PJ_SUCCESS; +} + + +/* Get user data */ +PJ_DEF(void*) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock) +{ + PJ_ASSERT_RETURN(stun_sock, NULL); + return stun_sock->user_data; +} + +/* Notify application that session has failed */ +static pj_bool_t sess_fail(pj_stun_sock *stun_sock, + pj_stun_sock_op op, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_bool_t ret; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(stun_sock->obj_name, "Session failed because %s failed: %s", + pj_stun_sock_op_name(op), errmsg)); + + ret = (*stun_sock->cb.on_status)(stun_sock, op, status); + + return ret; +} + +/* DNS resolver callback */ +static void dns_srv_resolver_cb(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + pj_stun_sock *stun_sock = (pj_stun_sock*) user_data; + + /* Clear query */ + stun_sock->q = NULL; + + /* Handle error */ + if (status != PJ_SUCCESS) { + sess_fail(stun_sock, PJ_STUN_SOCK_DNS_OP, status); + return; + } + + pj_assert(rec->count); + pj_assert(rec->entry[0].server.addr_count); + + PJ_TODO(SUPPORT_IPV6_IN_RESOLVER); + pj_assert(stun_sock->af == pj_AF_INET()); + + /* Set the address */ + pj_sockaddr_in_init(&stun_sock->srv_addr.ipv4, NULL, + rec->entry[0].port); + stun_sock->srv_addr.ipv4.sin_addr = rec->entry[0].server.addr[0]; + + /* Start sending Binding request */ + get_mapped_addr(stun_sock); +} + + +/* Start sending STUN Binding request */ +static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + /* Increment request counter and create STUN Binding request */ + ++stun_sock->tsx_id[5]; + status = pj_stun_session_create_req(stun_sock->stun_sess, + PJ_STUN_BINDING_REQUEST, + PJ_STUN_MAGIC, + (const pj_uint8_t*)stun_sock->tsx_id, + &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Send request */ + status=pj_stun_session_send_msg(stun_sock->stun_sess, INTERNAL_MSG_TOKEN, + PJ_FALSE, PJ_TRUE, &stun_sock->srv_addr, + pj_sockaddr_get_len(&stun_sock->srv_addr), + tdata); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + sess_fail(stun_sock, PJ_STUN_SOCK_BINDING_OP, status); + return status; +} + +/* Get info */ +PJ_DEF(pj_status_t) pj_stun_sock_get_info( pj_stun_sock *stun_sock, + pj_stun_sock_info *info) +{ + int addr_len; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_sock && info, PJ_EINVAL); + + /* Copy STUN server address and mapped address */ + pj_memcpy(&info->srv_addr, &stun_sock->srv_addr, + sizeof(pj_sockaddr)); + pj_memcpy(&info->mapped_addr, &stun_sock->mapped_addr, + sizeof(pj_sockaddr)); + + /* Retrieve bound address */ + addr_len = sizeof(info->bound_addr); + status = pj_sock_getsockname(stun_sock->sock_fd, &info->bound_addr, + &addr_len); + if (status != PJ_SUCCESS) + return status; + + /* If socket is bound to a specific interface, then only put that + * interface in the alias list. Otherwise query all the interfaces + * in the host. + */ + if (pj_sockaddr_has_addr(&info->bound_addr)) { + info->alias_cnt = 1; + pj_sockaddr_cp(&info->aliases[0], &info->bound_addr); + } else { + unsigned i; + + /* Enum all IP interfaces in the host */ + info->alias_cnt = PJ_ARRAY_SIZE(info->aliases); + status = pj_enum_ip_interface(stun_sock->af, &info->alias_cnt, + info->aliases); + if (status != PJ_SUCCESS) + return status; + + /* Set the port number for each address. + */ + if (stun_sock->af == pj_AF_INET()) { + for (i=0; ialias_cnt; ++i) { + pj_sockaddr_set_port(&info->aliases[i], + pj_sockaddr_get_port(&info->bound_addr)); + } + } + } + + return PJ_SUCCESS; +} + +/* Send application data */ +PJ_DEF(pj_status_t) pj_stun_sock_sendto( pj_stun_sock *stun_sock, + pj_ioqueue_op_key_t *send_key, + const void *pkt, + unsigned pkt_len, + unsigned flag, + const pj_sockaddr_t *dst_addr, + unsigned addr_len) +{ + pj_ssize_t size; + PJ_ASSERT_RETURN(stun_sock && pkt && dst_addr && addr_len, PJ_EINVAL); + + if (send_key==NULL) + send_key = &stun_sock->send_key; + + size = pkt_len; + return pj_activesock_sendto(stun_sock->active_sock, send_key, + pkt, &size, flag, dst_addr, addr_len); +} + +/* This callback is called by the STUN session to send packet */ +static pj_status_t sess_on_send_msg(pj_stun_session *sess, + void *token, + const void *pkt, + pj_size_t pkt_size, + const pj_sockaddr_t *dst_addr, + unsigned addr_len) +{ + pj_stun_sock *stun_sock; + pj_ssize_t size; + + stun_sock = (pj_stun_sock *) pj_stun_session_get_user_data(sess); + + pj_assert(token==INTERNAL_MSG_TOKEN); + PJ_UNUSED_ARG(token); + + size = pkt_size; + return pj_activesock_sendto(stun_sock->active_sock, + &stun_sock->int_send_key, + pkt, &size, 0, dst_addr, addr_len); +} + +/* This callback is called by the STUN session when outgoing transaction + * is complete + */ +static void sess_on_request_complete(pj_stun_session *sess, + pj_status_t status, + void *token, + pj_stun_tx_data *tdata, + const pj_stun_msg *response, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_sock *stun_sock; + const pj_stun_sockaddr_attr *mapped_attr; + pj_stun_sock_op op; + pj_bool_t mapped_changed; + pj_bool_t resched = PJ_TRUE; + + stun_sock = (pj_stun_sock *) pj_stun_session_get_user_data(sess); + + PJ_UNUSED_ARG(tdata); + PJ_UNUSED_ARG(token); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + /* Check if this is a keep-alive or the first Binding request */ + if (pj_sockaddr_has_addr(&stun_sock->mapped_addr)) + op = PJ_STUN_SOCK_KEEP_ALIVE_OP; + else + op = PJ_STUN_SOCK_BINDING_OP; + + /* Handle failure */ + if (status != PJ_SUCCESS) { + resched = sess_fail(stun_sock, op, status); + goto on_return; + } + + /* Get XOR-MAPPED-ADDRESS, or MAPPED-ADDRESS when XOR-MAPPED-ADDRESS + * doesn't exist. + */ + mapped_attr = (const pj_stun_sockaddr_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, + 0); + if (mapped_attr==NULL) { + mapped_attr = (const pj_stun_sockaddr_attr*) + pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, + 0); + } + + if (mapped_attr == NULL) { + resched = sess_fail(stun_sock, op, PJNATH_ESTUNNOMAPPEDADDR); + goto on_return; + } + + /* Determine if mapped address has changed, and save the new mapped + * address and call callback if so + */ + mapped_changed = !pj_sockaddr_has_addr(&stun_sock->mapped_addr) || + pj_sockaddr_cmp(&stun_sock->mapped_addr, + &mapped_attr->sockaddr) != 0; + if (mapped_changed) { + /* Print mapped adress */ + { + char addrinfo[PJ_INET6_ADDRSTRLEN+10]; + PJ_LOG(4,(stun_sock->obj_name, + "STUN mapped address found/changed: %s", + pj_sockaddr_print(&mapped_attr->sockaddr, + addrinfo, sizeof(addrinfo), 3))); + } + + pj_sockaddr_cp(&stun_sock->mapped_addr, &mapped_attr->sockaddr); + + resched = (*stun_sock->cb.on_status)(stun_sock, op, PJ_SUCCESS); + + goto on_return; + } + +on_return: + /* Start/restart keep-alive timer */ + if (resched) + start_ka_timer(stun_sock); +} + +/* Schedule keep-alive timer */ +static void start_ka_timer(pj_stun_sock *stun_sock) +{ + if (stun_sock->ka_timer.id != 0) { + pj_timer_heap_cancel(stun_sock->stun_cfg.timer_heap, + &stun_sock->ka_timer); + stun_sock->ka_timer.id = 0; + } + + pj_assert(stun_sock->ka_interval != 0); + if (stun_sock->ka_interval > 0) { + pj_time_val delay; + + delay.sec = stun_sock->ka_interval; + delay.msec = 0; + + if (pj_timer_heap_schedule(stun_sock->stun_cfg.timer_heap, + &stun_sock->ka_timer, + &delay) == PJ_SUCCESS) + { + stun_sock->ka_timer.id = PJ_TRUE; + } + } +} + +/* Keep-alive timer callback */ +static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pj_stun_sock *stun_sock; + + stun_sock = (pj_stun_sock *) te->user_data; + + PJ_UNUSED_ARG(th); + + /* Time to send STUN Binding request */ + if (get_mapped_addr(stun_sock) != PJ_SUCCESS) + return; + + /* Next keep-alive timer will be scheduled once the request + * is complete. + */ +} + +/* Callback from active socket when incoming packet is received */ +static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + pj_stun_sock *stun_sock; + pj_stun_msg_hdr *hdr; + pj_uint16_t type; + + stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock); + + /* Log socket error */ + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(stun_sock->obj_name, "recvfrom() error: %s", errmsg)); + return PJ_TRUE; + } + + /* Check that this is STUN message */ + status = pj_stun_msg_check((const pj_uint8_t*)data, size, + PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET); + if (status != PJ_SUCCESS) { + /* Not STUN -- give it to application */ + goto process_app_data; + } + + /* Treat packet as STUN header and copy the STUN message type. + * We don't want to access the type directly from the header + * since it may not be properly aligned. + */ + hdr = (pj_stun_msg_hdr*) data; + pj_memcpy(&type, &hdr->type, 2); + type = pj_ntohs(type); + + /* If the packet is a STUN Binding response and part of the + * transaction ID matches our internal ID, then this is + * our internal STUN message (Binding request or keep alive). + * Give it to our STUN session. + */ + if (!PJ_STUN_IS_RESPONSE(type) || + PJ_STUN_GET_METHOD(type) != PJ_STUN_BINDING_METHOD || + pj_memcmp(hdr->tsx_id, stun_sock->tsx_id, 10) != 0) + { + /* Not STUN Binding response, or STUN transaction ID mismatch. + * This is not our message too -- give it to application. + */ + goto process_app_data; + } + + /* This is our STUN Binding response. Give it to the STUN session */ + status = pj_stun_session_on_rx_pkt(stun_sock->stun_sess, data, size, + PJ_STUN_IS_DATAGRAM, NULL, NULL, + src_addr, addr_len); + return status!=PJNATH_ESTUNDESTROYED ? PJ_TRUE : PJ_FALSE; + +process_app_data: + if (stun_sock->cb.on_rx_data) { + pj_bool_t ret; + + ret = (*stun_sock->cb.on_rx_data)(stun_sock, data, size, + src_addr, addr_len); + return ret; + } + + return PJ_TRUE; +} + +/* Callback from active socket about send status */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, + pj_ioqueue_op_key_t *send_key, + pj_ssize_t sent) +{ + pj_stun_sock *stun_sock; + + stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock); + + /* Don't report to callback if this is internal message */ + if (send_key == &stun_sock->int_send_key) { + return PJ_TRUE; + } + + /* Report to callback */ + if (stun_sock->cb.on_data_sent) { + pj_bool_t ret; + + /* If app gives NULL send_key in sendto() function, then give + * NULL in the callback too + */ + if (send_key == &stun_sock->send_key) + send_key = NULL; + + /* Call callback */ + ret = (*stun_sock->cb.on_data_sent)(stun_sock, send_key, sent); + + return ret; + } + + return PJ_TRUE; +} + diff --git a/pjnath/src/pjnath/stun_transaction.c b/pjnath/src/pjnath/stun_transaction.c index ebee152a..1242dfe7 100644 --- a/pjnath/src/pjnath/stun_transaction.c +++ b/pjnath/src/pjnath/stun_transaction.c @@ -31,16 +31,17 @@ struct pj_stun_client_tsx { char obj_name[PJ_MAX_OBJ_NAME]; - pj_stun_config *cfg; pj_stun_tsx_cb cb; void *user_data; pj_bool_t complete; pj_bool_t require_retransmit; + unsigned rto_msec; pj_timer_entry retransmit_timer; unsigned transmit_count; pj_time_val retransmit_time; + pj_timer_heap_t *timer_heap; pj_timer_entry destroy_timer; @@ -70,7 +71,8 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_create(pj_stun_config *cfg, PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL); tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx); - tsx->cfg = cfg; + tsx->rto_msec = cfg->rto_msec; + tsx->timer_heap = cfg->timer_heap; pj_memcpy(&tsx->cb, cb, sizeof(*cb)); tsx->retransmit_timer.cb = &retransmit_timer_callback; @@ -99,22 +101,23 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy( /* Cancel previously registered timer */ if (tsx->destroy_timer.id != 0) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->destroy_timer); + pj_timer_heap_cancel(tsx->timer_heap, &tsx->destroy_timer); tsx->destroy_timer.id = 0; } /* Stop retransmission, just in case */ if (tsx->retransmit_timer.id != 0) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer); + pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer); tsx->retransmit_timer.id = 0; } - status = pj_timer_heap_schedule(tsx->cfg->timer_heap, + status = pj_timer_heap_schedule(tsx->timer_heap, &tsx->destroy_timer, delay); if (status != PJ_SUCCESS) return status; tsx->destroy_timer.id = TIMER_ACTIVE; + tsx->cb.on_complete = NULL; return PJ_SUCCESS; } @@ -128,11 +131,11 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx) PJ_ASSERT_RETURN(tsx, PJ_EINVAL); if (tsx->retransmit_timer.id != 0) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer); + pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer); tsx->retransmit_timer.id = 0; } if (tsx->destroy_timer.id != 0) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->destroy_timer); + pj_timer_heap_cancel(tsx->timer_heap, &tsx->destroy_timer); tsx->destroy_timer.id = 0; } @@ -186,7 +189,7 @@ static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx) /* Calculate retransmit/timeout delay */ if (tsx->transmit_count == 0) { tsx->retransmit_time.sec = 0; - tsx->retransmit_time.msec = tsx->cfg->rto_msec; + tsx->retransmit_time.msec = tsx->rto_msec; } else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT-1) { unsigned msec; @@ -205,7 +208,7 @@ static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx) * cancel it (as opposed to when schedule_timer() failed we cannot * cancel transmission). */; - status = pj_timer_heap_schedule(tsx->cfg->timer_heap, + status = pj_timer_heap_schedule(tsx->timer_heap, &tsx->retransmit_timer, &tsx->retransmit_time); if (status != PJ_SUCCESS) { @@ -223,9 +226,12 @@ static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx) /* Send message */ status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size); - if (status != PJ_SUCCESS) { + + if (status == PJNATH_ESTUNDESTROYED) { + /* We've been destroyed, don't access the object. */ + } else if (status != PJ_SUCCESS) { if (tsx->retransmit_timer.id != 0) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, + pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer); tsx->retransmit_timer.id = 0; } @@ -279,12 +285,15 @@ static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0); } } + /* We might have been destroyed, don't try to access the object */ return; } tsx->retransmit_timer.id = 0; status = tsx_transmit_msg(tsx); - if (status != PJ_SUCCESS) { + if (status == PJNATH_ESTUNDESTROYED) { + /* We've been destroyed, don't try to access the object */ + } else if (status != PJ_SUCCESS) { tsx->retransmit_timer.id = 0; if (!tsx->complete) { tsx->complete = PJ_TRUE; @@ -292,6 +301,7 @@ static void retransmit_timer_callback(pj_timer_heap_t *timer_heap, tsx->cb.on_complete(tsx, status, NULL, NULL, 0); } } + /* We might have been destroyed, don't try to access the object */ } } @@ -305,7 +315,7 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx) } if (tsx->retransmit_timer.id != 0) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer); + pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer); tsx->retransmit_timer.id = 0; } @@ -351,7 +361,7 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, * We can cancel retransmit timer now. */ if (tsx->retransmit_timer.id) { - pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer); + pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer); tsx->retransmit_timer.id = 0; } @@ -384,6 +394,7 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx, if (tsx->cb.on_complete) { tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len); } + /* We might have been destroyed, don't try to access the object */ } return PJ_SUCCESS; diff --git a/pjnath/src/pjnath/turn_session.c b/pjnath/src/pjnath/turn_session.c index 6c662d66..0176e139 100644 --- a/pjnath/src/pjnath/turn_session.c +++ b/pjnath/src/pjnath/turn_session.c @@ -29,8 +29,9 @@ #include #include -#define MAX_SRV_CNT 4 -#define REFRESH_SEC_BEFORE 60 +#define PJ_TURN_CHANNEL_MIN 0x4000 +#define PJ_TURN_CHANNEL_MAX 0xFFFE /* inclusive */ +#define PJ_TURN_PEER_HTABLE_SIZE 8 static const char *state_names[] = { @@ -66,11 +67,13 @@ struct pj_turn_session const char *obj_name; pj_turn_session_cb cb; void *user_data; + pj_stun_config stun_cfg; pj_lock_t *lock; int busy; pj_turn_state_t state; + pj_status_t last_status; pj_bool_t pending_destroy; pj_bool_t destroy_notified; @@ -87,7 +90,7 @@ struct pj_turn_session pj_uint16_t default_port; pj_uint16_t af; - pj_turn_tp_type tp_type; + pj_turn_tp_type conn_type; pj_uint16_t srv_addr_cnt; pj_sockaddr *srv_addr_list; pj_sockaddr *srv_addr; @@ -95,6 +98,7 @@ struct pj_turn_session pj_bool_t pending_alloc; pj_turn_alloc_param alloc_param; + pj_sockaddr mapped_addr; pj_sockaddr relay_addr; pj_hash_table_t *peer_table; @@ -176,13 +180,13 @@ PJ_DEF(const char*) pj_turn_state_name(pj_turn_state_t state) /* * Create TURN client session. */ -PJ_DEF(pj_status_t) pj_turn_session_create( pj_stun_config *cfg, +PJ_DEF(pj_status_t) pj_turn_session_create( const pj_stun_config *cfg, const char *name, int af, - pj_turn_tp_type tp_type, + pj_turn_tp_type conn_type, const pj_turn_session_cb *cb, - void *user_data, unsigned options, + void *user_data, pj_turn_session **p_sess) { pj_pool_t *pool; @@ -206,11 +210,14 @@ PJ_DEF(pj_status_t) pj_turn_session_create( pj_stun_config *cfg, sess->obj_name = pool->obj_name; sess->timer_heap = cfg->timer_heap; sess->af = (pj_uint16_t)af; - sess->tp_type = tp_type; + sess->conn_type = conn_type; sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; sess->user_data = user_data; sess->next_ch = PJ_TURN_CHANNEL_MIN; + /* Copy STUN session */ + pj_memcpy(&sess->stun_cfg, cfg, sizeof(pj_stun_config)); + /* Copy callback */ pj_memcpy(&sess->cb, cb, sizeof(*cb)); @@ -233,8 +240,8 @@ PJ_DEF(pj_status_t) pj_turn_session_create( pj_stun_config *cfg, stun_cb.on_send_msg = &stun_on_send_msg; stun_cb.on_request_complete = &stun_on_request_complete; stun_cb.on_rx_indication = &stun_on_rx_indication; - status = pj_stun_session_create(cfg, sess->obj_name, &stun_cb, PJ_FALSE, - &sess->stun); + status = pj_stun_session_create(&sess->stun_cfg, sess->obj_name, &stun_cb, + PJ_FALSE, &sess->stun); if (status != PJ_SUCCESS) { do_destroy(sess); return status; @@ -333,9 +340,10 @@ static void sess_shutdown(pj_turn_session *sess, case PJ_TURN_STATE_NULL: break; case PJ_TURN_STATE_RESOLVING: - pj_assert(sess->dns_async != NULL); - pj_dns_resolver_cancel_query(sess->dns_async, PJ_FALSE); - sess->dns_async = NULL; + if (sess->dns_async != NULL) { + pj_dns_resolver_cancel_query(sess->dns_async, PJ_FALSE); + sess->dns_async = NULL; + } break; case PJ_TURN_STATE_RESOLVED: break; @@ -365,13 +373,13 @@ static void sess_shutdown(pj_turn_session *sess, /* Schedule destroy */ pj_time_val delay = {0, 0}; + set_state(sess, PJ_TURN_STATE_DESTROYING); + if (sess->timer.id != TIMER_NONE) { pj_timer_heap_cancel(sess->timer_heap, &sess->timer); sess->timer.id = TIMER_NONE; } - set_state(sess, PJ_TURN_STATE_DESTROYING); - sess->timer.id = TIMER_DESTROY; pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay); } @@ -400,6 +408,8 @@ PJ_DEF(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess) */ PJ_DEF(pj_status_t) pj_turn_session_destroy( pj_turn_session *sess) { + PJ_ASSERT_RETURN(sess, PJ_EINVAL); + set_state(sess, PJ_TURN_STATE_DEALLOCATED); sess_shutdown(sess, PJ_SUCCESS); return PJ_SUCCESS; @@ -419,15 +429,19 @@ PJ_DEF(pj_status_t) pj_turn_session_get_info( pj_turn_session *sess, pj_gettimeofday(&now); info->state = sess->state; - info->tp_type = sess->tp_type; + info->conn_type = sess->conn_type; info->lifetime = sess->expiry.sec - now.sec; + info->last_status = sess->last_status; if (sess->srv_addr) pj_memcpy(&info->server, sess->srv_addr, sizeof(info->server)); else pj_bzero(&info->server, sizeof(info->server)); - pj_memcpy(&info->relay_addr, &sess->relay_addr, sizeof(sess->relay_addr)); + pj_memcpy(&info->mapped_addr, &sess->mapped_addr, + sizeof(sess->mapped_addr)); + pj_memcpy(&info->relay_addr, &sess->relay_addr, + sizeof(sess->relay_addr)); return PJ_SUCCESS; } @@ -453,6 +467,19 @@ PJ_DEF(void*) pj_turn_session_get_user_data(pj_turn_session *sess) } +/* + * Configure message logging. By default all flags are enabled. + * + * @param sess The TURN client session. + * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag + */ +PJ_DEF(void) pj_turn_session_set_log( pj_turn_session *sess, + unsigned flags) +{ + pj_stun_session_set_log(sess->stun, flags); +} + + /** * Set the server or domain name of the server. */ @@ -461,6 +488,8 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess, int default_port, pj_dns_resolver *resolver) { + pj_sockaddr tmp_addr; + pj_bool_t is_ip_addr; pj_status_t status; PJ_ASSERT_RETURN(sess && domain, PJ_EINVAL); @@ -468,14 +497,20 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess, pj_lock_acquire(sess->lock); - if (resolver) { + /* See if "domain" contains just IP address */ + tmp_addr.addr.sa_family = sess->af; + status = pj_inet_pton(sess->af, domain, + pj_sockaddr_get_addr(&tmp_addr)); + is_ip_addr = (status == PJ_SUCCESS); + + if (!is_ip_addr && resolver) { /* Resolve with DNS SRV resolution, and fallback to DNS A resolution * if default_port is specified. */ unsigned opt = 0; pj_str_t res_name; - switch (sess->tp_type) { + switch (sess->conn_type) { case PJ_TURN_TP_UDP: res_name = pj_str("_turn._udp."); break; @@ -501,6 +536,12 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess, (int)domain->slen, domain->ptr)); set_state(sess, PJ_TURN_STATE_RESOLVING); + /* User may have destroyed us in the callback */ + if (sess->state != PJ_TURN_STATE_RESOLVING) { + status = PJ_ECANCELLED; + goto on_return; + } + status = pj_dns_srv_resolve(domain, &res_name, default_port, sess->pool, resolver, opt, sess, &dns_srv_resolver_cb, &sess->dns_async); @@ -520,12 +561,19 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess, PJ_ASSERT_RETURN(default_port>0 && default_port<65536, PJ_EINVAL); sess->default_port = (pj_uint16_t)default_port; - cnt = MAX_SRV_CNT; + cnt = PJ_TURN_MAX_DNS_SRV_CNT; ai = (pj_addrinfo*) pj_pool_calloc(sess->pool, cnt, sizeof(pj_addrinfo)); PJ_LOG(5,(sess->obj_name, "Resolving %.*s with DNS A", (int)domain->slen, domain->ptr)); + set_state(sess, PJ_TURN_STATE_RESOLVING); + + /* User may have destroyed us in the callback */ + if (sess->state != PJ_TURN_STATE_RESOLVING) { + status = PJ_ECANCELLED; + goto on_return; + } status = pj_getaddrinfo(sess->af, domain, &cnt, ai); if (status != PJ_SUCCESS) @@ -636,7 +684,7 @@ PJ_DEF(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess, /* Send request */ set_state(sess, PJ_TURN_STATE_ALLOCATING); - retransmit = (sess->tp_type == PJ_TURN_TP_UDP); + retransmit = (sess->conn_type == PJ_TURN_TP_UDP); status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, retransmit, sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), @@ -681,7 +729,7 @@ static void send_refresh(pj_turn_session *sess, int lifetime) } status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, - (sess->tp_type==PJ_TURN_TP_UDP), + (sess->conn_type==PJ_TURN_TP_UDP), sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), tdata); @@ -833,7 +881,7 @@ PJ_DEF(pj_status_t) pj_turn_session_bind_channel(pj_turn_session *sess, * for future reference when we receive the ChannelBind response. */ status = pj_stun_session_send_msg(sess->stun, peer, PJ_FALSE, - (sess->tp_type==PJ_TURN_TP_UDP), + (sess->conn_type==PJ_TURN_TP_UDP), sess->srv_addr, pj_sockaddr_get_len(sess->srv_addr), tdata); @@ -849,12 +897,12 @@ on_return: * The packet maybe a STUN packet or ChannelData packet. */ PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess, - const pj_uint8_t *pkt, - unsigned pkt_len, - pj_bool_t is_datagram) + void *pkt, + unsigned pkt_len) { pj_bool_t is_stun; pj_status_t status; + pj_bool_t is_datagram; /* Packet could be ChannelData or STUN message (response or * indication). @@ -863,14 +911,16 @@ PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess, /* Start locking the session */ pj_lock_acquire(sess->lock); + is_datagram = (sess->conn_type==PJ_TURN_TP_UDP); + /* Quickly check if this is STUN message */ - is_stun = ((pkt[0] & 0xC0) == 0); + is_stun = ((((pj_uint8_t*)pkt)[0] & 0xC0) == 0); if (is_stun) { /* This looks like STUN, give it to the STUN session */ unsigned options; - options = PJ_STUN_CHECK_PACKET; + options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK; if (is_datagram) options |= PJ_STUN_IS_DATAGRAM; status=pj_stun_session_on_rx_pkt(sess->stun, pkt, pkt_len, @@ -905,8 +955,8 @@ PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess, } /* Notify application */ - (*sess->cb.on_rx_data)(sess, pkt+sizeof(cd), cd.length, - &peer->addr, + (*sess->cb.on_rx_data)(sess, ((pj_uint8_t*)pkt)+sizeof(cd), + cd.length, &peer->addr, pj_sockaddr_get_len(&peer->addr)); status = PJ_SUCCESS; @@ -953,6 +1003,8 @@ static void on_session_fail( pj_turn_session *sess, pj_status_t status, const pj_str_t *reason) { + sess->last_status = status; + do { pj_str_t reason1; char err_msg[PJ_ERR_MSG_SIZE]; @@ -1010,6 +1062,7 @@ static void on_allocate_success(pj_turn_session *sess, { const pj_stun_lifetime_attr *lf_attr; const pj_stun_relay_addr_attr *raddr_attr; + const pj_stun_sockaddr_attr *mapped_attr; pj_str_t s; pj_time_val timeout; @@ -1071,6 +1124,12 @@ static void on_allocate_success(pj_turn_session *sess, "for now")); return; } + if (raddr_attr && !pj_sockaddr_has_addr(&raddr_attr->sockaddr)) { + on_session_fail(sess, method, PJNATH_EINSTUNMSG, + pj_cstr(&s, "Error: Invalid IP address in " + "RELAY-ADDRESS attribute")); + return; + } /* Save relayed address */ if (raddr_attr) { @@ -1091,6 +1150,14 @@ static void on_allocate_success(pj_turn_session *sess, } } + /* Get mapped address */ + mapped_attr = (const pj_stun_sockaddr_attr*) + pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); + if (mapped_attr) { + pj_memcpy(&sess->mapped_addr, &mapped_attr->sockaddr, + sizeof(mapped_attr->sockaddr)); + } + /* Success */ /* Cancel existing keep-alive timer, if any */ @@ -1132,6 +1199,17 @@ static void stun_on_request_complete(pj_stun_session *stun, sess = (pj_turn_session*)pj_stun_session_get_user_data(stun); if (method == PJ_STUN_ALLOCATE_METHOD) { + + /* Destroy if we have pending destroy request */ + if (sess->pending_destroy) { + if (status == PJ_SUCCESS) + sess->state = PJ_TURN_STATE_READY; + else + sess->state = PJ_TURN_STATE_DEALLOCATED; + sess_shutdown(sess, PJ_SUCCESS); + return; + } + /* Handle ALLOCATE response */ if (status==PJ_SUCCESS && PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) @@ -1298,7 +1376,7 @@ static void dns_srv_resolver_cb(void *user_data, const pj_dns_srv_record *rec) { pj_turn_session *sess = (pj_turn_session*) user_data; - unsigned i, cnt; + unsigned i, cnt, tot_cnt; /* Clear async resolver */ sess->dns_async = NULL; @@ -1309,11 +1387,27 @@ static void dns_srv_resolver_cb(void *user_data, return; } + /* Calculate total number of server entries in the response */ + tot_cnt = 0; + for (i=0; icount; ++i) { + tot_cnt += rec->entry[i].server.addr_count; + } + + if (tot_cnt > PJ_TURN_MAX_DNS_SRV_CNT) + tot_cnt = PJ_TURN_MAX_DNS_SRV_CNT; + + /* Allocate server entries */ + sess->srv_addr_list = (pj_sockaddr*) + pj_pool_calloc(sess->pool, tot_cnt, + sizeof(pj_sockaddr)); + /* Copy results to server entries */ - for (i=0, cnt=0; icount && cntcount && cntentry[i].server.addr_count && cntentry[i].server.addr_count && + cntsrv_addr_list[cnt].ipv4; addr->sin_family = sess->af; diff --git a/pjnath/src/pjnath/turn_sock.c b/pjnath/src/pjnath/turn_sock.c index cd2e08d4..e595d271 100644 --- a/pjnath/src/pjnath/turn_sock.c +++ b/pjnath/src/pjnath/turn_sock.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include #include #include #include @@ -50,11 +51,8 @@ struct pj_turn_sock int af; pj_turn_tp_type conn_type; - pj_sock_t sock; - pj_ioqueue_key_t *key; - pj_ioqueue_op_key_t read_key; + pj_activesock_t *active_sock; pj_ioqueue_op_key_t send_key; - pj_uint8_t pkt[PJ_TURN_MAX_PKT_LEN]; }; @@ -71,18 +69,22 @@ static void turn_on_channel_bound(pj_turn_session *sess, unsigned addr_len, unsigned ch_num); static void turn_on_rx_data(pj_turn_session *sess, - const pj_uint8_t *pkt, + void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, unsigned addr_len); static void turn_on_state(pj_turn_session *sess, pj_turn_state_t old_state, pj_turn_state_t new_state); -static void on_read_complete(pj_ioqueue_key_t *key, - pj_ioqueue_op_key_t *op_key, - pj_ssize_t bytes_read); -static void on_connect_complete(pj_ioqueue_key_t *key, - pj_status_t status); + +static pj_bool_t on_data_read(pj_activesock_t *asock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder); +static pj_bool_t on_connect_complete(pj_activesock_t *asock, + pj_status_t status); + static void destroy(pj_turn_sock *turn_sock); @@ -158,7 +160,7 @@ PJ_DEF(pj_status_t) pj_turn_sock_create(pj_stun_config *cfg, sess_cb.on_rx_data = &turn_on_rx_data; sess_cb.on_state = &turn_on_state; status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type, - &sess_cb, turn_sock, 0, &turn_sock->sess); + &sess_cb, 0, turn_sock, &turn_sock->sess); if (status != PJ_SUCCESS) { destroy(turn_sock); return status; @@ -187,13 +189,9 @@ static void destroy(pj_turn_sock *turn_sock) turn_sock->sess = NULL; } - if (turn_sock->key) { - pj_ioqueue_unregister(turn_sock->key); - turn_sock->key = NULL; - turn_sock->sock = 0; - } else if (turn_sock->sock) { - pj_sock_close(turn_sock->sock); - turn_sock->sock = 0; + if (turn_sock->active_sock) { + pj_activesock_close(turn_sock->active_sock); + turn_sock->active_sock = NULL; } if (turn_sock->lock) { @@ -271,7 +269,8 @@ static void sess_fail(pj_turn_sock *turn_sock, const char *title, pj_status_t status) { show_err(turn_sock, title, status); - pj_turn_session_destroy(turn_sock->sess); + if (turn_sock->sess) + pj_turn_session_destroy(turn_sock->sess); } /* @@ -280,6 +279,7 @@ static void sess_fail(pj_turn_sock *turn_sock, const char *title, PJ_DEF(pj_status_t) pj_turn_sock_set_user_data( pj_turn_sock *turn_sock, void *user_data) { + PJ_ASSERT_RETURN(turn_sock, PJ_EINVAL); turn_sock->user_data = user_data; return PJ_SUCCESS; } @@ -289,6 +289,7 @@ PJ_DEF(pj_status_t) pj_turn_sock_set_user_data( pj_turn_sock *turn_sock, */ PJ_DEF(void*) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock) { + PJ_ASSERT_RETURN(turn_sock, NULL); return turn_sock->user_data; } @@ -296,7 +297,7 @@ PJ_DEF(void*) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock) * Get info. */ PJ_DEF(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock, - pj_turn_session_info *info) + pj_turn_session_info *info) { PJ_ASSERT_RETURN(turn_sock && info, PJ_EINVAL); @@ -309,15 +310,41 @@ PJ_DEF(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock, } } +/** + * Lock the TURN socket. Application may need to call this function to + * synchronize access to other objects to avoid deadlock. + */ +PJ_DEF(pj_status_t) pj_turn_sock_lock(pj_turn_sock *turn_sock) +{ + return pj_lock_acquire(turn_sock->lock); +} + +/** + * Unlock the TURN socket. + */ +PJ_DEF(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock) +{ + return pj_lock_release(turn_sock->lock); +} + +/* + * Set STUN message logging for this TURN session. + */ +PJ_DEF(void) pj_turn_sock_set_log( pj_turn_sock *turn_sock, + unsigned flags) +{ + pj_turn_session_set_log(turn_sock->sess, flags); +} + /* * Initialize. */ -PJ_DEF(pj_status_t) pj_turn_sock_init(pj_turn_sock *turn_sock, - const pj_str_t *domain, - int default_port, - pj_dns_resolver *resolver, - const pj_stun_auth_cred *cred, - const pj_turn_alloc_param *param) +PJ_DEF(pj_status_t) pj_turn_sock_alloc(pj_turn_sock *turn_sock, + const pj_str_t *domain, + int default_port, + pj_dns_resolver *resolver, + const pj_stun_auth_cred *cred, + const pj_turn_alloc_param *param) { pj_status_t status; @@ -392,16 +419,16 @@ PJ_DEF(pj_status_t) pj_turn_sock_bind_channel( pj_turn_sock *turn_sock, /* * Notification when outgoing TCP socket has been connected. */ -static void on_connect_complete(pj_ioqueue_key_t *key, - pj_status_t status) +static pj_bool_t on_connect_complete(pj_activesock_t *asock, + pj_status_t status) { pj_turn_sock *turn_sock; - turn_sock = (pj_turn_sock*) pj_ioqueue_get_user_data(key); + turn_sock = (pj_turn_sock*) pj_activesock_get_user_data(asock); if (status != PJ_SUCCESS) { sess_fail(turn_sock, "TCP connect() error", status); - return; + return PJ_FALSE; } if (turn_sock->conn_type != PJ_TURN_TP_UDP) { @@ -409,8 +436,8 @@ static void on_connect_complete(pj_ioqueue_key_t *key, } /* Kick start pending read operation */ - pj_ioqueue_op_key_init(&turn_sock->read_key, sizeof(turn_sock->read_key)); - on_read_complete(turn_sock->key, &turn_sock->read_key, INIT); + status = pj_activesock_start_read(asock, turn_sock->pool, + PJ_TURN_MAX_PKT_LEN, 0); /* Init send_key */ pj_ioqueue_op_key_init(&turn_sock->send_key, sizeof(turn_sock->send_key)); @@ -419,56 +446,43 @@ static void on_connect_complete(pj_ioqueue_key_t *key, status = pj_turn_session_alloc(turn_sock->sess, &turn_sock->alloc_param); if (status != PJ_SUCCESS) { sess_fail(turn_sock, "Error sending ALLOCATE", status); - return; + return PJ_FALSE; } + + return PJ_TRUE; } /* * Notification from ioqueue when incoming UDP packet is received. */ -static void on_read_complete(pj_ioqueue_key_t *key, - pj_ioqueue_op_key_t *op_key, - pj_ssize_t bytes_read) +static pj_bool_t on_data_read(pj_activesock_t *asock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder) { - enum { MAX_RETRY = 10 }; pj_turn_sock *turn_sock; - int retry = 0; - pj_status_t status; + pj_bool_t ret = PJ_TRUE; - turn_sock = (pj_turn_sock*) pj_ioqueue_get_user_data(key); + turn_sock = (pj_turn_sock*) pj_activesock_get_user_data(asock); pj_lock_acquire(turn_sock->lock); - do { - if (bytes_read == INIT) { - /* Special instruction to initialize pending read() */ - } else if (bytes_read > 0 && turn_sock->sess) { - /* Report incoming packet to TURN session */ - pj_turn_session_on_rx_pkt(turn_sock->sess, turn_sock->pkt, - bytes_read, - turn_sock->conn_type == PJ_TURN_TP_UDP); - } else if (bytes_read <= 0 && turn_sock->conn_type != PJ_TURN_TP_UDP) { - sess_fail(turn_sock, "TCP connection closed", -bytes_read); - goto on_return; - } - - /* Read next packet */ - bytes_read = sizeof(turn_sock->pkt); - status = pj_ioqueue_recv(turn_sock->key, op_key, - turn_sock->pkt, &bytes_read, 0); - - if (status != PJ_EPENDING && status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - - pj_strerror(status, errmsg, sizeof(errmsg)); - sess_fail(turn_sock, "Socket recv() error", status); - goto on_return; - } - - } while (status != PJ_EPENDING && status != PJ_ECANCELLED && - ++retry < MAX_RETRY); + if (status == PJ_SUCCESS && turn_sock->sess) { + /* Report incoming packet to TURN session */ + PJ_TODO(REPORT_PARSED_LEN); + pj_turn_session_on_rx_pkt(turn_sock->sess, data, size); + } else if (status != PJ_SUCCESS && + turn_sock->conn_type != PJ_TURN_TP_UDP) + { + sess_fail(turn_sock, "TCP connection closed", status); + ret = PJ_FALSE; + goto on_return; + } on_return: pj_lock_release(turn_sock->lock); + + return ret; } @@ -482,7 +496,7 @@ static pj_status_t turn_on_send_pkt(pj_turn_session *sess, unsigned dst_addr_len) { pj_turn_sock *turn_sock = (pj_turn_sock*) - pj_turn_session_get_user_data(sess); + pj_turn_session_get_user_data(sess); pj_ssize_t len = pkt_len; pj_status_t status; @@ -495,8 +509,8 @@ static pj_status_t turn_on_send_pkt(pj_turn_session *sess, PJ_UNUSED_ARG(dst_addr); PJ_UNUSED_ARG(dst_addr_len); - status = pj_ioqueue_send(turn_sock->key, &turn_sock->send_key, - pkt, &len, 0); + status = pj_activesock_send(turn_sock->active_sock, &turn_sock->send_key, + pkt, &len, 0); if (status != PJ_SUCCESS && status != PJ_EPENDING) { show_err(turn_sock, "socket send()", status); } @@ -524,7 +538,7 @@ static void turn_on_channel_bound(pj_turn_session *sess, * Callback from TURN session upon incoming data. */ static void turn_on_rx_data(pj_turn_session *sess, - const pj_uint8_t *pkt, + void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, unsigned addr_len) @@ -559,7 +573,19 @@ static void turn_on_state(pj_turn_session *sess, return; } - if (new_state == PJ_TURN_STATE_RESOLVED) { + /* Notify app first */ + if (turn_sock->cb.on_state) { + (*turn_sock->cb.on_state)(turn_sock, old_state, new_state); + } + + /* Make sure user hasn't destroyed us in the callback */ + if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) { + pj_turn_session_info info; + pj_turn_session_get_info(turn_sock->sess, &info); + new_state = info.state; + } + + if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) { /* * Once server has been resolved, initiate outgoing TCP * connection to the server. @@ -567,19 +593,16 @@ static void turn_on_state(pj_turn_session *sess, pj_turn_session_info info; char addrtxt[PJ_INET6_ADDRSTRLEN+8]; int sock_type; - pj_ioqueue_callback ioq_cb; + pj_sock_t sock; + pj_activesock_cb asock_cb; /* Close existing connection, if any. This happens when * we're switching to alternate TURN server when either TCP * connection or ALLOCATE request failed. */ - if (turn_sock->key) { - pj_ioqueue_unregister(turn_sock->key); - turn_sock->key = NULL; - turn_sock->sock = 0; - } else if (turn_sock->sock) { - pj_sock_close(turn_sock->sock); - turn_sock->sock = 0; + if (turn_sock->active_sock) { + pj_activesock_close(turn_sock->active_sock); + turn_sock->active_sock = NULL; } /* Get server address from session info */ @@ -591,20 +614,21 @@ static void turn_on_state(pj_turn_session *sess, sock_type = pj_SOCK_STREAM(); /* Init socket */ - status = pj_sock_socket(turn_sock->af, sock_type, 0, - &turn_sock->sock); + status = pj_sock_socket(turn_sock->af, sock_type, 0, &sock); if (status != PJ_SUCCESS) { pj_turn_sock_destroy(turn_sock); return; } - /* Register to ioqeuue */ - pj_bzero(&ioq_cb, sizeof(ioq_cb)); - ioq_cb.on_read_complete = &on_read_complete; - ioq_cb.on_connect_complete = &on_connect_complete; - status = pj_ioqueue_register_sock(turn_sock->pool, turn_sock->cfg.ioqueue, - turn_sock->sock, turn_sock, - &ioq_cb, &turn_sock->key); + /* Create active socket */ + pj_bzero(&asock_cb, sizeof(asock_cb)); + asock_cb.on_data_read = &on_data_read; + asock_cb.on_connect_complete = &on_connect_complete; + status = pj_activesock_create(turn_sock->pool, sock, + sock_type, NULL, + turn_sock->cfg.ioqueue, &asock_cb, + turn_sock, + &turn_sock->active_sock); if (status != PJ_SUCCESS) { pj_turn_sock_destroy(turn_sock); return; @@ -616,10 +640,12 @@ static void turn_on_state(pj_turn_session *sess, sizeof(addrtxt), 3))); /* Initiate non-blocking connect */ - status = pj_ioqueue_connect(turn_sock->key, &info.server, - pj_sockaddr_get_len(&info.server)); + status=pj_activesock_start_connect(turn_sock->active_sock, + turn_sock->pool, + &info.server, + pj_sockaddr_get_len(&info.server)); if (status == PJ_SUCCESS) { - on_connect_complete(turn_sock->key, PJ_SUCCESS); + on_connect_complete(turn_sock->active_sock, PJ_SUCCESS); } else if (status != PJ_EPENDING) { pj_turn_sock_destroy(turn_sock); return; @@ -630,10 +656,6 @@ static void turn_on_state(pj_turn_session *sess, */ } - if (turn_sock->cb.on_state) { - (*turn_sock->cb.on_state)(turn_sock, old_state, new_state); - } - if (new_state >= PJ_TURN_STATE_DESTROYING && turn_sock->sess) { pj_time_val delay = {0, 0}; diff --git a/pjnath/src/pjturn-client/client_main.c b/pjnath/src/pjturn-client/client_main.c index ea6ffc39..823cf497 100644 --- a/pjnath/src/pjturn-client/client_main.c +++ b/pjnath/src/pjturn-client/client_main.c @@ -68,7 +68,7 @@ static struct options static int worker_thread(void *unused); static void turn_on_rx_data(pj_turn_sock *relay, - const pj_uint8_t *pkt, + void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, unsigned addr_len); @@ -274,7 +274,7 @@ static pj_status_t create_relay(void) } srv = pj_str(o.srv_addr); - CHECK( pj_turn_sock_init(g.relay, /* the relay */ + CHECK(pj_turn_sock_alloc(g.relay, /* the relay */ &srv, /* srv addr */ (o.srv_port?atoi(o.srv_port):PJ_STUN_PORT),/* def port */ NULL, /* resolver */ @@ -294,7 +294,7 @@ static void destroy_relay(void) static void turn_on_rx_data(pj_turn_sock *relay, - const pj_uint8_t *pkt, + void *pkt, unsigned pkt_len, const pj_sockaddr_t *peer_addr, unsigned addr_len) diff --git a/pjnath/src/pjturn-srv/auth.c b/pjnath/src/pjturn-srv/auth.c index d2527dfa..4e0e10a4 100644 --- a/pjnath/src/pjturn-srv/auth.c +++ b/pjnath/src/pjturn-srv/auth.c @@ -35,8 +35,7 @@ static struct cred_t { { "100", "100" }, { "700", "700" }, - { "701", "701" }, - { "702", "702" } + { "701", "701" } }; #define THE_NONCE "pjnath" -- cgit v1.2.3