diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-06-06 14:47:10 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-06-06 14:47:10 +0000 |
commit | 8ec5bd6b3d5bafb1d3ab11236a3adc45ac5f04d8 (patch) | |
tree | 75c82cf12d34d58e3ccf64e6eb08e8628e8f94cf /pjnath/src/pjnath-test | |
parent | 77825e4c00356383e69f3fc4c63eccd0a3aa103f (diff) |
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
Diffstat (limited to 'pjnath/src/pjnath-test')
-rw-r--r-- | pjnath/src/pjnath-test/ice_test.c | 1096 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/server.c | 652 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/server.h | 108 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/sess_auth.c | 6 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/stun_sock_test.c | 844 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/test.c | 112 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/test.h | 29 | ||||
-rw-r--r-- | pjnath/src/pjnath-test/turn_sock_test.c | 515 |
8 files changed, 2990 insertions, 372 deletions
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; i<ice_st->comp_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; i<dummy_cnt; ++i) { - pj_sockaddr_in addr; + ns_ip = pj_str("127.0.0.1"); + ns_port = (pj_uint16_t)DNS_SERVER_PORT; + status = pj_dns_resolver_set_ns(sess->resolver, 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; i<ice_st->comp_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; i<remote->cfg.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; i<min_cnt; ++i) { + const pj_ice_sess_check *c1; + const pj_ice_sess_check *c2; - /* Init ICE on im1 */ - status = pj_ice_strans_init_ice(im1, PJ_ICE_SESS_ROLE_CONTROLLING, NULL, NULL); - if (status != PJ_SUCCESS) - return -29; + c1 = pj_ice_strans_get_valid_pair(ept1->ice, 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 (; i<max_cnt; ++i) { + if (ept1->cfg.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; i<comp_cnt; ++i) { - status = verify_address(im1, im2, i+1); - if (status != 0) - return status; + /* Start ICE on caller */ + rc = start_ice(&sess->caller, &sess->callee); + if (rc != PJ_SUCCESS) { + destroy_sess(sess, 500); + return -130; } - /* And the other way around */ - for (i=0; i<comp_cnt; ++i) { - status = verify_address(im2, im1, i+1); - if (status != 0) - return status; + /* Wait until negotiation is complete on both endpoints */ +#define ALL_DONE (sess->caller.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; i<PJ_ARRAY_SIZE(sess_cfg); ++i) { + struct sess_cfg_t *cfg = &sess_cfg[i]; + unsigned delay[] = { 50, 2000 }; + unsigned d; + + PJ_LOG(3,("", " %s", cfg->title)); + + /* For each test item, test with various answer delay */ + for (d=0; d<PJ_ARRAY_SIZE(delay); ++d) { + struct role_t { + pj_ice_sess_role ua1; + pj_ice_sess_role ua2; + } role[] = + { + { ROLE1, ROLE2}, + { ROLE2, ROLE1}, + { ROLE1, ROLE1}, + { ROLE2, ROLE2} + }; + unsigned j; + + cfg->ua1.answer_delay = delay[d]; + cfg->ua2.answer_delay = delay[d]; + + /* For each test item, test with role conflict scenarios */ + for (j=0; j<PJ_ARRAY_SIZE(role); ++j) { + unsigned k1; + + cfg->ua1.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 <benny@prijono.org> + * + * 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; i<test_srv->turn_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; i<test_srv->turn_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; j<alloc->perm_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; i<alloc->perm_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 <benny@prijono.org> + * + * 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 <pjnath.h> +#include <pjlib-util.h> +#include <pjlib.h> + +#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 <benny@prijono.org> + * + * 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 <benny@prijono.org> + * + * 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<<new_state)) == 0); + + /* new_state must be greater than old_state */ + pj_assert(new_state > 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<<PJ_TURN_STATE_RESOLVING)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_RESOLVING is not called")); + return -140; + } + + if ((result.state_called & (1<<PJ_TURN_STATE_RESOLVED)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_RESOLVED is not called")); + return -150; + } + + if ((result.state_called & (1<<PJ_TURN_STATE_ALLOCATING)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_ALLOCATING is not called")); + return -155; + } + + if ((result.state_called & (1<<PJ_TURN_STATE_READY)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_READY is not called")); + return -160; + } + + if ((result.state_called & (1<<PJ_TURN_STATE_DEALLOCATING)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_DEALLOCATING is not called")); + return -170; + } + + if ((result.state_called & (1<<PJ_TURN_STATE_DEALLOCATED)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_DEALLOCATED is not called")); + return -180; + } + + if ((result.state_called & (1<<PJ_TURN_STATE_DESTROYING)) == 0) { + PJ_LOG(3,("", " error: PJ_TURN_STATE_DESTROYING is not called")); + return -190; + } + + poll_events(stun_cfg, 500, PJ_FALSE); + rc = check_pjlib_state(stun_cfg, &pjlib_state); + if (rc != 0) { + PJ_LOG(3,("", " error: memory/timer-heap leak detected")); + return rc; + } + } + + return 0; +} + + +///////////////////////////////////////////////////////////////////// + +static int destroy_test(pj_stun_config *stun_cfg, + pj_bool_t with_dns_srv, + pj_bool_t in_callback) +{ + 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; + int target_state; + int rc; + + PJ_LOG(3,("", " destroy test %s %s", + (in_callback? "in callback" : ""), + (with_dns_srv? "with DNS srv" : "") + )); + + test_cfg.client.enable_dns_srv = with_dns_srv; + + for (target_state=PJ_TURN_STATE_RESOLVING; target_state<=PJ_TURN_STATE_READY; ++target_state) { + enum { TIMEOUT = 60 }; + pjlib_state pjlib_state; + pj_turn_session_info info; + pj_time_val tstart; + + capture_pjlib_state(stun_cfg, &pjlib_state); + + PJ_LOG(3,("", " %s", pj_turn_state_name((pj_turn_state_t)target_state))); + + if (in_callback) + test_cfg.client.destroy_on_state = target_state; + + rc = create_test_session(stun_cfg, &test_cfg, &sess); + if (rc != 0) + return rc; + + if (in_callback) { + pj_gettimeofday(&tstart); + rc = 0; + while (sess->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; +} + |