summaryrefslogtreecommitdiff
path: root/pjnath/src
diff options
context:
space:
mode:
Diffstat (limited to 'pjnath/src')
-rw-r--r--pjnath/src/pjnath-test/ice_test.c1096
-rw-r--r--pjnath/src/pjnath-test/server.c652
-rw-r--r--pjnath/src/pjnath-test/server.h108
-rw-r--r--pjnath/src/pjnath-test/sess_auth.c6
-rw-r--r--pjnath/src/pjnath-test/stun_sock_test.c844
-rw-r--r--pjnath/src/pjnath-test/test.c112
-rw-r--r--pjnath/src/pjnath-test/test.h29
-rw-r--r--pjnath/src/pjnath-test/turn_sock_test.c515
-rw-r--r--pjnath/src/pjnath/errno.c2
-rw-r--r--pjnath/src/pjnath/ice_session.c176
-rw-r--r--pjnath/src/pjnath/ice_strans.c1708
-rw-r--r--pjnath/src/pjnath/stun_msg.c18
-rw-r--r--pjnath/src/pjnath/stun_session.c253
-rw-r--r--pjnath/src/pjnath/stun_sock.c829
-rw-r--r--pjnath/src/pjnath/stun_transaction.c39
-rw-r--r--pjnath/src/pjnath/turn_session.c158
-rw-r--r--pjnath/src/pjnath/turn_sock.c216
-rw-r--r--pjnath/src/pjturn-client/client_main.c6
-rw-r--r--pjnath/src/pjturn-srv/auth.c3
19 files changed, 5314 insertions, 1456 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, &current_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;
+}
+
diff --git a/pjnath/src/pjnath/errno.c b/pjnath/src/pjnath/errno.c
index a075a0c0..e71fa634 100644
--- a/pjnath/src/pjnath/errno.c
+++ b/pjnath/src/pjnath/errno.c
@@ -52,6 +52,8 @@ static const struct
PJ_BUILD_ERR( PJNATH_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"),
PJ_BUILD_ERR( PJNATH_ESTUNINSERVER, "Invalid STUN server or server not configured"),
+ PJ_BUILD_ERR( PJNATH_ESTUNDESTROYED, "STUN object has been destoyed"),
+
/* ICE related errors */
PJ_BUILD_ERR( PJNATH_ENOICE, "ICE session not available"),
PJ_BUILD_ERR( PJNATH_EICEINPROGRESS, "ICE check is in progress"),
diff --git a/pjnath/src/pjnath/ice_session.c b/pjnath/src/pjnath/ice_session.c
index 8b0538d8..ccf545c5 100644
--- a/pjnath/src/pjnath/ice_session.c
+++ b/pjnath/src/pjnath/ice_session.c
@@ -21,6 +21,7 @@
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/guid.h>
+#include <pj/hash.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
@@ -101,6 +102,11 @@ typedef struct timer_data
} timer_data;
+/* This is the data that will be attached as token to outgoing
+ * STUN messages.
+ */
+
+
/* Forward declarations */
static void destroy_ice(pj_ice_sess *ice,
pj_status_t reason);
@@ -169,6 +175,21 @@ PJ_DEF(const char*) pj_ice_get_cand_type_name(pj_ice_cand_type type)
}
+PJ_DEF(const char*) pj_ice_sess_role_name(pj_ice_sess_role role)
+{
+ switch (role) {
+ case PJ_ICE_SESS_ROLE_UNKNOWN:
+ return "Unknown";
+ case PJ_ICE_SESS_ROLE_CONTROLLED:
+ return "Controlled";
+ case PJ_ICE_SESS_ROLE_CONTROLLING:
+ return "Controlling";
+ default:
+ return "??";
+ }
+}
+
+
/* Get the prefix for the foundation */
static int get_type_prefix(pj_ice_cand_type type)
{
@@ -183,17 +204,28 @@ static int get_type_prefix(pj_ice_cand_type type)
}
}
-/* Calculate foundation */
+/* Calculate foundation:
+ * Two candidates have the same foundation when they are "similar" - of
+ * the same type and obtained from the same host candidate and STUN
+ * server using the same protocol. Otherwise, their foundation is
+ * different.
+ */
PJ_DEF(void) pj_ice_calc_foundation(pj_pool_t *pool,
pj_str_t *foundation,
pj_ice_cand_type type,
const pj_sockaddr *base_addr)
{
char buf[64];
+ pj_uint32_t val;
+ if (base_addr->addr.sa_family == pj_AF_INET()) {
+ val = pj_ntohl(base_addr->ipv4.sin_addr.s_addr);
+ } else {
+ val = pj_hash_calc(0, pj_sockaddr_get_addr(base_addr),
+ pj_sockaddr_get_addr_len(base_addr));
+ }
pj_ansi_snprintf(buf, sizeof(buf), "%c%x",
- get_type_prefix(type),
- (int)pj_ntohl(base_addr->ipv4.sin_addr.s_addr));
+ get_type_prefix(type), val);
pj_strdup2(pool, foundation, buf);
}
@@ -263,7 +295,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg,
PJ_ASSERT_RETURN(stun_cfg && cb && p_ice, PJ_EINVAL);
if (name == NULL)
- name = "ice%p";
+ name = "icess%p";
pool = pj_pool_create(stun_cfg->pf, name, PJNATH_POOL_LEN_ICE_SESS,
PJNATH_POOL_INC_ICE_SESS, NULL);
@@ -300,6 +332,12 @@ PJ_DEF(pj_status_t) pj_ice_sess_create(pj_stun_config *stun_cfg,
}
}
+ /* Initialize transport datas */
+ for (i=0; i<PJ_ARRAY_SIZE(ice->tp_data); ++i) {
+ ice->tp_data[i].transport_id = i;
+ ice->tp_data[i].has_req_data = PJ_FALSE;
+ }
+
if (local_ufrag == NULL) {
ice->rx_ufrag.ptr = (char*) pj_pool_alloc(ice->pool, PJ_ICE_UFRAG_LEN);
pj_create_random_string(ice->rx_ufrag.ptr, PJ_ICE_UFRAG_LEN);
@@ -551,6 +589,7 @@ static pj_uint32_t CALC_CAND_PRIO(pj_ice_sess *ice,
*/
PJ_DEF(pj_status_t) pj_ice_sess_add_cand(pj_ice_sess *ice,
unsigned comp_id,
+ unsigned transport_id,
pj_ice_cand_type type,
pj_uint16_t local_pref,
const pj_str_t *foundation,
@@ -576,17 +615,14 @@ PJ_DEF(pj_status_t) pj_ice_sess_add_cand(pj_ice_sess *ice,
}
lcand = &ice->lcand[ice->lcand_cnt];
- lcand->comp_id = comp_id;
+ lcand->comp_id = (pj_uint8_t)comp_id;
+ lcand->transport_id = (pj_uint8_t)transport_id;
lcand->type = type;
pj_strdup(ice->pool, &lcand->foundation, foundation);
lcand->prio = CALC_CAND_PRIO(ice, type, local_pref, lcand->comp_id);
pj_memcpy(&lcand->addr, addr, addr_len);
pj_memcpy(&lcand->base_addr, base_addr, addr_len);
- if (rel_addr)
- pj_memcpy(&lcand->rel_addr, rel_addr, addr_len);
- else
- pj_bzero(&lcand->rel_addr, sizeof(lcand->rel_addr));
-
+ pj_memcpy(&lcand->rel_addr, rel_addr, addr_len);
pj_ansi_strcpy(ice->tmp.txt, pj_inet_ntoa(lcand->addr.ipv4.sin_addr));
LOG4((ice->obj_name,
@@ -1322,9 +1358,13 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list(
}
/* Disable our components which don't have matching component */
- if (ice->comp_cnt==2 && highest_comp==1) {
- ice->comp_cnt = 1;
+ for (i=highest_comp; i<ice->comp_cnt; ++i) {
+ if (ice->comp[i].stun_sess) {
+ pj_stun_session_destroy(ice->comp[i].stun_sess);
+ pj_bzero(&ice->comp[i], sizeof(ice->comp[i]));
+ }
}
+ ice->comp_cnt = highest_comp;
/* Init timer entry in the checklist. Initially the timer ID is FALSE
* because timer is not running.
@@ -1345,26 +1385,13 @@ PJ_DEF(pj_status_t) pj_ice_sess_create_check_list(
return PJ_SUCCESS;
}
-
-/* This is the data that will be attached as user data to outgoing
- * STUN requests, and it will be given back when we receive completion
- * status of the request.
- */
-struct req_data
-{
- pj_ice_sess *ice;
- pj_ice_sess_checklist *clist;
- unsigned ckid;
-};
-
-
/* Perform check on the specified candidate pair */
static pj_status_t perform_check(pj_ice_sess *ice,
pj_ice_sess_checklist *clist,
unsigned check_id)
{
pj_ice_sess_comp *comp;
- struct req_data *rd;
+ pj_ice_msg_data *msg_data;
pj_ice_sess_check *check;
const pj_ice_sess_cand *lcand;
const pj_ice_sess_cand *rcand;
@@ -1392,10 +1419,12 @@ static pj_status_t perform_check(pj_ice_sess *ice,
/* Attach data to be retrieved later when STUN request transaction
* completes and on_stun_request_complete() callback is called.
*/
- rd = PJ_POOL_ZALLOC_T(check->tdata->pool, struct req_data);
- rd->ice = ice;
- rd->clist = clist;
- rd->ckid = check_id;
+ msg_data = PJ_POOL_ZALLOC_T(check->tdata->pool, pj_ice_msg_data);
+ msg_data->transport_id = lcand->transport_id;
+ msg_data->has_req_data = PJ_TRUE;
+ msg_data->data.req.ice = ice;
+ msg_data->data.req.clist = clist;
+ msg_data->data.req.ckid = check_id;
/* Add PRIORITY */
prio = CALC_CAND_PRIO(ice, PJ_ICE_CAND_TYPE_PRFLX, 65535,
@@ -1427,7 +1456,7 @@ static pj_status_t perform_check(pj_ice_sess *ice,
*/
/* Initiate STUN transaction to send the request */
- status = pj_stun_session_send_msg(comp->stun_sess, (void*)rd, PJ_FALSE,
+ status = pj_stun_session_send_msg(comp->stun_sess, msg_data, PJ_FALSE,
PJ_TRUE, &rcand->addr,
sizeof(pj_sockaddr_in), check->tdata);
if (status != PJ_SUCCESS) {
@@ -1655,12 +1684,10 @@ static pj_status_t on_stun_send_msg(pj_stun_session *sess,
{
stun_data *sd = (stun_data*) pj_stun_session_get_user_data(sess);
pj_ice_sess *ice = sd->ice;
-
- PJ_UNUSED_ARG(token);
-
- return (*ice->cb.on_tx_pkt)(ice, sd->comp_id,
- pkt, pkt_size,
- dst_addr, addr_len);
+ pj_ice_msg_data *msg_data = (pj_ice_msg_data*) token;
+
+ return (*ice->cb.on_tx_pkt)(ice, sd->comp_id, msg_data->transport_id,
+ pkt, pkt_size, dst_addr, addr_len);
}
@@ -1673,7 +1700,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
- struct req_data *rd = (struct req_data*) token;
+ pj_ice_msg_data *msg_data = (pj_ice_msg_data*) token;
pj_ice_sess *ice;
pj_ice_sess_check *check, *new_check;
pj_ice_sess_cand *lcand;
@@ -1684,9 +1711,12 @@ static void on_stun_request_complete(pj_stun_session *stun_sess,
PJ_UNUSED_ARG(stun_sess);
PJ_UNUSED_ARG(src_addr_len);
- ice = rd->ice;
- check = &rd->clist->checks[rd->ckid];
- clist = rd->clist;
+ pj_assert(msg_data->has_req_data);
+
+ ice = msg_data->data.req.ice;
+ clist = msg_data->data.req.clist;
+ check = &clist->checks[msg_data->data.req.ckid];
+
/* Mark STUN transaction as complete */
pj_assert(tdata == check->tdata);
@@ -1739,7 +1769,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess,
/* Resend request */
LOG4((ice->obj_name, "Resending check because of role conflict"));
check_set_state(ice, check, PJ_ICE_SESS_CHECK_STATE_WAITING, 0);
- perform_check(ice, clist, rd->ckid);
+ perform_check(ice, clist, msg_data->data.req.ckid);
pj_mutex_unlock(ice->mutex);
return;
}
@@ -1846,6 +1876,7 @@ static void on_stun_request_complete(pj_stun_session *stun_sess,
/* Add new peer reflexive candidate */
status = pj_ice_sess_add_cand(ice, check->lcand->comp_id,
+ msg_data->transport_id,
PJ_ICE_CAND_TYPE_PRFLX,
65535, &foundation,
&xaddr->sockaddr,
@@ -1919,6 +1950,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
{
stun_data *sd;
const pj_stun_msg *msg = rdata->msg;
+ pj_ice_msg_data *msg_data;
pj_ice_sess *ice;
pj_stun_priority_attr *prio_attr;
pj_stun_use_candidate_attr *uc_attr;
@@ -1929,12 +1961,11 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
PJ_UNUSED_ARG(pkt);
PJ_UNUSED_ARG(pkt_len);
- PJ_UNUSED_ARG(token);
-
+
/* Reject any requests except Binding request */
if (msg->hdr.type != PJ_STUN_BINDING_REQUEST) {
pj_stun_session_respond(sess, rdata, PJ_STUN_SC_BAD_REQUEST,
- NULL, NULL, PJ_TRUE,
+ NULL, token, PJ_TRUE,
src_addr, src_addr_len);
return PJ_SUCCESS;
}
@@ -2001,7 +2032,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
} else {
/* Generate 487 response */
pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT,
- NULL, NULL, PJ_TRUE,
+ NULL, token, PJ_TRUE,
src_addr, src_addr_len);
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
@@ -2013,7 +2044,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
if (pj_cmp_timestamp(&ice->tie_breaker, &role_attr->value) < 0) {
/* Generate 487 response */
pj_stun_session_respond(sess, rdata, PJ_STUN_SC_ROLE_CONFLICT,
- NULL, NULL, PJ_TRUE,
+ NULL, token, PJ_TRUE,
src_addr, src_addr_len);
pj_mutex_unlock(ice->mutex);
return PJ_SUCCESS;
@@ -2034,11 +2065,18 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
return status;
}
+ /* Add XOR-MAPPED-ADDRESS attribute */
status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
PJ_STUN_ATTR_XOR_MAPPED_ADDR,
PJ_TRUE, src_addr, src_addr_len);
- status = pj_stun_session_send_msg(sess, NULL, PJ_TRUE, PJ_TRUE,
+ /* Create a msg_data to be associated with this response */
+ msg_data = PJ_POOL_ZALLOC_T(tdata->pool, pj_ice_msg_data);
+ msg_data->transport_id = ((pj_ice_msg_data*)token)->transport_id;
+ msg_data->has_req_data = PJ_FALSE;
+
+ /* Send the response */
+ status = pj_stun_session_send_msg(sess, msg_data, PJ_TRUE, PJ_TRUE,
src_addr, src_addr_len, tdata);
@@ -2058,6 +2096,7 @@ static pj_status_t on_stun_rx_request(pj_stun_session *sess,
/* Init rcheck */
rcheck->comp_id = sd->comp_id;
+ rcheck->transport_id = ((pj_ice_msg_data*)token)->transport_id;
rcheck->src_addr_len = src_addr_len;
pj_memcpy(&rcheck->src_addr, src_addr, src_addr_len);
rcheck->use_candidate = (uc_attr != NULL);
@@ -2090,7 +2129,6 @@ static void handle_incoming_check(pj_ice_sess *ice,
pj_ice_sess_cand *lcand = NULL;
pj_ice_sess_cand *rcand;
unsigned i;
- pj_bool_t is_relayed;
comp = find_comp(ice, rcheck->comp_id);
@@ -2109,7 +2147,7 @@ static void handle_incoming_check(pj_ice_sess *ice,
*/
if (i == ice->rcand_cnt) {
rcand = &ice->rcand[ice->rcand_cnt++];
- rcand->comp_id = rcheck->comp_id;
+ rcand->comp_id = (pj_uint8_t)rcheck->comp_id;
rcand->type = PJ_ICE_CAND_TYPE_PRFLX;
rcand->prio = rcheck->priority;
pj_memcpy(&rcand->addr, &rcheck->src_addr, rcheck->src_addr_len);
@@ -2147,12 +2185,14 @@ static void handle_incoming_check(pj_ice_sess *ice,
}
}
#else
- /* Just get candidate with the highest priority for the specified
- * component ID in the checklist.
+ /* Just get candidate with the highest priority and same transport ID
+ * for the specified component ID in the checklist.
*/
for (i=0; i<ice->clist.count; ++i) {
pj_ice_sess_check *c = &ice->clist.checks[i];
- if (c->lcand->comp_id == rcheck->comp_id) {
+ if (c->lcand->comp_id == rcheck->comp_id &&
+ c->lcand->transport_id == rcheck->transport_id)
+ {
lcand = c->lcand;
break;
}
@@ -2170,11 +2210,6 @@ static void handle_incoming_check(pj_ice_sess *ice,
/*
* Create candidate pair for this request.
*/
- /* First check if the source address is the source address of the
- * STUN relay, to determine if local candidate is relayed candidate.
- */
- PJ_TODO(DETERMINE_IF_REQUEST_COMES_FROM_RELAYED_CANDIDATE);
- is_relayed = PJ_FALSE;
/*
* 7.2.1.4. Triggered Checks
@@ -2309,6 +2344,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice,
{
pj_status_t status = PJ_SUCCESS;
pj_ice_sess_comp *comp;
+ pj_ice_sess_cand *cand;
PJ_ASSERT_RETURN(ice && comp_id, PJ_EINVAL);
@@ -2332,7 +2368,9 @@ PJ_DEF(pj_status_t) pj_ice_sess_send_data(pj_ice_sess *ice,
goto on_return;
}
- status = (*ice->cb.on_tx_pkt)(ice, comp_id, data, data_len,
+ cand = comp->valid_check->lcand;
+ status = (*ice->cb.on_tx_pkt)(ice, comp_id, cand->transport_id,
+ data, data_len,
&comp->valid_check->rcand->addr,
sizeof(pj_sockaddr_in));
@@ -2344,6 +2382,7 @@ on_return:
PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
unsigned comp_id,
+ unsigned transport_id,
void *pkt,
pj_size_t pkt_size,
const pj_sockaddr_t *src_addr,
@@ -2351,6 +2390,8 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
{
pj_status_t status = PJ_SUCCESS;
pj_ice_sess_comp *comp;
+ pj_ice_msg_data *msg_data = NULL;
+ unsigned i;
pj_status_t stun_status;
PJ_ASSERT_RETURN(ice, PJ_EINVAL);
@@ -2363,11 +2404,24 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
goto on_return;
}
+ /* Find transport */
+ for (i=0; i<PJ_ARRAY_SIZE(ice->tp_data); ++i) {
+ if (ice->tp_data[i].transport_id == transport_id) {
+ msg_data = &ice->tp_data[i];
+ break;
+ }
+ }
+ if (msg_data == NULL) {
+ pj_assert(!"Invalid transport ID");
+ status = PJ_EINVAL;
+ goto on_return;
+ }
+
stun_status = pj_stun_msg_check((const pj_uint8_t*)pkt, pkt_size,
PJ_STUN_IS_DATAGRAM);
if (stun_status == PJ_SUCCESS) {
status = pj_stun_session_on_rx_pkt(comp->stun_sess, pkt, pkt_size,
- PJ_STUN_IS_DATAGRAM, NULL,
+ PJ_STUN_IS_DATAGRAM, msg_data,
NULL, src_addr, src_addr_len);
if (status != PJ_SUCCESS) {
pj_strerror(status, ice->tmp.errmsg, sizeof(ice->tmp.errmsg));
@@ -2375,7 +2429,7 @@ PJ_DEF(pj_status_t) pj_ice_sess_on_rx_pkt(pj_ice_sess *ice,
ice->tmp.errmsg));
}
} else {
- (*ice->cb.on_rx_data)(ice, comp_id, pkt, pkt_size,
+ (*ice->cb.on_rx_data)(ice, comp_id, transport_id, pkt, pkt_size,
src_addr, src_addr_len);
}
diff --git a/pjnath/src/pjnath/ice_strans.c b/pjnath/src/pjnath/ice_strans.c
index 7266e079..34b74f0d 100644
--- a/pjnath/src/pjnath/ice_strans.c
+++ b/pjnath/src/pjnath/ice_strans.c
@@ -19,9 +19,11 @@
#include <pjnath/ice_strans.h>
#include <pjnath/errno.h>
#include <pj/addr_resolv.h>
+#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ip_helper.h>
#include <pj/log.h>
+#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
@@ -35,792 +37,571 @@
#endif
+/* Transport IDs */
+enum tp_type
+{
+ TP_NONE,
+ TP_STUN,
+ TP_TURN
+};
+
+/* Candidate preference default values */
+#define SRFLX_PREF 65535
+#define HOST_PREF 65530
+#define RELAY_PREF 65525
+
/* ICE callbacks */
static void on_ice_complete(pj_ice_sess *ice, pj_status_t status);
static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
unsigned comp_id,
+ unsigned transport_id,
const void *pkt, pj_size_t size,
const pj_sockaddr_t *dst_addr,
unsigned dst_addr_len);
static void ice_rx_data(pj_ice_sess *ice,
- unsigned comp_id,
- void *pkt, pj_size_t size,
- const pj_sockaddr_t *src_addr,
- unsigned src_addr_len);
-
-/* Ioqueue callback */
-static void on_read_complete(pj_ioqueue_key_t *key,
- pj_ioqueue_op_key_t *op_key,
- pj_ssize_t bytes_read);
-
-static void destroy_component(pj_ice_strans_comp *comp);
-static void destroy_ice_st(pj_ice_strans *ice_st, pj_status_t reason);
-
-
-/* STUN session callback */
-static pj_status_t stun_on_send_msg(pj_stun_session *sess,
- void *token,
- const void *pkt,
- pj_size_t pkt_size,
- const pj_sockaddr_t *dst_addr,
- unsigned addr_len);
-static void stun_on_request_complete(pj_stun_session *sess,
- pj_status_t status,
- void *token,
- pj_stun_tx_data *tdata,
- const pj_stun_msg *response,
- const pj_sockaddr_t *src_addr,
- unsigned src_addr_len);
-
-/* Keep-alive timer */
-static void start_ka_timer(pj_ice_strans *ice_st);
-static void stop_ka_timer(pj_ice_strans *ice_st);
-
-/* Utility: print error */
+ unsigned comp_id,
+ unsigned transport_id,
+ void *pkt, pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+
+/* STUN socket callbacks */
+/* Notification when incoming packet has been received. */
+static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock,
+ void *pkt,
+ unsigned pkt_len,
+ const pj_sockaddr_t *src_addr,
+ unsigned addr_len);
+/* Notifification when asynchronous send operation has completed. */
+static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+/* Notification when the status of the STUN transport has changed. */
+static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
+ pj_stun_sock_op op,
+ pj_status_t status);
+
+
+/* TURN callbacks */
+static void turn_on_rx_data(pj_turn_sock *turn_sock,
+ void *pkt,
+ unsigned pkt_len,
+ const pj_sockaddr_t *peer_addr,
+ unsigned addr_len);
+static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
+ pj_turn_state_t new_state);
+
+
+
+/* Forward decls */
+static void destroy_ice_st(pj_ice_strans *ice_st);
#define ice_st_perror(ice_st,msg,rc) pjnath_perror(ice_st->obj_name,msg,rc)
-
-/*
- * Create ICE stream transport
+static void sess_init_update(pj_ice_strans *ice_st);
+
+static void sess_add_ref(pj_ice_strans *ice_st);
+static pj_bool_t sess_dec_ref(pj_ice_strans *ice_st);
+
+/**
+ * This structure describes an ICE stream transport component. A component
+ * in ICE stream transport typically corresponds to a single socket created
+ * for this component, and bound to a specific transport address. This
+ * component may have multiple alias addresses, for example one alias
+ * address for each interfaces in multi-homed host, another for server
+ * reflexive alias, and another for relayed alias. For each transport
+ * address alias, an ICE stream transport candidate (#pj_ice_sess_cand) will
+ * be created, and these candidates will eventually registered to the ICE
+ * session.
*/
-PJ_DEF(pj_status_t) pj_ice_strans_create( pj_stun_config *stun_cfg,
- const char *name,
- unsigned comp_cnt,
- void *user_data,
- const pj_ice_strans_cb *cb,
- pj_ice_strans **p_ice_st)
+typedef struct pj_ice_strans_comp
{
- pj_pool_t *pool;
- pj_ice_strans *ice_st;
+ pj_ice_strans *ice_st; /**< ICE stream transport. */
+ unsigned comp_id; /**< Component ID. */
- PJ_ASSERT_RETURN(stun_cfg && comp_cnt && cb && p_ice_st, PJ_EINVAL);
- PJ_ASSERT_RETURN(stun_cfg->ioqueue && stun_cfg->timer_heap, PJ_EINVAL);
+ pj_stun_sock *stun_sock; /**< STUN transport. */
+ pj_turn_sock *turn_sock; /**< TURN relay transport. */
- if (name == NULL)
- name = "icstr%p";
+ unsigned cand_cnt; /**< # of candidates/aliaes. */
+ pj_ice_sess_cand cand_list[PJ_ICE_ST_MAX_CAND]; /**< Cand array */
- pool = pj_pool_create(stun_cfg->pf, name, PJNATH_POOL_LEN_ICE_STRANS,
- PJNATH_POOL_INC_ICE_STRANS, NULL);
- ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans);
- ice_st->pool = pool;
- pj_memcpy(ice_st->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
- ice_st->user_data = user_data;
-
- ice_st->comp_cnt = comp_cnt;
- ice_st->comp = (pj_ice_strans_comp**) pj_pool_calloc(pool, comp_cnt,
- sizeof(void*));
+ unsigned default_cand; /**< Default candidate. */
- pj_memcpy(&ice_st->cb, cb, sizeof(*cb));
- pj_memcpy(&ice_st->stun_cfg, stun_cfg, sizeof(*stun_cfg));
+} pj_ice_strans_comp;
- PJ_LOG(4,(ice_st->obj_name, "ICE stream transport created"));
+/**
+ * This structure represents the ICE stream transport.
+ */
+struct pj_ice_strans
+{
+ char *obj_name; /**< Log ID. */
+ pj_pool_t *pool; /**< Pool used by this object. */
+ void *user_data; /**< Application data. */
+ pj_ice_strans_cfg cfg; /**< Configuration. */
+ pj_ice_strans_cb cb; /**< Application callback. */
- *p_ice_st = ice_st;
- return PJ_SUCCESS;
-}
+ pj_ice_sess *ice; /**< ICE session. */
+ pj_time_val start_time;/**< Time when ICE was started */
-/* Destroy ICE */
-static void destroy_ice_st(pj_ice_strans *ice_st, pj_status_t reason)
-{
- unsigned i;
- char obj_name[PJ_MAX_OBJ_NAME];
+ unsigned comp_cnt; /**< Number of components. */
+ pj_ice_strans_comp **comp; /**< Components array. */
- if (reason == PJ_SUCCESS) {
- pj_memcpy(obj_name, ice_st->obj_name, PJ_MAX_OBJ_NAME);
- PJ_LOG(4,(obj_name, "ICE stream transport shutting down"));
- }
+ pj_timer_entry ka_timer; /**< STUN keep-alive timer. */
- /* Kill keep-alive timer, if any */
- stop_ka_timer(ice_st);
+ pj_atomic_t *busy_cnt; /**< To prevent destroy */
+ pj_bool_t destroy_req;/**< Destroy has been called? */
+ pj_bool_t cb_called; /**< Init error callback called?*/
+};
- /* Destroy ICE if we have ICE */
- if (ice_st->ice) {
- pj_ice_sess_destroy(ice_st->ice);
- ice_st->ice = NULL;
- }
- /* Destroy all components */
- for (i=0; i<ice_st->comp_cnt; ++i) {
- if (ice_st->comp[i]) {
- destroy_component(ice_st->comp[i]);
- ice_st->comp[i] = NULL;
- }
- }
- ice_st->comp_cnt = 0;
+/* Validate configuration */
+static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg)
+{
+ pj_status_t status;
- /* Done */
- pj_pool_release(ice_st->pool);
+ status = pj_stun_config_check_valid(&cfg->stun_cfg);
+ if (!status)
+ return status;
- if (reason == PJ_SUCCESS) {
- PJ_LOG(4,(obj_name, "ICE stream transport destroyed"));
- }
+ return PJ_SUCCESS;
}
+
/*
- * Destroy ICE stream transport.
+ * Initialize ICE transport configuration with default values.
*/
-PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st)
+PJ_DEF(void) pj_ice_strans_cfg_default(pj_ice_strans_cfg *cfg)
{
- destroy_ice_st(ice_st, PJ_SUCCESS);
- return PJ_SUCCESS;
+ pj_bzero(cfg, sizeof(*cfg));
+
+ pj_stun_config_init(&cfg->stun_cfg, NULL, 0, NULL, NULL);
+ pj_stun_sock_cfg_default(&cfg->stun.cfg);
+ pj_turn_alloc_param_default(&cfg->turn.alloc_param);
+
+ cfg->af = pj_AF_INET();
+ cfg->stun.port = PJ_STUN_PORT;
+ cfg->turn.conn_type = PJ_TURN_TP_UDP;
}
+
/*
- * Resolve STUN server
+ * Copy configuration.
*/
-PJ_DEF(pj_status_t) pj_ice_strans_set_stun_domain(pj_ice_strans *ice_st,
- pj_dns_resolver *resolver,
- const pj_str_t *domain)
+PJ_DEF(void) pj_ice_strans_cfg_copy( pj_pool_t *pool,
+ pj_ice_strans_cfg *dst,
+ const pj_ice_strans_cfg *src)
{
- /* Yeah, TODO */
- PJ_UNUSED_ARG(ice_st);
- PJ_UNUSED_ARG(resolver);
- PJ_UNUSED_ARG(domain);
- return -1;
+ pj_memcpy(dst, src, sizeof(*src));
+
+ if (src->stun.server.slen)
+ pj_strdup(pool, &dst->stun.server, &src->stun.server);
+ if (src->turn.server.slen)
+ pj_strdup(pool, &dst->turn.server, &src->turn.server);
+ pj_stun_auth_cred_dup(pool, &dst->turn.auth_cred,
+ &src->turn.auth_cred);
}
/*
- * Set STUN server address.
+ * Create the component.
*/
-PJ_DEF(pj_status_t) pj_ice_strans_set_stun_srv( pj_ice_strans *ice_st,
- const pj_sockaddr_in *stun_srv,
- const pj_sockaddr_in *turn_srv)
+static pj_status_t create_comp(pj_ice_strans *ice_st, unsigned comp_id)
{
- PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
- /* Must not have pending resolver job */
- PJ_ASSERT_RETURN(ice_st->has_rjob==PJ_FALSE, PJ_EINVALIDOP);
-
- if (stun_srv) {
- pj_memcpy(&ice_st->stun_srv, stun_srv, sizeof(pj_sockaddr_in));
- } else {
- pj_bzero(&ice_st->stun_srv, sizeof(pj_sockaddr_in));
- }
-
- if (turn_srv) {
- pj_memcpy(&ice_st->turn_srv, turn_srv, sizeof(pj_sockaddr_in));
- } else {
- pj_bzero(&ice_st->turn_srv, sizeof(pj_sockaddr_in));
- }
-
- return PJ_SUCCESS;
-}
-
-/* Add new candidate */
-static pj_status_t add_cand( pj_ice_strans *ice_st,
- pj_ice_strans_comp *comp,
- unsigned comp_id,
- pj_ice_cand_type type,
- pj_uint16_t local_pref,
- const pj_sockaddr_in *addr,
- pj_bool_t set_default)
-{
- pj_ice_strans_cand *cand;
- unsigned i;
-
- PJ_ASSERT_RETURN(ice_st && comp && addr, PJ_EINVAL);
- PJ_ASSERT_RETURN(comp->cand_cnt < PJ_ICE_ST_MAX_CAND, PJ_ETOOMANY);
-
- /* Check that we don't have candidate with the same
- * address.
- */
- for (i=0; i<comp->cand_cnt; ++i) {
- if (pj_memcmp(addr, &comp->cand_list[i].addr,
- sizeof(pj_sockaddr_in))==0)
- {
- /* Duplicate */
- PJ_LOG(5,(ice_st->obj_name, "Duplicate candidate not added"));
- return PJ_SUCCESS;
- }
- }
+ pj_ice_strans_comp *comp = NULL;
+ pj_status_t status;
- cand = &comp->cand_list[comp->cand_cnt];
-
- pj_bzero(cand, sizeof(*cand));
- cand->type = type;
- cand->status = PJ_SUCCESS;
- pj_memcpy(&cand->addr, addr, sizeof(pj_sockaddr_in));
- cand->ice_cand_id = -1;
- cand->local_pref = local_pref;
- pj_ice_calc_foundation(ice_st->pool, &cand->foundation, type,
- &comp->local_addr);
-
- if (set_default)
- comp->default_cand = comp->cand_cnt;
-
- PJ_LOG(5,(ice_st->obj_name,
- "Candidate %s:%d (type=%s) added to component %d",
- pj_inet_ntoa(addr->sin_addr),
- (int)pj_ntohs(addr->sin_port),
- pj_ice_get_cand_type_name(type),
- comp_id));
-
- comp->cand_cnt++;
- return PJ_SUCCESS;
-}
+ /* Verify arguments */
+ PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL);
-/* Create new component (i.e. socket) */
-static pj_status_t create_component(pj_ice_strans *ice_st,
- unsigned comp_id,
- pj_uint32_t options,
- const pj_sockaddr_in *addr,
- pj_ice_strans_comp **p_comp)
-{
- enum { MAX_RETRY=100, PORT_INC=2 };
- pj_ioqueue_callback ioqueue_cb;
- pj_ice_strans_comp *comp;
- int retry, addr_len;
- struct {
- pj_uint32_t a1, a2, a3;
- } tsx_id;
- pj_status_t status;
+ /* Check that component ID present */
+ PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID);
+ /* Create component */
comp = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_strans_comp);
comp->ice_st = ice_st;
comp->comp_id = comp_id;
- comp->options = options;
- comp->sock = PJ_INVALID_SOCKET;
- comp->last_status = PJ_SUCCESS;
-
- /* Create transaction ID for STUN keep alives */
- tsx_id.a1 = 0;
- tsx_id.a2 = comp_id;
- tsx_id.a3 = (pj_uint32_t) (unsigned long) ice_st;
- pj_memcpy(comp->ka_tsx_id, &tsx_id, sizeof(comp->ka_tsx_id));
-
- /* Create socket */
- status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &comp->sock);
- if (status != PJ_SUCCESS)
- return status;
-
- /* Init address */
- if (addr)
- pj_memcpy(&comp->local_addr, addr, sizeof(pj_sockaddr_in));
- else
- pj_sockaddr_in_init(&comp->local_addr.ipv4, NULL, 0);
-
- /* Retry binding socket */
- for (retry=0; retry<MAX_RETRY; ++retry) {
- pj_uint16_t port;
-
- status = pj_sock_bind(comp->sock, &comp->local_addr,
- sizeof(pj_sockaddr_in));
- if (status == PJ_SUCCESS)
- break;
-
- if (options & PJ_ICE_ST_OPT_NO_PORT_RETRY)
- goto on_error;
-
- port = pj_ntohs(comp->local_addr.ipv4.sin_port);
- port += PORT_INC;
- comp->local_addr.ipv4.sin_port = pj_htons(port);
- }
-
- /* Get the actual port where the socket is bound to.
- * (don't care about the address, it will be retrieved later)
- */
- addr_len = sizeof(comp->local_addr);
- status = pj_sock_getsockname(comp->sock, &comp->local_addr, &addr_len);
- if (status != PJ_SUCCESS)
- goto on_error;
-
- /* Register to ioqueue */
- pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
- ioqueue_cb.on_read_complete = &on_read_complete;
- status = pj_ioqueue_register_sock(ice_st->pool, ice_st->stun_cfg.ioqueue,
- comp->sock, comp, &ioqueue_cb,
- &comp->key);
- if (status != PJ_SUCCESS)
- goto on_error;
-
- /* Disable concurrency */
- status = pj_ioqueue_set_concurrency(comp->key, PJ_FALSE);
- if (status != PJ_SUCCESS)
- goto on_error;
-
- pj_ioqueue_op_key_init(&comp->read_op, sizeof(comp->read_op));
- pj_ioqueue_op_key_init(&comp->write_op, sizeof(comp->write_op));
-
- /* Kick start reading the socket */
- on_read_complete(comp->key, &comp->read_op, 0);
- /* If the socket is bound to INADDR_ANY, then lookup all interfaces in
- * the host and add them into cand_list. Otherwise if the socket is bound
- * to a specific interface, then only add that specific interface to
- * cand_list.
- */
- if (((options & PJ_ICE_ST_OPT_DONT_ADD_CAND)==0) &&
- comp->local_addr.ipv4.sin_addr.s_addr == 0)
- {
- /* Socket is bound to INADDR_ANY */
- unsigned i, ifs_cnt;
- pj_sockaddr ifs[PJ_ICE_ST_MAX_CAND-2];
-
- /* Reset default candidate */
- comp->default_cand = -1;
-
- /* Enum all IP interfaces in the host */
- ifs_cnt = PJ_ARRAY_SIZE(ifs);
- status = pj_enum_ip_interface(pj_AF_INET(), &ifs_cnt, ifs);
- if (status != PJ_SUCCESS)
- goto on_error;
+ ice_st->comp[comp_id-1] = comp;
- /* Set default IP interface as the base address */
- status = pj_gethostip(pj_AF_INET(), &comp->local_addr);
+ /* Initialize default candidate */
+ comp->default_cand = 0;
+
+ /* Create STUN transport if configured */
+ if (ice_st->cfg.stun.server.slen || !ice_st->cfg.stun.no_host_cands) {
+ pj_stun_sock_cb stun_sock_cb;
+ pj_ice_sess_cand *cand;
+
+ pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb));
+ stun_sock_cb.on_rx_data = &stun_on_rx_data;
+ stun_sock_cb.on_status = &stun_on_status;
+ stun_sock_cb.on_data_sent = &stun_on_data_sent;
+
+ /* Create the STUN transport */
+ status = pj_stun_sock_create(&ice_st->cfg.stun_cfg, NULL,
+ ice_st->cfg.af, &stun_sock_cb,
+ &ice_st->cfg.stun.cfg,
+ comp, &comp->stun_sock);
if (status != PJ_SUCCESS)
- goto on_error;
-
- /* Add candidate entry for each interface */
- for (i=0; i<ifs_cnt; ++i) {
- pj_sockaddr_in cand_addr;
- pj_bool_t set_default;
- pj_uint16_t local_pref;
-
- /* Ignore 127.0.0.0/24 address */
- if ((pj_ntohl(ifs[i].ipv4.sin_addr.s_addr) >> 24)==127)
- continue;
-
- pj_memcpy(&cand_addr, &comp->local_addr, sizeof(pj_sockaddr_in));
- cand_addr.sin_addr.s_addr = ifs[i].ipv4.sin_addr.s_addr;
+ return status;
+ /* Start STUN Binding resolution and add srflx candidate
+ * only if server is set
+ */
+ if (ice_st->cfg.stun.server.slen) {
+ pj_stun_sock_info stun_sock_info;
+
+ /* Add pending job */
+ ///sess_add_ref(ice_st);
+
+ /* Start Binding resolution */
+ status = pj_stun_sock_start(comp->stun_sock,
+ &ice_st->cfg.stun.server,
+ ice_st->cfg.stun.port,
+ ice_st->cfg.resolver);
+ if (status != PJ_SUCCESS) {
+ ///sess_dec_ref(ice_st);
+ return status;
+ }
- /* If the IP address is equal to local address, assign it
- * as default candidate.
- */
- if (ifs[i].ipv4.sin_addr.s_addr == comp->local_addr.ipv4.sin_addr.s_addr) {
- set_default = PJ_TRUE;
- local_pref = 65535;
- } else {
- set_default = PJ_FALSE;
- local_pref = 0;
+ /* Enumerate addresses */
+ status = pj_stun_sock_get_info(comp->stun_sock, &stun_sock_info);
+ if (status != PJ_SUCCESS) {
+ ///sess_dec_ref(ice_st);
+ return status;
}
- status = add_cand(ice_st, comp, comp_id,
- PJ_ICE_CAND_TYPE_HOST,
- local_pref, &cand_addr, set_default);
- if (status != PJ_SUCCESS)
- goto on_error;
+ /* Add srflx candidate with pending status */
+ cand = &comp->cand_list[comp->cand_cnt++];
+ cand->type = PJ_ICE_CAND_TYPE_SRFLX;
+ cand->status = PJ_EPENDING;
+ cand->local_pref = SRFLX_PREF;
+ cand->transport_id = TP_STUN;
+ cand->comp_id = (pj_uint8_t) comp_id;
+ pj_sockaddr_cp(&cand->base_addr, &stun_sock_info.aliases[0]);
+ pj_sockaddr_cp(&cand->rel_addr, &cand->base_addr);
+ pj_ice_calc_foundation(ice_st->pool, &cand->foundation,
+ cand->type, &cand->base_addr);
+ PJ_LOG(4,(ice_st->obj_name,
+ "Comp %d: srflx candidate starts Binding discovery",
+ comp_id));
+
+ /* Set default candidate to srflx */
+ comp->default_cand = cand - comp->cand_list;
}
-
- } else if ((options & PJ_ICE_ST_OPT_DONT_ADD_CAND)==0) {
- /* Socket is bound to specific address.
- * In this case only add that address as a single entry in the
- * cand_list table.
+ /* Add local addresses to host candidates, unless no_host_cands
+ * flag is set.
*/
- status = add_cand(ice_st, comp, comp_id,
- PJ_ICE_CAND_TYPE_HOST,
- 65535, &comp->local_addr.ipv4,
- PJ_TRUE);
- if (status != PJ_SUCCESS)
- goto on_error;
+ if (ice_st->cfg.stun.no_host_cands == PJ_FALSE) {
+ pj_stun_sock_info stun_sock_info;
+ unsigned i;
- } else if (options & PJ_ICE_ST_OPT_DONT_ADD_CAND) {
- /* If application doesn't want to add candidate, just fix local_addr
- * in case its value is zero.
- */
- if (comp->local_addr.ipv4.sin_addr.s_addr == 0) {
- status = pj_gethostip(pj_AF_INET(), &comp->local_addr);
+ /* Enumerate addresses */
+ status = pj_stun_sock_get_info(comp->stun_sock, &stun_sock_info);
if (status != PJ_SUCCESS)
return status;
+
+ for (i=0; i<stun_sock_info.alias_cnt; ++i) {
+ char addrinfo[PJ_INET6_ADDRSTRLEN+10];
+ const pj_sockaddr *addr = &stun_sock_info.aliases[i];
+
+ /* Leave one candidate for relay */
+ if (comp->cand_cnt >= PJ_ICE_ST_MAX_CAND-1) {
+ PJ_LOG(4,(ice_st->obj_name, "Too many host candidates"));
+ break;
+ }
+
+ /* Ignore loopback addresses unless cfg->stun.loop_addr
+ * is set
+ */
+ if ((pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==127) {
+ if (ice_st->cfg.stun.loop_addr==PJ_FALSE)
+ continue;
+ }
+
+ cand = &comp->cand_list[comp->cand_cnt++];
+
+ cand->type = PJ_ICE_CAND_TYPE_HOST;
+ cand->status = PJ_SUCCESS;
+ cand->local_pref = HOST_PREF;
+ cand->transport_id = TP_STUN;
+ cand->comp_id = (pj_uint8_t) comp_id;
+ pj_sockaddr_cp(&cand->addr, addr);
+ pj_sockaddr_cp(&cand->base_addr, addr);
+ pj_bzero(&cand->rel_addr, sizeof(cand->rel_addr));
+ pj_ice_calc_foundation(ice_st->pool, &cand->foundation,
+ cand->type, &cand->base_addr);
+
+ PJ_LOG(4,(ice_st->obj_name,
+ "Comp %d: host candidate %s added",
+ comp_id, pj_sockaddr_print(&cand->addr, addrinfo,
+ sizeof(addrinfo), 3)));
+ }
}
}
+ /* Create TURN relay if configured. */
+ if (ice_st->cfg.turn.server.slen) {
+ pj_turn_sock_cb turn_sock_cb;
+ pj_ice_sess_cand *cand;
- /* Done */
- if (p_comp)
- *p_comp = comp;
-
- return PJ_SUCCESS;
-
-on_error:
- destroy_component(comp);
- return status;
-}
+ /* Init TURN socket */
+ pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb));
+ turn_sock_cb.on_rx_data = &turn_on_rx_data;
+ turn_sock_cb.on_state = &turn_on_state;
-/*
- * This is callback called by ioqueue on incoming packet
- */
-static void on_read_complete(pj_ioqueue_key_t *key,
- pj_ioqueue_op_key_t *op_key,
- pj_ssize_t bytes_read)
-{
- pj_ice_strans_comp *comp = (pj_ice_strans_comp*)
- pj_ioqueue_get_user_data(key);
- pj_ice_strans *ice_st = comp->ice_st;
- pj_ssize_t pkt_size;
- enum { RETRY = 10 };
- unsigned retry;
- pj_status_t status;
+ status = pj_turn_sock_create(&ice_st->cfg.stun_cfg, ice_st->cfg.af,
+ ice_st->cfg.turn.conn_type,
+ &turn_sock_cb, 0, comp,
+ &comp->turn_sock);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
- if (bytes_read > 0) {
- /*
- * Okay, we got a packet from the socket for the component. There is
- * a bit of situation here, since this packet could be one of these:
- *
- * 1) this could be the response of STUN binding request sent by
- * this component to a) an initial request to get the STUN mapped
- * address of this component, or b) subsequent request to keep
- * the binding alive.
- *
- * 2) this could be a packet (STUN or not STUN) sent from the STUN
- * relay server. In this case, still there are few options to do
- * for this packet: a) process this locally if this packet is
- * related to TURN session management (e.g. Allocate response),
- * b) forward this packet to ICE if this is related to ICE
- * discovery process.
- *
- * 3) this could be a STUN request or response sent as part of ICE
- * discovery process.
- *
- * 4) this could be application's packet, e.g. when ICE processing
- * is done and agents start sending RTP/RTCP packets to each
- * other, or when ICE processing is not done and this ICE stream
- * transport decides to allow sending data.
- *
- * So far we don't have good solution for this.
- * The process below is just a workaround.
- */
- status = pj_stun_msg_check(comp->pkt, bytes_read,
- PJ_STUN_IS_DATAGRAM);
+ /* Add pending job */
+ ///sess_add_ref(ice_st);
- if (status == PJ_SUCCESS) {
- if (comp->stun_sess &&
- PJ_STUN_IS_RESPONSE(((pj_stun_msg_hdr*)comp->pkt)->type) &&
- pj_memcmp(comp->pkt+8, comp->ka_tsx_id, 12) == 0)
- {
- status = pj_stun_session_on_rx_pkt(comp->stun_sess, comp->pkt,
- bytes_read,
- PJ_STUN_IS_DATAGRAM, NULL,
- NULL, &comp->src_addr,
- comp->src_addr_len);
- } else if (ice_st->ice) {
- PJ_TODO(DISTINGUISH_BETWEEN_LOCAL_AND_RELAY);
-
- TRACE_PKT((comp->ice_st->obj_name,
- "Component %d RX packet from %s:%d",
- comp->comp_id,
- pj_inet_ntoa(comp->src_addr.ipv4.sin_addr),
- (int)pj_ntohs(comp->src_addr.ipv4.sin_port)));
-
- status = pj_ice_sess_on_rx_pkt(ice_st->ice, comp->comp_id,
- comp->pkt, bytes_read,
- &comp->src_addr,
- comp->src_addr_len);
- } else {
- /* This must have been a very late STUN reponse,
- * or an early STUN Binding Request when our local
- * ICE has not been created yet. */
- }
- } else {
- (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id,
- comp->pkt, bytes_read,
- &comp->src_addr, comp->src_addr_len);
+ /* Start allocation */
+ status=pj_turn_sock_alloc(comp->turn_sock,
+ &ice_st->cfg.turn.server,
+ ice_st->cfg.turn.port,
+ ice_st->cfg.resolver,
+ &ice_st->cfg.turn.auth_cred,
+ &ice_st->cfg.turn.alloc_param);
+ if (status != PJ_SUCCESS) {
+ ///sess_dec_ref(ice_st);
+ return status;
}
- } else if (bytes_read < 0) {
- ice_st_perror(comp->ice_st, "ioqueue read callback error",
- -bytes_read);
- }
+ /* Add relayed candidate with pending status */
+ cand = &comp->cand_list[comp->cand_cnt++];
+ cand->type = PJ_ICE_CAND_TYPE_RELAYED;
+ cand->status = PJ_EPENDING;
+ cand->local_pref = RELAY_PREF;
+ cand->transport_id = TP_TURN;
+ cand->comp_id = (pj_uint8_t) comp_id;
- /* Read next packet */
- for (retry=0; retry<RETRY;) {
- pkt_size = sizeof(comp->pkt);
- comp->src_addr_len = sizeof(comp->src_addr);
- status = pj_ioqueue_recvfrom(key, op_key, comp->pkt, &pkt_size,
- PJ_IOQUEUE_ALWAYS_ASYNC,
- &comp->src_addr, &comp->src_addr_len);
- if (status == PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) ||
- status == PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) ||
- status == PJ_STATUS_FROM_OS(OSERR_ECONNRESET))
- {
- ice_st_perror(comp->ice_st, "ioqueue recvfrom() error", status);
- ++retry;
- continue;
- } else if (status != PJ_SUCCESS && status != PJ_EPENDING) {
- retry += 2;
- ice_st_perror(comp->ice_st, "ioqueue recvfrom() error", status);
- } else {
- break;
- }
- }
-}
+ PJ_LOG(4,(ice_st->obj_name,
+ "Comp %d: TURN relay candidate waiting for allocation",
+ comp_id));
-/*
- * Destroy a component
- */
-static void destroy_component(pj_ice_strans_comp *comp)
-{
- if (comp->stun_sess) {
- pj_stun_session_destroy(comp->stun_sess);
- comp->stun_sess = NULL;
+ /* Set default candidate to relay */
+ comp->default_cand = cand - comp->cand_list;
}
- if (comp->key) {
- pj_ioqueue_unregister(comp->key);
- comp->key = NULL;
- comp->sock = PJ_INVALID_SOCKET;
- } else if (comp->sock != PJ_INVALID_SOCKET && comp->sock != 0) {
- pj_sock_close(comp->sock);
- comp->sock = PJ_INVALID_SOCKET;
- }
+ return PJ_SUCCESS;
}
-/* STUN keep-alive timer callback */
-static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
+/*
+ * Create ICE stream transport
+ */
+PJ_DEF(pj_status_t) pj_ice_strans_create( const char *name,
+ const pj_ice_strans_cfg *cfg,
+ unsigned comp_cnt,
+ void *user_data,
+ const pj_ice_strans_cb *cb,
+ pj_ice_strans **p_ice_st)
{
- pj_ice_strans *ice_st = (pj_ice_strans*)te->user_data;
+ pj_pool_t *pool;
+ pj_ice_strans *ice_st;
unsigned i;
pj_status_t status;
- PJ_UNUSED_ARG(th);
+ status = pj_ice_strans_cfg_check_valid(cfg);
+ if (status != PJ_SUCCESS)
+ return status;
- ice_st->ka_timer.id = PJ_FALSE;
+ PJ_ASSERT_RETURN(comp_cnt && cb && p_ice_st, PJ_EINVAL);
- for (i=0; i<ice_st->comp_cnt; ++i) {
- pj_ice_strans_comp *comp = ice_st->comp[i];
- pj_stun_tx_data *tdata;
- unsigned j;
+ if (name == NULL)
+ name = "ice%p";
- /* Does this component have STUN server reflexive candidate? */
- for (j=0; j<comp->cand_cnt; ++j) {
- if (comp->cand_list[j].type == PJ_ICE_CAND_TYPE_SRFLX)
- break;
- }
- if (j == comp->cand_cnt)
- continue;
-
- /* Create STUN binding request */
- status = pj_stun_session_create_req(comp->stun_sess,
- PJ_STUN_BINDING_REQUEST,
- PJ_STUN_MAGIC,
- comp->ka_tsx_id, &tdata);
- if (status != PJ_SUCCESS)
- continue;
+ pool = pj_pool_create(cfg->stun_cfg.pf, name, PJNATH_POOL_LEN_ICE_STRANS,
+ PJNATH_POOL_INC_ICE_STRANS, NULL);
+ ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans);
+ ice_st->pool = pool;
+ ice_st->obj_name = pool->obj_name;
+ ice_st->user_data = user_data;
- /* tdata->user_data is NULL for keep-alive */
- //tdata->user_data = NULL;
+ PJ_LOG(4,(ice_st->obj_name,
+ "Creating ICE stream transport with %d component(s)",
+ comp_cnt));
- ++comp->pending_cnt;
+ pj_ice_strans_cfg_copy(pool, &ice_st->cfg, cfg);
+ pj_memcpy(&ice_st->cb, cb, sizeof(*cb));
+
+ status = pj_atomic_create(pool, 0, &ice_st->busy_cnt);
+ if (status != PJ_SUCCESS) {
+ destroy_ice_st(ice_st);
+ return status;
+ }
+ ice_st->comp_cnt = comp_cnt;
+ ice_st->comp = (pj_ice_strans_comp**)
+ pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*));
- /* Send STUN binding request */
- PJ_LOG(5,(ice_st->obj_name, "Sending STUN keep-alive from %s;%d",
- pj_inet_ntoa(comp->local_addr.ipv4.sin_addr),
- pj_ntohs(comp->local_addr.ipv4.sin_port)));
- status = pj_stun_session_send_msg(comp->stun_sess, &comp->cand_list[j],
- PJ_FALSE, PJ_TRUE, &ice_st->stun_srv,
- sizeof(pj_sockaddr_in), tdata);
+ for (i=0; i<comp_cnt; ++i) {
+ status = create_comp(ice_st, i+1);
if (status != PJ_SUCCESS) {
- --comp->pending_cnt;
+ destroy_ice_st(ice_st);
+ return status;
}
}
- /* Start next timer */
- start_ka_timer(ice_st);
-}
+ /* Check if all candidates are ready (this may call callback) */
+ sess_init_update(ice_st);
-/* Start STUN keep-alive timer */
-static void start_ka_timer(pj_ice_strans *ice_st)
-{
- pj_time_val delay;
+ PJ_LOG(4,(ice_st->obj_name, "ICE stream transport created"));
- /* Skip if timer is already running */
- if (ice_st->ka_timer.id != PJ_FALSE)
- return;
+ *p_ice_st = ice_st;
+ return PJ_SUCCESS;
+}
- delay.sec = PJ_ICE_ST_KEEP_ALIVE_MIN;
- delay.msec = pj_rand() % (PJ_ICE_ST_KEEP_ALIVE_MAX_RAND * 1000);
- pj_time_val_normalize(&delay);
+/* Destroy ICE */
+static void destroy_ice_st(pj_ice_strans *ice_st)
+{
+ unsigned i;
- ice_st->ka_timer.cb = &ka_timer_cb;
- ice_st->ka_timer.user_data = ice_st;
-
- if (pj_timer_heap_schedule(ice_st->stun_cfg.timer_heap,
- &ice_st->ka_timer, &delay)==PJ_SUCCESS)
- {
- ice_st->ka_timer.id = PJ_TRUE;
+ /* Destroy ICE if we have ICE */
+ if (ice_st->ice) {
+ pj_ice_sess_destroy(ice_st->ice);
+ ice_st->ice = NULL;
}
-}
+ /* Destroy all components */
+ for (i=0; i<ice_st->comp_cnt; ++i) {
+ if (ice_st->comp[i]) {
+ if (ice_st->comp[i]->stun_sock) {
+ pj_stun_sock_set_user_data(ice_st->comp[i]->stun_sock, NULL);
+ pj_stun_sock_destroy(ice_st->comp[i]->stun_sock);
+ ice_st->comp[i]->stun_sock = NULL;
+ }
+ if (ice_st->comp[i]->turn_sock) {
+ pj_turn_sock_set_user_data(ice_st->comp[i]->turn_sock, NULL);
+ pj_turn_sock_destroy(ice_st->comp[i]->turn_sock);
+ ice_st->comp[i]->turn_sock = NULL;
+ }
+ }
+ }
+ ice_st->comp_cnt = 0;
-/* Stop STUN keep-alive timer */
-static void stop_ka_timer(pj_ice_strans *ice_st)
-{
- /* Skip if timer is already stop */
- if (ice_st->ka_timer.id == PJ_FALSE)
- return;
+ /* Destroy reference counter */
+ if (ice_st->busy_cnt) {
+ pj_assert(pj_atomic_get(ice_st->busy_cnt)==0);
+ pj_atomic_destroy(ice_st->busy_cnt);
+ ice_st->busy_cnt = NULL;
+ }
- pj_timer_heap_cancel(ice_st->stun_cfg.timer_heap, &ice_st->ka_timer);
- ice_st->ka_timer.id = PJ_FALSE;
+ /* Done */
+ pj_pool_release(ice_st->pool);
}
-
-/*
- * Add STUN mapping to a component.
- */
-static pj_status_t get_stun_mapped_addr(pj_ice_strans *ice_st,
- pj_ice_strans_comp *comp)
+/* Notification about failure */
+static void sess_fail(pj_ice_strans *ice_st, pj_ice_strans_op op,
+ const char *title, pj_status_t status)
{
- pj_ice_strans_cand *cand;
- pj_stun_session_cb sess_cb;
- pj_stun_tx_data *tdata;
- pj_status_t status;
+ char errmsg[PJ_ERR_MSG_SIZE];
- PJ_ASSERT_RETURN(ice_st && comp, PJ_EINVAL);
-
- /* Bail out if STUN server is still being resolved */
- if (ice_st->has_rjob)
- return PJ_EBUSY;
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(ice_st->obj_name, "%s: %s", title, errmsg));
+
+ if (op==PJ_ICE_STRANS_OP_INIT && ice_st->cb_called)
+ return;
- /* Just return (successfully) if STUN server is not configured */
- if (ice_st->stun_srv.sin_family == 0)
- return PJ_SUCCESS;
+ ice_st->cb_called = PJ_TRUE;
+ if (ice_st->cb.on_ice_complete)
+ (*ice_st->cb.on_ice_complete)(ice_st, op, status);
+}
- /* Create STUN session for this component */
- pj_bzero(&sess_cb, sizeof(sess_cb));
- sess_cb.on_request_complete = &stun_on_request_complete;
- sess_cb.on_send_msg = &stun_on_send_msg;
- status = pj_stun_session_create(&ice_st->stun_cfg, ice_st->obj_name,
- &sess_cb, PJ_FALSE, &comp->stun_sess);
- if (status != PJ_SUCCESS)
- return status;
+/* Update initialization status */
+static void sess_init_update(pj_ice_strans *ice_st)
+{
+ unsigned i;
- /* Associate component with STUN session */
- pj_stun_session_set_user_data(comp->stun_sess, (void*)comp);
+ /* Ignore if init callback has been called */
+ if (ice_st->cb_called)
+ return;
- /* Create STUN binding request */
- status = pj_stun_session_create_req(comp->stun_sess,
- PJ_STUN_BINDING_REQUEST,
- PJ_STUN_MAGIC,
- comp->ka_tsx_id,
- &tdata);
- if (status != PJ_SUCCESS)
- return status;
+ /* Notify application when all candidates have been gathered */
+ for (i=0; i<ice_st->comp_cnt; ++i) {
+ unsigned j;
+ pj_ice_strans_comp *comp = ice_st->comp[i];
- /* Will be attached to tdata in send_msg() */
- cand = &comp->cand_list[comp->cand_cnt];
+ for (j=0; j<comp->cand_cnt; ++j) {
+ pj_ice_sess_cand *cand = &comp->cand_list[j];
- /* Add pending count first, since stun_on_request_complete()
- * may be called before this function completes
- */
- comp->pending_cnt++;
-
- /* Add new alias to this component */
- cand->type = PJ_ICE_CAND_TYPE_SRFLX;
- cand->status = PJ_EPENDING;
- cand->ice_cand_id = -1;
- cand->local_pref = 65535;
- pj_ice_calc_foundation(ice_st->pool, &cand->foundation,
- PJ_ICE_CAND_TYPE_SRFLX, &comp->local_addr);
-
- ++comp->cand_cnt;
-
- /* Send STUN binding request */
- status = pj_stun_session_send_msg(comp->stun_sess, (void*)cand, PJ_FALSE,
- PJ_TRUE, &ice_st->stun_srv,
- sizeof(pj_sockaddr_in), tdata);
- if (status != PJ_SUCCESS) {
- --comp->pending_cnt;
- --comp->cand_cnt;
- return status;
+ if (cand->status == PJ_EPENDING)
+ return;
+ }
}
- return PJ_SUCCESS;
+ /* All candidates have been gathered */
+ ice_st->cb_called = PJ_TRUE;
+ if (ice_st->cb.on_ice_complete)
+ (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT,
+ PJ_SUCCESS);
}
-
/*
- * Create the component.
+ * Destroy ICE stream transport.
*/
-PJ_DEF(pj_status_t) pj_ice_strans_create_comp(pj_ice_strans *ice_st,
- unsigned comp_id,
- pj_uint32_t options,
- const pj_sockaddr_in *addr)
+PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st)
{
- pj_ice_strans_comp *comp = NULL;
- pj_status_t status;
-
- /* Verify arguments */
- PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL);
-
- /* Check that component ID present */
- PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID);
-
- /* Can't add new component while ICE is running */
- PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EBUSY);
-
- /* Can't add new component while resolver is running */
- PJ_ASSERT_RETURN(ice_st->has_rjob == PJ_FALSE, PJ_EBUSY);
-
+ char obj_name[PJ_MAX_OBJ_NAME];
- /* Create component */
- status = create_component(ice_st, comp_id, options, addr, &comp);
- if (status != PJ_SUCCESS)
- return status;
+ PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
- if ((options & PJ_ICE_ST_OPT_DISABLE_STUN) == 0) {
- status = get_stun_mapped_addr(ice_st, comp);
- if (status != PJ_SUCCESS) {
- destroy_component(comp);
- return status;
- }
+ ice_st->destroy_req = PJ_TRUE;
+ if (pj_atomic_get(ice_st->busy_cnt) > 0) {
+ PJ_LOG(5,(ice_st->obj_name,
+ "ICE strans object is busy, will destroy later"));
+ return PJ_EPENDING;
}
+
+ pj_memcpy(obj_name, ice_st->obj_name, PJ_MAX_OBJ_NAME);
+ destroy_ice_st(ice_st);
- /* Store this component */
- ice_st->comp[comp_id-1] = comp;
-
+ PJ_LOG(4,(obj_name, "ICE stream transport destroyed"));
return PJ_SUCCESS;
}
-PJ_DEF(pj_status_t) pj_ice_strans_add_cand( pj_ice_strans *ice_st,
- unsigned comp_id,
- pj_ice_cand_type type,
- pj_uint16_t local_pref,
- const pj_sockaddr_in *addr,
- pj_bool_t set_default)
+/*
+ * Increment busy counter.
+ */
+static void sess_add_ref(pj_ice_strans *ice_st)
{
- pj_ice_strans_comp *comp;
-
-
- PJ_ASSERT_RETURN(ice_st && comp_id && addr, PJ_EINVAL);
- PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJ_EINVAL);
- PJ_ASSERT_RETURN(ice_st->comp[comp_id-1] != NULL, PJ_EINVALIDOP);
-
- comp = ice_st->comp[comp_id-1];
- return add_cand(ice_st, comp, comp_id, type, local_pref, addr,
- set_default);
+ pj_atomic_inc(ice_st->busy_cnt);
}
-
-PJ_DEF(pj_status_t) pj_ice_strans_get_comps_status(pj_ice_strans *ice_st)
+/*
+ * Decrement busy counter. If the counter has reached zero and destroy
+ * has been requested, destroy the object and return FALSE.
+ */
+static pj_bool_t sess_dec_ref(pj_ice_strans *ice_st)
{
- unsigned i;
- pj_status_t worst = PJ_SUCCESS;
-
- for (i=0; i<ice_st->comp_cnt; ++i) {
- pj_ice_strans_comp *comp = ice_st->comp[i];
-
- if (comp->last_status == PJ_SUCCESS) {
- /* okay */
- } else if (comp->pending_cnt && worst==PJ_SUCCESS) {
- worst = PJ_EPENDING;
- break;
- } else if (comp->last_status != PJ_SUCCESS) {
- worst = comp->last_status;
- break;
- }
-
- if (worst != PJ_SUCCESS)
- break;
+ int count = pj_atomic_dec_and_get(ice_st->busy_cnt);
+ pj_assert(count >= 0);
+ if (count==0 && ice_st->destroy_req) {
+ pj_ice_strans_destroy(ice_st);
+ return PJ_FALSE;
+ } else {
+ return PJ_TRUE;
}
+}
- return worst;
+/*
+ * Get user data
+ */
+PJ_DEF(void*) pj_ice_strans_get_user_data(pj_ice_strans *ice_st)
+{
+ PJ_ASSERT_RETURN(ice_st, NULL);
+ return ice_st->user_data;
}
+
/*
* Create ICE!
*/
@@ -832,7 +613,7 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
pj_status_t status;
unsigned i;
pj_ice_sess_cb ice_cb;
- const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 };
+ //const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 };
/* Check arguments */
PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
@@ -848,7 +629,7 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
ice_cb.on_tx_pkt = &ice_tx_pkt;
/* Create! */
- status = pj_ice_sess_create(&ice_st->stun_cfg, ice_st->obj_name, role,
+ status = pj_ice_sess_create(&ice_st->cfg.stun_cfg, ice_st->obj_name, role,
ice_st->comp_cnt, &ice_cb,
local_ufrag, local_passwd, &ice_st->ice);
if (status != PJ_SUCCESS)
@@ -857,6 +638,7 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
/* Associate user data */
ice_st->ice->user_data = (void*)ice_st;
+#if 0
/* If default candidate for components are SRFLX one, upload a custom
* type priority to ICE session so that SRFLX candidates will get
* checked first.
@@ -867,30 +649,44 @@ PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
{
pj_ice_sess_set_prefs(ice_st->ice, srflx_prio);
}
+#endif
-
- /* Add candidates */
+ /* Add components/candidates */
for (i=0; i<ice_st->comp_cnt; ++i) {
unsigned j;
- pj_ice_strans_comp *comp= ice_st->comp[i];
+ pj_ice_strans_comp *comp = ice_st->comp[i];
+
+ /* Re-enable logging for Send/Data indications */
+ if (comp->turn_sock) {
+ PJ_LOG(5,(ice_st->obj_name,
+ "Disabling STUN Indication logging for "
+ "component %d", i+1));
+ pj_turn_sock_set_log(comp->turn_sock, 0xFFFF);
+ }
for (j=0; j<comp->cand_cnt; ++j) {
- pj_ice_strans_cand *cand = &comp->cand_list[j];
+ pj_ice_sess_cand *cand = &comp->cand_list[j];
+ unsigned ice_cand_id;
/* Skip if candidate is not ready */
if (cand->status != PJ_SUCCESS) {
PJ_LOG(5,(ice_st->obj_name,
- "Candidate %d in component %d is not added",
+ "Candidate %d of comp %d is not added (pending)",
j, i));
continue;
}
+ /* Must have address */
+ pj_assert(pj_sockaddr_has_addr(&cand->addr));
+
+ /* Add the candidate */
status = pj_ice_sess_add_cand(ice_st->ice, comp->comp_id,
- cand->type, cand->local_pref,
+ cand->transport_id, cand->type,
+ cand->local_pref,
&cand->foundation, &cand->addr,
- &comp->local_addr, NULL,
- sizeof(pj_sockaddr_in),
- (unsigned*)&cand->ice_cand_id);
+ &cand->base_addr, &cand->rel_addr,
+ pj_sockaddr_get_len(&cand->addr),
+ (unsigned*)&ice_cand_id);
if (status != PJ_SUCCESS)
goto on_error;
}
@@ -907,22 +703,22 @@ on_error:
* Enum candidates
*/
PJ_DEF(pj_status_t) pj_ice_strans_enum_cands(pj_ice_strans *ice_st,
- unsigned *count,
- pj_ice_sess_cand cand[])
+ unsigned comp_id,
+ unsigned *count,
+ pj_ice_sess_cand cand[])
{
unsigned i, cnt;
- pj_ice_sess_cand *pcand;
+ pj_ice_strans_comp *comp;
- PJ_ASSERT_RETURN(ice_st && count && cand, PJ_EINVAL);
- PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP);
+ PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt &&
+ count && cand, PJ_EINVAL);
- cnt = ice_st->ice->lcand_cnt;
+ comp = ice_st->comp[comp_id - 1];
+ cnt = comp->cand_cnt;
cnt = (cnt > *count) ? *count : cnt;
- *count = 0;
for (i=0; i<cnt; ++i) {
- pcand = &ice_st->ice->lcand[i];
- pj_memcpy(&cand[i], pcand, sizeof(pj_ice_sess_cand));
+ pj_memcpy(&cand[i], &comp->cand_list[i], sizeof(pj_ice_sess_cand));
}
*count = cnt;
@@ -930,6 +726,49 @@ PJ_DEF(pj_status_t) pj_ice_strans_enum_cands(pj_ice_strans *ice_st,
}
/*
+ * Get default candidate.
+ */
+PJ_DEF(pj_status_t) pj_ice_strans_get_def_cand( pj_ice_strans *ice_st,
+ unsigned comp_id,
+ pj_ice_sess_cand *cand)
+{
+ const pj_ice_sess_check *valid_pair;
+
+ PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt &&
+ cand, PJ_EINVAL);
+
+ valid_pair = pj_ice_strans_get_valid_pair(ice_st, comp_id);
+ if (valid_pair) {
+ pj_memcpy(cand, valid_pair->lcand, sizeof(pj_ice_sess_cand));
+ } else {
+ pj_ice_strans_comp *comp = ice_st->comp[comp_id - 1];
+ pj_assert(comp->default_cand>=0 && comp->default_cand<comp->cand_cnt);
+ pj_memcpy(cand, &comp->cand_list[comp->default_cand],
+ sizeof(pj_ice_sess_cand));
+ }
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get the current ICE role.
+ */
+PJ_DEF(pj_ice_sess_role) pj_ice_strans_get_role(pj_ice_strans *ice_st)
+{
+ PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_ICE_SESS_ROLE_UNKNOWN);
+ return ice_st->ice->role;
+}
+
+/*
+ * Change session role.
+ */
+PJ_DEF(pj_status_t) pj_ice_strans_change_role( pj_ice_strans *ice_st,
+ pj_ice_sess_role new_role)
+{
+ PJ_ASSERT_RETURN(ice_st && ice_st->ice, PJ_EINVALIDOP);
+ return pj_ice_sess_change_role(ice_st->ice, new_role);
+}
+
+/*
* Start ICE processing !
*/
PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
@@ -940,11 +779,19 @@ PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
{
pj_status_t status;
+ PJ_ASSERT_RETURN(ice_st && rem_ufrag && rem_passwd &&
+ rem_cand_cnt && rem_cand, PJ_EINVAL);
+
+ /* Mark start time */
+ pj_gettimeofday(&ice_st->start_time);
+
+ /* Build check list */
status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd,
rem_cand_cnt, rem_cand);
if (status != PJ_SUCCESS)
return status;
+ /* Start ICE negotiation! */
status = pj_ice_sess_start_check(ice_st->ice);
if (status != PJ_SUCCESS) {
pj_ice_strans_stop_ice(ice_st);
@@ -954,30 +801,36 @@ PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
}
/*
+ * Get valid pair.
+ */
+PJ_DEF(const pj_ice_sess_check*)
+pj_ice_strans_get_valid_pair(const pj_ice_strans *ice_st,
+ unsigned comp_id)
+{
+ PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt,
+ NULL);
+
+ if (ice_st->ice == NULL)
+ return NULL;
+
+ return ice_st->ice->comp[comp_id-1].valid_check;
+}
+
+/*
* Stop ICE!
*/
PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st)
{
- unsigned i;
-
if (ice_st->ice) {
pj_ice_sess_destroy(ice_st->ice);
ice_st->ice = NULL;
}
- /* Invalidate all candidate Ids */
- for (i=0; i<ice_st->comp_cnt; ++i) {
- unsigned j;
- for (j=0; j<ice_st->comp[i]->cand_cnt; ++j) {
- ice_st->comp[i]->cand_list[j].ice_cand_id = -1;
- }
- }
-
return PJ_SUCCESS;
}
/*
- * Send packet using non-ICE means (e.g. when ICE was not negotiated).
+ * Application wants to send outgoing packet.
*/
PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st,
unsigned comp_id,
@@ -997,18 +850,24 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st,
/* If ICE is available, send data with ICE */
if (ice_st->ice) {
- return pj_ice_sess_send_data(ice_st->ice, comp_id, data, data_len);
- }
+ if (comp->turn_sock) {
+ pj_turn_sock_lock(comp->turn_sock);
+ }
+ status = pj_ice_sess_send_data(ice_st->ice, comp_id, data, data_len);
+ if (comp->turn_sock) {
+ pj_turn_sock_unlock(comp->turn_sock);
+ }
+ return status;
- /* Otherwise send direcly with the socket. This is for compatibility
- * with remote that doesn't support ICE.
- */
- pkt_size = data_len;
- status = pj_ioqueue_sendto(comp->key, &comp->write_op,
- data, &pkt_size, 0,
- dst_addr, dst_addr_len);
-
- return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
+ } else if (comp->stun_sock) {
+
+ pkt_size = data_len;
+ status = pj_stun_sock_sendto(comp->stun_sock, NULL, data, data_len,
+ 0, dst_addr, dst_addr_len);
+ return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
+
+ } else
+ return PJ_EINVALIDOP;
}
/*
@@ -1018,9 +877,79 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st,
static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
{
pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
+ pj_time_val t;
+ unsigned msec;
+
+ sess_add_ref(ice_st);
+
+ pj_gettimeofday(&t);
+ PJ_TIME_VAL_SUB(t, ice_st->start_time);
+ msec = PJ_TIME_VAL_MSEC(t);
+
if (ice_st->cb.on_ice_complete) {
- (*ice_st->cb.on_ice_complete)(ice_st, status);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(ice_st->obj_name,
+ "ICE negotiation failed after %ds:%03d: %s",
+ msec/1000, msec%1000, errmsg));
+ } else {
+ unsigned i;
+ enum {
+ msg_disable_ind = 0xFFFF &
+ ~(PJ_STUN_SESS_LOG_TX_IND|
+ PJ_STUN_SESS_LOG_RX_IND)
+ };
+
+ PJ_LOG(4,(ice_st->obj_name,
+ "ICE negotiation success after %ds:%03d",
+ msec/1000, msec%1000));
+
+ for (i=0; i<ice_st->comp_cnt; ++i) {
+ const pj_ice_sess_check *check;
+
+ check = pj_ice_strans_get_valid_pair(ice_st, i+1);
+ if (check) {
+ char lip[PJ_INET6_ADDRSTRLEN+10];
+ char rip[PJ_INET6_ADDRSTRLEN+10];
+
+ pj_sockaddr_print(&check->lcand->addr, lip,
+ sizeof(lip), 3);
+ pj_sockaddr_print(&check->rcand->addr, rip,
+ sizeof(rip), 3);
+
+ if (check->lcand->transport_id == TP_TURN) {
+ /* Disable logging for Send/Data indications */
+ PJ_LOG(5,(ice_st->obj_name,
+ "Disabling STUN Indication logging for "
+ "component %d", i+1));
+ pj_turn_sock_set_log(ice_st->comp[i]->turn_sock,
+ msg_disable_ind);
+ }
+
+ PJ_LOG(4,(ice_st->obj_name, " Comp %d: "
+ "sending from %s candidate %s to "
+ "%s candidate %s",
+ i+1,
+ pj_ice_get_cand_type_name(check->lcand->type),
+ lip,
+ pj_ice_get_cand_type_name(check->rcand->type),
+ rip));
+
+ } else {
+ PJ_LOG(4,(ice_st->obj_name,
+ "Comp %d: disabled", i+1));
+ }
+ }
+ }
+
+ (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION,
+ status);
+
+
}
+
+ sess_dec_ref(ice_st);
}
/*
@@ -1028,30 +957,42 @@ static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
*/
static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
unsigned comp_id,
+ unsigned transport_id,
const void *pkt, pj_size_t size,
const pj_sockaddr_t *dst_addr,
unsigned dst_addr_len)
{
pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
- pj_ice_strans_comp *comp = NULL;
- pj_ssize_t pkt_size;
+ pj_ice_strans_comp *comp;
pj_status_t status;
- PJ_TODO(TX_TO_RELAY);
-
PJ_ASSERT_RETURN(comp_id && comp_id <= ice_st->comp_cnt, PJ_EINVAL);
+
comp = ice_st->comp[comp_id-1];
TRACE_PKT((comp->ice_st->obj_name,
- "Component %d TX packet to %s:%d",
- comp_id,
+ "Component %d TX packet to %s:%d with transport %d",
+ comp_id,
pj_inet_ntoa(((pj_sockaddr_in*)dst_addr)->sin_addr),
- (int)pj_ntohs(((pj_sockaddr_in*)dst_addr)->sin_port)));
-
- pkt_size = size;
- status = pj_ioqueue_sendto(comp->key, &comp->write_op,
- pkt, &pkt_size, 0,
- dst_addr, dst_addr_len);
+ (int)pj_ntohs(((pj_sockaddr_in*)dst_addr)->sin_port),
+ transport_id));
+
+ if (transport_id == TP_TURN) {
+ if (comp->turn_sock) {
+ status = pj_turn_sock_sendto(comp->turn_sock,
+ (const pj_uint8_t*)pkt, size,
+ dst_addr, dst_addr_len);
+ } else {
+ status = PJ_EINVALIDOP;
+ }
+ } else if (transport_id == TP_STUN) {
+ status = pj_stun_sock_sendto(comp->stun_sock, NULL,
+ pkt, size, 0,
+ dst_addr, dst_addr_len);
+ } else {
+ pj_assert(!"Invalid transport ID");
+ status = PJ_EINVALIDOP;
+ }
return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
}
@@ -1061,148 +1002,295 @@ static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
*/
static void ice_rx_data(pj_ice_sess *ice,
unsigned comp_id,
+ unsigned transport_id,
void *pkt, pj_size_t size,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
+ PJ_UNUSED_ARG(transport_id);
+
if (ice_st->cb.on_rx_data) {
(*ice_st->cb.on_rx_data)(ice_st, comp_id, pkt, size,
src_addr, src_addr_len);
}
}
-/*
- * Callback called by STUN session to send outgoing packet.
+/* Notification when incoming packet has been received from
+ * the STUN socket.
*/
-static pj_status_t stun_on_send_msg(pj_stun_session *sess,
- void *token,
- const void *pkt,
- pj_size_t size,
- const pj_sockaddr_t *dst_addr,
- unsigned dst_addr_len)
+static pj_bool_t stun_on_rx_data(pj_stun_sock *stun_sock,
+ void *pkt,
+ unsigned pkt_len,
+ const pj_sockaddr_t *src_addr,
+ unsigned addr_len)
{
pj_ice_strans_comp *comp;
- pj_ssize_t pkt_size;
+ pj_ice_strans *ice_st;
pj_status_t status;
- PJ_UNUSED_ARG(token);
+ comp = (pj_ice_strans_comp*) pj_stun_sock_get_user_data(stun_sock);
+ ice_st = comp->ice_st;
- comp = (pj_ice_strans_comp*) pj_stun_session_get_user_data(sess);
- pkt_size = size;
- status = pj_ioqueue_sendto(comp->key, &comp->write_op,
- pkt, &pkt_size, 0,
- dst_addr, dst_addr_len);
-
- return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
+ sess_add_ref(ice_st);
+
+ if (ice_st->ice == NULL) {
+ /* The ICE session is gone, but we're still receiving packets.
+ * This could also happen if remote doesn't do ICE. So just
+ * report this to application.
+ */
+ if (ice_st->cb.on_rx_data) {
+ (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id, pkt, pkt_len,
+ src_addr, addr_len);
+ }
+
+ } else {
+
+ /* Hand over the packet to ICE session */
+ status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id,
+ TP_STUN, pkt, pkt_len,
+ src_addr, addr_len);
+
+ if (status != PJ_SUCCESS) {
+ ice_st_perror(comp->ice_st, "Error processing packet",
+ status);
+ }
+ }
+
+ return sess_dec_ref(ice_st);
}
-/*
- * Callback sent by STUN session when outgoing STUN request has
- * completed.
+/* Notifification when asynchronous send operation to the STUN socket
+ * has completed.
*/
-static void stun_on_request_complete(pj_stun_session *sess,
- pj_status_t status,
- void *token,
- pj_stun_tx_data *tdata,
- const pj_stun_msg *response,
- const pj_sockaddr_t *src_addr,
- unsigned src_addr_len)
+static pj_bool_t stun_on_data_sent(pj_stun_sock *stun_sock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent)
+{
+ PJ_UNUSED_ARG(stun_sock);
+ PJ_UNUSED_ARG(send_key);
+ PJ_UNUSED_ARG(sent);
+ return PJ_TRUE;
+}
+
+/* Notification when the status of the STUN transport has changed. */
+static pj_bool_t stun_on_status(pj_stun_sock *stun_sock,
+ pj_stun_sock_op op,
+ pj_status_t status)
{
pj_ice_strans_comp *comp;
- pj_ice_strans_cand *cand = NULL;
- pj_stun_xor_mapped_addr_attr *xa;
- pj_stun_mapped_addr_attr *ma;
- pj_sockaddr *mapped_addr;
- char ip[20];
-
- comp = (pj_ice_strans_comp*) pj_stun_session_get_user_data(sess);
- cand = (pj_ice_strans_cand*) token;
-
- PJ_UNUSED_ARG(token);
- PJ_UNUSED_ARG(tdata);
- PJ_UNUSED_ARG(src_addr);
- PJ_UNUSED_ARG(src_addr_len);
-
- if (cand == NULL) {
- /* This is keep-alive */
+ pj_ice_strans *ice_st;
+ pj_ice_sess_cand *cand = NULL;
+ unsigned i;
+
+ comp = (pj_ice_strans_comp*) pj_stun_sock_get_user_data(stun_sock);
+ ice_st = comp->ice_st;
+
+ sess_add_ref(ice_st);
+
+ /* Find the srflx cancidate */
+ for (i=0; i<comp->cand_cnt; ++i) {
+ if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_SRFLX) {
+ cand = &comp->cand_list[i];
+ break;
+ }
+ }
+
+ pj_assert(status != PJ_EPENDING);
+
+ switch (op) {
+ case PJ_STUN_SOCK_DNS_OP:
if (status != PJ_SUCCESS) {
- ice_st_perror(comp->ice_st, "STUN keep-alive request failed",
- status);
+ /* May not have cand, e.g. when error during init */
+ if (cand)
+ cand->status = status;
+ sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT, "DNS resolution failed",
+ status);
+ }
+ break;
+ case PJ_STUN_SOCK_BINDING_OP:
+ if (status == PJ_SUCCESS) {
+ pj_stun_sock_info info;
+
+ status = pj_stun_sock_get_info(stun_sock, &info);
+ if (status == PJ_SUCCESS) {
+ char ipaddr[PJ_INET6_ADDRSTRLEN+10];
+ pj_bool_t dup = PJ_FALSE;
+
+ /* Eliminate the srflx candidate if the address is
+ * equal to other (host) candidates.
+ */
+ for (i=0; i<comp->cand_cnt; ++i) {
+ if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_HOST &&
+ pj_sockaddr_cmp(&comp->cand_list[i].addr,
+ &info.mapped_addr) == 0)
+ {
+ dup = PJ_TRUE;
+ break;
+ }
+ }
+
+ if (dup) {
+ /* Duplicate found, remove the srflx candidate */
+ pj_array_erase(comp->cand_list, sizeof(comp->cand_list[0]),
+ comp->cand_cnt, cand - comp->cand_list);
+ --comp->cand_cnt;
+ } else {
+ /* Otherwise update the address */
+ pj_sockaddr_cp(&cand->addr, &info.mapped_addr);
+ cand->status = PJ_SUCCESS;
+ }
+
+ PJ_LOG(4,(comp->ice_st->obj_name,
+ "Comp %d: Binding discovery complete, "
+ "srflx address is %s",
+ comp->comp_id,
+ pj_sockaddr_print(&info.mapped_addr, ipaddr,
+ sizeof(ipaddr), 3)));
+
+ sess_init_update(ice_st);
+ }
+ }
+
+ if (status != PJ_SUCCESS) {
+ /* May not have cand, e.g. when error during init */
+ if (cand)
+ cand->status = status;
+ sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT,
+ "STUN binding request failed", status);
}
+ break;
+ case PJ_STUN_SOCK_KEEP_ALIVE_OP:
+ if (status != PJ_SUCCESS) {
+ pj_assert(cand != NULL);
+ cand->status = status;
+ sess_fail(ice_st, PJ_ICE_STRANS_OP_INIT,
+ "STUN keep-alive failed", status);
+ }
+ break;
+ }
+
+ return sess_dec_ref(ice_st);
+}
+
+/* Callback when TURN socket has received a packet */
+static void turn_on_rx_data(pj_turn_sock *turn_sock,
+ void *pkt,
+ unsigned pkt_len,
+ const pj_sockaddr_t *peer_addr,
+ unsigned addr_len)
+{
+ pj_ice_strans_comp *comp;
+ pj_status_t status;
+
+ comp = (pj_ice_strans_comp*) pj_turn_sock_get_user_data(turn_sock);
+ if (comp == NULL) {
+ /* We have disassociated ourselves from the TURN socket */
return;
}
- /* Decrement pending count for this component */
- pj_assert(comp->pending_cnt > 0);
- comp->pending_cnt--;
+ sess_add_ref(comp->ice_st);
- if (status == PJNATH_ESTUNTIMEDOUT) {
+ if (comp->ice_st->ice == NULL) {
+ /* The ICE session is gone, but we're still receiving packets.
+ * This could also happen if remote doesn't do ICE and application
+ * specifies TURN as the default address in SDP.
+ * So in this case just give the packet to application.
+ */
+ if (comp->ice_st->cb.on_rx_data) {
+ (*comp->ice_st->cb.on_rx_data)(comp->ice_st, comp->comp_id, pkt,
+ pkt_len, peer_addr, addr_len);
+ }
- PJ_LOG(4,(comp->ice_st->obj_name,
- "STUN Binding request has timed-out, will retry "
- "again alter"));
+ } else {
- /* Restart keep-alive timer */
- start_ka_timer(comp->ice_st);
- return;
+ /* Hand over the packet to ICE */
+ status = pj_ice_sess_on_rx_pkt(comp->ice_st->ice, comp->comp_id,
+ TP_TURN, pkt, pkt_len,
+ peer_addr, addr_len);
- } else if (status != PJ_SUCCESS) {
- comp->last_status = cand->status = status;
- ice_st_perror(comp->ice_st, "STUN Binding request failed",
- cand->status);
- return;
+ if (status != PJ_SUCCESS) {
+ ice_st_perror(comp->ice_st,
+ "Error processing packet from TURN relay",
+ status);
+ }
}
- xa = (pj_stun_xor_mapped_addr_attr*)
- pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0);
- ma = (pj_stun_mapped_addr_attr*)
- pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0);
-
- if (xa)
- mapped_addr = &xa->sockaddr;
- else if (ma)
- mapped_addr = &ma->sockaddr;
- else {
- cand->status = PJNATH_ESTUNNOMAPPEDADDR;
- ice_st_perror(comp->ice_st, "STUN Binding request failed",
- cand->status);
+ sess_dec_ref(comp->ice_st);
+}
+
+
+/* Callback when TURN client state has changed */
+static void turn_on_state(pj_turn_sock *turn_sock, pj_turn_state_t old_state,
+ pj_turn_state_t new_state)
+{
+ pj_ice_strans_comp *comp;
+
+ comp = (pj_ice_strans_comp*) pj_turn_sock_get_user_data(turn_sock);
+ if (comp == NULL) {
+ /* Not interested in further state notification once the relay is
+ * disconnecting.
+ */
return;
}
- /* Save IP address for logging */
- pj_ansi_strcpy(ip, pj_inet_ntoa(comp->local_addr.ipv4.sin_addr));
+ PJ_LOG(5,(comp->ice_st->obj_name, "TURN client state changed %s --> %s",
+ pj_turn_state_name(old_state), pj_turn_state_name(new_state)));
+
+ sess_add_ref(comp->ice_st);
+
+ if (new_state == PJ_TURN_STATE_READY) {
+ pj_turn_session_info rel_info;
+ char ipaddr[PJ_INET6_ADDRSTRLEN+8];
+ pj_ice_sess_cand *cand = NULL;
+ unsigned i;
+
+ /* Get allocation info */
+ pj_turn_sock_get_info(turn_sock, &rel_info);
+
+ /* Find relayed candidate in the component */
+ for (i=0; i<comp->cand_cnt; ++i) {
+ if (comp->cand_list[i].type == PJ_ICE_CAND_TYPE_RELAYED) {
+ cand = &comp->cand_list[i];
+ break;
+ }
+ }
+ pj_assert(cand != NULL);
+
+ /* Update candidate */
+ pj_sockaddr_cp(&cand->addr, &rel_info.relay_addr);
+ pj_sockaddr_cp(&cand->base_addr, &rel_info.relay_addr);
+ pj_sockaddr_cp(&cand->rel_addr, &rel_info.mapped_addr);
+ pj_ice_calc_foundation(comp->ice_st->pool, &cand->foundation,
+ PJ_ICE_CAND_TYPE_RELAYED,
+ &rel_info.relay_addr);
+ cand->status = PJ_SUCCESS;
- /* Ignore response if it reports the same address */
- if (comp->local_addr.ipv4.sin_addr.s_addr == mapped_addr->ipv4.sin_addr.s_addr &&
- comp->local_addr.ipv4.sin_port == mapped_addr->ipv4.sin_port)
- {
PJ_LOG(4,(comp->ice_st->obj_name,
- "Candidate %s:%d is directly connected to Internet, "
- "STUN mapped address is ignored",
- ip, pj_ntohs(comp->local_addr.ipv4.sin_port)));
- return;
+ "Comp %d: TURN allocation complete, relay address is %s",
+ comp->comp_id,
+ pj_sockaddr_print(&rel_info.relay_addr, ipaddr,
+ sizeof(ipaddr), 3)));
+
+ sess_init_update(comp->ice_st);
+
+ } else if (new_state >= PJ_TURN_STATE_DEALLOCATING) {
+ pj_turn_session_info info;
+
+ pj_turn_sock_get_info(turn_sock, &info);
+
+ /* Unregister ourself from the TURN relay */
+ pj_turn_sock_set_user_data(turn_sock, NULL);
+ comp->turn_sock = NULL;
+
+ /* Set session to fail if we're still initializing */
+ if (old_state < PJ_TURN_STATE_READY) {
+ sess_fail(comp->ice_st, PJ_ICE_STRANS_OP_INIT,
+ "TURN relay failed", info.last_status);
+ }
}
- PJ_LOG(5,(comp->ice_st->obj_name,
- "STUN mapped address for %s:%d is %s:%d",
- ip, (int)pj_ntohs(comp->local_addr.ipv4.sin_port),
- pj_inet_ntoa(mapped_addr->ipv4.sin_addr),
- (int)pj_ntohs(mapped_addr->ipv4.sin_port)));
- pj_memcpy(&cand->addr, mapped_addr, sizeof(pj_sockaddr_in));
- cand->status = PJ_SUCCESS;
-
- /* Set this candidate as the default candidate */
- comp->default_cand = (cand - comp->cand_list);
- comp->last_status = PJ_SUCCESS;
-
- /* We have STUN, so we must start the keep-alive timer */
- start_ka_timer(comp->ice_st);
-
- /* Notify app that STUN address has changed. */
- if (comp->ice_st->cb.on_addr_change)
- (*comp->ice_st->cb.on_addr_change)(comp->ice_st, comp->comp_id,
- (cand - comp->cand_list));
+ sess_dec_ref(comp->ice_st);
}
diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
index 21315f3b..97aaeb27 100644
--- a/pjnath/src/pjnath/stun_msg.c
+++ b/pjnath/src/pjnath/stun_msg.c
@@ -1557,7 +1557,7 @@ PJ_DEF(pj_status_t) pj_stun_unknown_attr_create(pj_pool_t *pool,
/* Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. */
PJ_DEF(pj_status_t) pj_stun_msg_add_unknown_attr(pj_pool_t *pool,
pj_stun_msg *msg,
- unsigned attr_cnt,
+ pj_size_t attr_cnt,
const pj_uint16_t attr_types[])
{
pj_stun_unknown_attr *attr = NULL;
@@ -1646,7 +1646,7 @@ static void* clone_unknown_attr(pj_pool_t *pool, const void *src)
PJ_DEF(pj_status_t) pj_stun_binary_attr_create(pj_pool_t *pool,
int attr_type,
const pj_uint8_t *data,
- unsigned length,
+ pj_size_t length,
pj_stun_binary_attr **p_attr)
{
pj_stun_binary_attr *attr;
@@ -1673,7 +1673,7 @@ PJ_DEF(pj_status_t) pj_stun_msg_add_binary_attr(pj_pool_t *pool,
pj_stun_msg *msg,
int attr_type,
const pj_uint8_t *data,
- unsigned length)
+ pj_size_t length)
{
pj_stun_binary_attr *attr = NULL;
pj_status_t status;
@@ -1833,10 +1833,10 @@ PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg,
/*
* Check that the PDU is potentially a valid STUN message.
*/
-PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, unsigned pdu_len,
+PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len,
unsigned options)
{
- unsigned msg_len;
+ pj_size_t msg_len;
PJ_ASSERT_RETURN(pdu, PJ_EINVAL);
@@ -1938,10 +1938,10 @@ PJ_DEF(pj_status_t) pj_stun_msg_create_response(pj_pool_t *pool,
*/
PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
const pj_uint8_t *pdu,
- unsigned pdu_len,
+ pj_size_t pdu_len,
unsigned options,
pj_stun_msg **p_msg,
- unsigned *p_parsed_len,
+ pj_size_t *p_parsed_len,
pj_stun_msg **p_response)
{
@@ -2190,9 +2190,9 @@ static char *print_binary(const pj_uint8_t *data, unsigned data_len)
*/
PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
pj_uint8_t *buf, unsigned buf_size,
- unsigned options,
+ pj_size_t options,
const pj_str_t *key,
- unsigned *p_msg_len)
+ pj_size_t *p_msg_len)
{
pj_uint8_t *start = buf;
pj_stun_msgint_attr *amsgint = NULL;
diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c
index 6545c750..6e7d4ef3 100644
--- a/pjnath/src/pjnath/stun_session.c
+++ b/pjnath/src/pjnath/stun_session.c
@@ -29,14 +29,23 @@ struct pj_stun_session
pj_stun_session_cb cb;
void *user_data;
+ pj_atomic_t *busy;
+ pj_bool_t destroy_request;
+
pj_bool_t use_fingerprint;
+ pj_pool_t *rx_pool;
+
+#if PJ_LOG_MAX_LEVEL >= 5
char dump_buf[1000];
+#endif
+ unsigned log_flag;
pj_stun_auth_type auth_type;
pj_stun_auth_cred cred;
int auth_retry;
pj_str_t next_nonce;
+ pj_str_t server_realm;
pj_str_t srv_name;
@@ -79,7 +88,7 @@ static pj_stun_tsx_cb tsx_cb =
static pj_status_t tsx_add(pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
- pj_list_push_back(&sess->pending_request_list, tdata);
+ pj_list_push_front(&sess->pending_request_list, tdata);
return PJ_SUCCESS;
}
@@ -138,11 +147,13 @@ static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx)
pj_stun_tx_data *tdata;
tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
+ tsx_erase(tdata->sess, tdata);
+
pj_stun_client_tsx_destroy(tsx);
pj_pool_release(tdata->pool);
}
-static void destroy_tdata(pj_stun_tx_data *tdata)
+static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force)
{
if (tdata->res_timer.id != PJ_FALSE) {
pj_timer_heap_cancel(tdata->sess->cfg->timer_heap,
@@ -151,14 +162,21 @@ static void destroy_tdata(pj_stun_tx_data *tdata)
pj_list_erase(tdata);
}
- if (tdata->client_tsx) {
- pj_time_val delay = {2, 0};
- tsx_erase(tdata->sess, tdata);
- pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay);
- tdata->client_tsx = NULL;
+ if (force) {
+ if (tdata->client_tsx) {
+ tsx_erase(tdata->sess, tdata);
+ pj_stun_client_tsx_destroy(tdata->client_tsx);
+ }
+ pj_pool_release(tdata->pool);
} else {
- pj_pool_release(tdata->pool);
+ if (tdata->client_tsx) {
+ pj_time_val delay = {2, 0};
+ pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay);
+
+ } else {
+ pj_pool_release(tdata->pool);
+ }
}
}
@@ -169,7 +187,7 @@ PJ_DEF(void) pj_stun_msg_destroy_tdata( pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
PJ_UNUSED_ARG(sess);
- destroy_tdata(tdata);
+ destroy_tdata(tdata, PJ_FALSE);
}
@@ -289,6 +307,7 @@ static pj_status_t handle_auth_challenge(pj_stun_session *sess,
ea->err_code == PJ_STUN_SC_STALE_NONCE)
{
const pj_stun_nonce_attr *anonce;
+ const pj_stun_realm_attr *arealm;
pj_stun_tx_data *tdata;
unsigned i;
pj_status_t status;
@@ -316,6 +335,13 @@ static pj_status_t handle_auth_challenge(pj_stun_session *sess,
/* Save next_nonce */
pj_strdup(sess->pool, &sess->next_nonce, &anonce->value);
+ /* Copy the realm from the response */
+ arealm = (pj_stun_realm_attr*)
+ pj_stun_msg_find_attr(response, PJ_STUN_ATTR_REALM, 0);
+ if (arealm) {
+ pj_strdup(sess->pool, &sess->server_realm, &arealm->value);
+ }
+
/* Create new request */
status = pj_stun_session_create_req(sess, request->msg->hdr.type,
request->msg->hdr.magic,
@@ -324,7 +350,8 @@ static pj_status_t handle_auth_challenge(pj_stun_session *sess,
return status;
/* Duplicate all the attributes in the old request, except
- * USERNAME, REALM, M-I, and NONCE
+ * USERNAME, REALM, M-I, and NONCE, which will be filled in
+ * later.
*/
for (i=0; i<request->msg->attr_count; ++i) {
const pj_stun_attr_hdr *asrc = request->msg->attr[i];
@@ -373,6 +400,10 @@ static void stun_tsx_on_complete(pj_stun_client_tsx *tsx,
tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
sess = tdata->sess;
+ /* Lock the session and prevent user from destroying us in the callback */
+ pj_atomic_inc(sess->busy);
+ pj_lock_acquire(sess->lock);
+
/* Handle authentication challenge */
handle_auth_challenge(sess, tdata, response, src_addr,
src_addr_len, &notify_user);
@@ -387,6 +418,13 @@ static void stun_tsx_on_complete(pj_stun_client_tsx *tsx,
*/
pj_stun_msg_destroy_tdata(sess, tdata);
tdata = NULL;
+
+ pj_lock_release(sess->lock);
+
+ if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) {
+ pj_stun_session_destroy(sess);
+ return;
+ }
}
static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx,
@@ -394,12 +432,27 @@ static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx,
pj_size_t pkt_size)
{
pj_stun_tx_data *tdata;
+ pj_stun_session *sess;
+ pj_status_t status;
tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
+ sess = tdata->sess;
+
+ /* Lock the session and prevent user from destroying us in the callback */
+ pj_atomic_inc(sess->busy);
+ pj_lock_acquire(sess->lock);
+
+ status = sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt,
+ pkt_size, tdata->dst_addr,
+ tdata->addr_len);
+ pj_lock_release(sess->lock);
- return tdata->sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt,
- pkt_size, tdata->dst_addr,
- tdata->addr_len);
+ if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) {
+ pj_stun_session_destroy(sess);
+ return PJNATH_ESTUNDESTROYED;
+ } else {
+ return status;
+ }
}
/* **************************************************************************/
@@ -428,11 +481,16 @@ PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_config *cfg,
sess->pool = pool;
pj_memcpy(&sess->cb, cb, sizeof(*cb));
sess->use_fingerprint = fingerprint;
+ sess->log_flag = 0xFFFF;
sess->srv_name.ptr = (char*) pj_pool_alloc(pool, 32);
sess->srv_name.slen = pj_ansi_snprintf(sess->srv_name.ptr, 32,
"pj_stun-%s", pj_get_version());
+ sess->rx_pool = pj_pool_create(sess->cfg->pf, "name",
+ PJNATH_POOL_LEN_STUN_TDATA,
+ PJNATH_POOL_INC_STUN_TDATA, NULL);
+
pj_list_init(&sess->pending_request_list);
pj_list_init(&sess->cached_response_list);
@@ -443,6 +501,13 @@ PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_config *cfg,
}
sess->delete_lock = PJ_TRUE;
+ status = pj_atomic_create(pool, 0, &sess->busy);
+ if (status != PJ_SUCCESS) {
+ pj_lock_destroy(sess->lock);
+ pj_pool_release(pool);
+ return status;
+ }
+
*p_sess = sess;
return PJ_SUCCESS;
@@ -453,13 +518,22 @@ PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess)
PJ_ASSERT_RETURN(sess, PJ_EINVAL);
pj_lock_acquire(sess->lock);
+
+ /* Can't destroy if we're in a callback */
+ sess->destroy_request = PJ_TRUE;
+ if (pj_atomic_get(sess->busy)) {
+ pj_lock_release(sess->lock);
+ return PJ_EPENDING;
+ }
+
while (!pj_list_empty(&sess->pending_request_list)) {
pj_stun_tx_data *tdata = sess->pending_request_list.next;
- destroy_tdata(tdata);
+ destroy_tdata(tdata, PJ_TRUE);
}
+
while (!pj_list_empty(&sess->cached_response_list)) {
pj_stun_tx_data *tdata = sess->cached_response_list.next;
- destroy_tdata(tdata);
+ destroy_tdata(tdata, PJ_TRUE);
}
pj_lock_release(sess->lock);
@@ -467,6 +541,11 @@ PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess)
pj_lock_destroy(sess->lock);
}
+ if (sess->rx_pool) {
+ pj_pool_release(sess->rx_pool);
+ sess->rx_pool = NULL;
+ }
+
pj_pool_release(sess->pool);
return PJ_SUCCESS;
@@ -538,12 +617,19 @@ PJ_DEF(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess,
return PJ_SUCCESS;
}
+PJ_DEF(void) pj_stun_session_set_log( pj_stun_session *sess,
+ unsigned flags)
+{
+ PJ_ASSERT_ON_FAIL(sess, return);
+ sess->log_flag = flags;
+}
static pj_status_t get_auth(pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
if (sess->cred.type == PJ_STUN_AUTH_CRED_STATIC) {
- tdata->auth_info.realm = sess->cred.data.static_cred.realm;
+ //tdata->auth_info.realm = sess->cred.data.static_cred.realm;
+ tdata->auth_info.realm = sess->server_realm;
tdata->auth_info.username = sess->cred.data.static_cred.username;
tdata->auth_info.nonce = sess->cred.data.static_cred.nonce;
@@ -633,6 +719,7 @@ PJ_DEF(pj_status_t) pj_stun_session_create_req(pj_stun_session *sess,
return status;
}
tdata->auth_info.nonce = sess->next_nonce;
+ tdata->auth_info.realm = sess->server_realm;
}
} else {
@@ -714,8 +801,18 @@ PJ_DEF(pj_status_t) pj_stun_session_create_res( pj_stun_session *sess,
static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg,
unsigned pkt_size, const pj_sockaddr_t *addr)
{
- char dst_name[80];
+ char dst_name[PJ_INET6_ADDRSTRLEN+10];
+ if ((PJ_STUN_IS_REQUEST(msg->hdr.type) &&
+ (sess->log_flag & PJ_STUN_SESS_LOG_TX_REQ)==0) ||
+ (PJ_STUN_IS_RESPONSE(msg->hdr.type) &&
+ (sess->log_flag & PJ_STUN_SESS_LOG_TX_RES)==0) ||
+ (PJ_STUN_IS_INDICATION(msg->hdr.type) &&
+ (sess->log_flag & PJ_STUN_SESS_LOG_TX_IND)==0))
+ {
+ return;
+ }
+
pj_sockaddr_print(addr, dst_name, sizeof(dst_name), 3);
PJ_LOG(5,(SNAME(sess),
@@ -749,7 +846,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
tdata->token = token;
tdata->retransmit = retransmit;
- /* Start locking the session now */
+ /* Lock the session and prevent user from destroying us in the callback */
+ pj_atomic_inc(sess->busy);
pj_lock_acquire(sess->lock);
/* Apply options */
@@ -757,9 +855,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
tdata->msg);
if (status != PJ_SUCCESS) {
pj_stun_msg_destroy_tdata(sess, tdata);
- pj_lock_release(sess->lock);
LOG_ERR_(sess, "Error applying options", status);
- return status;
+ goto on_return;
}
/* Encode message */
@@ -769,9 +866,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
&tdata->pkt_size);
if (status != PJ_SUCCESS) {
pj_stun_msg_destroy_tdata(sess, tdata);
- pj_lock_release(sess->lock);
LOG_ERR_(sess, "STUN encode() error", status);
- return status;
+ goto on_return;
}
/* Dump packet */
@@ -797,9 +893,8 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
tdata->pkt, tdata->pkt_size);
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
pj_stun_msg_destroy_tdata(sess, tdata);
- pj_lock_release(sess->lock);
LOG_ERR_(sess, "Error sending STUN request", status);
- return status;
+ goto on_return;
}
/* Add to pending request list */
@@ -824,10 +919,10 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
&tdata->res_timer,
&timeout);
if (status != PJ_SUCCESS) {
+ tdata->res_timer.id = PJ_FALSE;
pj_stun_msg_destroy_tdata(sess, tdata);
- pj_lock_release(sess->lock);
LOG_ERR_(sess, "Error scheduling response timer", status);
- return status;
+ goto on_return;
}
pj_list_push_back(&sess->cached_response_list, tdata);
@@ -838,7 +933,9 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
tdata->pkt_size, server, addr_len);
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
LOG_ERR_(sess, "Error sending STUN request", status);
+ goto on_return;
}
/* Destroy only when response is not cached*/
@@ -847,8 +944,15 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
}
}
-
+on_return:
pj_lock_release(sess->lock);
+
+ /* Check if application has called destroy() in the callback */
+ if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) {
+ pj_stun_session_destroy(sess);
+ return PJNATH_ESTUNDESTROYED;
+ }
+
return status;
}
@@ -892,6 +996,8 @@ PJ_DEF(pj_status_t) pj_stun_session_cancel_req( pj_stun_session *sess,
PJ_ASSERT_RETURN(!notify || notify_status!=PJ_SUCCESS, PJ_EINVAL);
PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL);
+ /* Lock the session and prevent user from destroying us in the callback */
+ pj_atomic_inc(sess->busy);
pj_lock_acquire(sess->lock);
if (notify) {
@@ -903,6 +1009,12 @@ PJ_DEF(pj_status_t) pj_stun_session_cancel_req( pj_stun_session *sess,
pj_stun_msg_destroy_tdata(sess, tdata);
pj_lock_release(sess->lock);
+
+ if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) {
+ pj_stun_session_destroy(sess);
+ return PJNATH_ESTUNDESTROYED;
+ }
+
return PJ_SUCCESS;
}
@@ -917,12 +1029,19 @@ PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess,
PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL);
+ /* Lock the session and prevent user from destroying us in the callback */
+ pj_atomic_inc(sess->busy);
pj_lock_acquire(sess->lock);
status = pj_stun_client_tsx_retransmit(tdata->client_tsx);
pj_lock_release(sess->lock);
+ if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) {
+ pj_stun_session_destroy(sess);
+ return PJNATH_ESTUNDESTROYED;
+ }
+
return status;
}
@@ -1165,6 +1284,36 @@ static pj_status_t on_incoming_indication(pj_stun_session *sess,
}
+/* Print outgoing message to log */
+static void dump_rx_msg(pj_stun_session *sess, const pj_stun_msg *msg,
+ unsigned pkt_size, const pj_sockaddr_t *addr)
+{
+ char src_info[PJ_INET6_ADDRSTRLEN+10];
+
+ if ((PJ_STUN_IS_REQUEST(msg->hdr.type) &&
+ (sess->log_flag & PJ_STUN_SESS_LOG_RX_REQ)==0) ||
+ (PJ_STUN_IS_RESPONSE(msg->hdr.type) &&
+ (sess->log_flag & PJ_STUN_SESS_LOG_RX_RES)==0) ||
+ (PJ_STUN_IS_INDICATION(msg->hdr.type) &&
+ (sess->log_flag & PJ_STUN_SESS_LOG_RX_IND)==0))
+ {
+ return;
+ }
+
+ pj_sockaddr_print(addr, src_info, sizeof(src_info), 3);
+
+ PJ_LOG(5,(SNAME(sess),
+ "RX %d bytes STUN message from %s:\n"
+ "--- begin STUN message ---\n"
+ "%s"
+ "--- end of STUN message ---\n",
+ pkt_size, src_info,
+ pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf),
+ NULL)));
+
+}
+
+/* Incoming packet */
PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
const void *packet,
pj_size_t pkt_size,
@@ -1175,47 +1324,34 @@ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
unsigned src_addr_len)
{
pj_stun_msg *msg, *response;
- pj_pool_t *tmp_pool;
- char *dump;
pj_status_t status;
PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL);
- tmp_pool = pj_pool_create(sess->cfg->pf, "tmpstun",
- PJNATH_POOL_LEN_STUN_TDATA,
- PJNATH_POOL_INC_STUN_TDATA, NULL);
- if (!tmp_pool)
- return PJ_ENOMEM;
+ /* Lock the session and prevent user from destroying us in the callback */
+ pj_atomic_inc(sess->busy);
+ pj_lock_acquire(sess->lock);
+
+ /* Reset pool */
+ pj_pool_reset(sess->rx_pool);
/* Try to parse the message */
- status = pj_stun_msg_decode(tmp_pool, (const pj_uint8_t*)packet,
+ status = pj_stun_msg_decode(sess->rx_pool, (const pj_uint8_t*)packet,
pkt_size, options,
&msg, parsed_len, &response);
if (status != PJ_SUCCESS) {
LOG_ERR_(sess, "STUN msg_decode() error", status);
if (response) {
- send_response(sess, token, tmp_pool, response, NULL,
+ send_response(sess, token, sess->rx_pool, response, NULL,
PJ_FALSE, src_addr, src_addr_len);
}
- pj_pool_release(tmp_pool);
- return status;
+ goto on_return;
}
- dump = (char*) pj_pool_alloc(tmp_pool, PJ_STUN_MAX_PKT_LEN);
-
- PJ_LOG(5,(SNAME(sess),
- "RX STUN message from %s:%d:\n"
- "--- begin STUN message ---\n"
- "%s"
- "--- end of STUN message ---\n",
- pj_inet_ntoa(((pj_sockaddr_in*)src_addr)->sin_addr),
- pj_ntohs(((pj_sockaddr_in*)src_addr)->sin_port),
- pj_stun_msg_dump(msg, dump, PJ_STUN_MAX_PKT_LEN, NULL)));
-
- pj_lock_acquire(sess->lock);
+ dump_rx_msg(sess, msg, pkt_size, src_addr);
/* For requests, check if we have cached response */
- status = check_cached_response(sess, tmp_pool, msg,
+ status = check_cached_response(sess, sess->rx_pool, msg,
src_addr, src_addr_len);
if (status == PJ_SUCCESS) {
goto on_return;
@@ -1231,13 +1367,13 @@ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
} else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
- status = on_incoming_request(sess, options, token, tmp_pool,
+ status = on_incoming_request(sess, options, token, sess->rx_pool,
(const pj_uint8_t*) packet, pkt_size,
msg, src_addr, src_addr_len);
} else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) {
- status = on_incoming_indication(sess, token, tmp_pool,
+ status = on_incoming_indication(sess, token, sess->rx_pool,
(const pj_uint8_t*) packet, pkt_size,
msg, src_addr, src_addr_len);
@@ -1249,9 +1385,14 @@ PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
on_return:
pj_lock_release(sess->lock);
- pj_pool_release(tmp_pool);
+ /* If we've received destroy request while we're on the callback,
+ * destroy the session now.
+ */
+ if (pj_atomic_dec_and_get(sess->busy)==0 && sess->destroy_request) {
+ pj_stun_session_destroy(sess);
+ return PJNATH_ESTUNDESTROYED;
+ }
+
return status;
}
-
-
diff --git a/pjnath/src/pjnath/stun_sock.c b/pjnath/src/pjnath/stun_sock.c
new file mode 100644
index 00000000..8ff5a1d6
--- /dev/null
+++ b/pjnath/src/pjnath/stun_sock.c
@@ -0,0 +1,829 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2007 Benny Prijono <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 <pjnath/stun_sock.h>
+#include <pjnath/errno.h>
+#include <pjnath/stun_transaction.h>
+#include <pjnath/stun_session.h>
+#include <pjlib-util/srv_resolver.h>
+#include <pj/activesock.h>
+#include <pj/addr_resolv.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/ip_helper.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+
+
+struct pj_stun_sock
+{
+ char *obj_name; /* Log identification */
+ pj_pool_t *pool; /* Pool */
+ void *user_data; /* Application user data */
+
+ int af; /* Address family */
+ pj_stun_config stun_cfg; /* STUN config (ioqueue etc)*/
+ pj_stun_sock_cb cb; /* Application callbacks */
+
+ int ka_interval; /* Keep alive interval */
+ pj_timer_entry ka_timer; /* Keep alive timer. */
+
+ pj_sockaddr srv_addr; /* Resolved server addr */
+ pj_sockaddr mapped_addr; /* Our public address */
+
+ pj_dns_async_query *q; /* Pending DNS query */
+ pj_sock_t sock_fd; /* Socket descriptor */
+ pj_activesock_t *active_sock; /* Active socket object */
+ pj_ioqueue_op_key_t send_key; /* Default send key for app */
+ pj_ioqueue_op_key_t int_send_key; /* Send key for internal */
+
+ pj_uint16_t tsx_id[6]; /* .. to match STUN msg */
+ pj_stun_session *stun_sess; /* STUN session */
+
+};
+
+/*
+ * Prototypes for static functions
+ */
+
+/* This callback is called by the STUN session to send packet */
+static pj_status_t sess_on_send_msg(pj_stun_session *sess,
+ void *token,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+
+/* This callback is called by the STUN session when outgoing transaction
+ * is complete
+ */
+static void sess_on_request_complete(pj_stun_session *sess,
+ pj_status_t status,
+ void *token,
+ pj_stun_tx_data *tdata,
+ const pj_stun_msg *response,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+/* DNS resolver callback */
+static void dns_srv_resolver_cb(void *user_data,
+ pj_status_t status,
+ const pj_dns_srv_record *rec);
+
+/* Start sending STUN Binding request */
+static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock);
+
+/* Callback from active socket when incoming packet is received */
+static pj_bool_t on_data_recvfrom(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ int addr_len,
+ pj_status_t status);
+
+/* Callback from active socket about send status */
+static pj_bool_t on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+
+/* Schedule keep-alive timer */
+static void start_ka_timer(pj_stun_sock *stun_sock);
+
+/* Keep-alive timer callback */
+static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te);
+
+#define INTERNAL_MSG_TOKEN (void*)1
+
+
+/*
+ * Retrieve the name representing the specified operation.
+ */
+PJ_DEF(const char*) pj_stun_sock_op_name(pj_stun_sock_op op)
+{
+ const char *names[] = {
+ "?",
+ "DNS resolution",
+ "STUN Binding request",
+ "Keep-alive"
+ };
+
+ return op <= PJ_STUN_SOCK_KEEP_ALIVE_OP ? names[op] : "?";
+};
+
+
+/*
+ * Initialize the STUN transport setting with its default values.
+ */
+PJ_DEF(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->max_pkt_size = PJ_STUN_SOCK_PKT_LEN;
+ cfg->async_cnt = 1;
+ cfg->ka_interval = PJ_STUN_KEEP_ALIVE_SEC;
+}
+
+
+/* Check that configuration setting is valid */
+static pj_bool_t pj_stun_sock_cfg_is_valid(const pj_stun_sock_cfg *cfg)
+{
+ return cfg->max_pkt_size > 1 && cfg->async_cnt >= 1;
+}
+
+/*
+ * Create the STUN transport using the specified configuration.
+ */
+PJ_DEF(pj_status_t) pj_stun_sock_create( pj_stun_config *stun_cfg,
+ const char *name,
+ int af,
+ const pj_stun_sock_cb *cb,
+ const pj_stun_sock_cfg *cfg,
+ void *user_data,
+ pj_stun_sock **p_stun_sock)
+{
+ pj_pool_t *pool;
+ pj_stun_sock *stun_sock;
+ pj_stun_sock_cfg default_cfg;
+ unsigned i;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(stun_cfg && cb && p_stun_sock, PJ_EINVAL);
+ PJ_ASSERT_RETURN(af==pj_AF_INET()||af==pj_AF_INET6(), PJ_EAFNOTSUP);
+ PJ_ASSERT_RETURN(!cfg || pj_stun_sock_cfg_is_valid(cfg), PJ_EINVAL);
+ PJ_ASSERT_RETURN(cb->on_status, PJ_EINVAL);
+
+ status = pj_stun_config_check_valid(stun_cfg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (name == NULL)
+ name = "stuntp%p";
+
+ if (cfg == NULL) {
+ pj_stun_sock_cfg_default(&default_cfg);
+ cfg = &default_cfg;
+ }
+
+
+ /* Create structure */
+ pool = pj_pool_create(stun_cfg->pf, name, 256, 512, NULL);
+ stun_sock = PJ_POOL_ZALLOC_T(pool, pj_stun_sock);
+ stun_sock->pool = pool;
+ stun_sock->obj_name = pool->obj_name;
+ stun_sock->user_data = user_data;
+ stun_sock->af = af;
+ stun_sock->sock_fd = PJ_INVALID_SOCKET;
+ pj_memcpy(&stun_sock->stun_cfg, stun_cfg, sizeof(*stun_cfg));
+ pj_memcpy(&stun_sock->cb, cb, sizeof(*cb));
+
+ stun_sock->ka_interval = cfg->ka_interval;
+ if (stun_sock->ka_interval == 0)
+ stun_sock->ka_interval = PJ_STUN_KEEP_ALIVE_SEC;
+
+ /* Create socket and bind socket */
+ status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &stun_sock->sock_fd);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (pj_sockaddr_has_addr(&cfg->bound_addr)) {
+ status = pj_sock_bind(stun_sock->sock_fd, &cfg->bound_addr,
+ pj_sockaddr_get_len(&cfg->bound_addr));
+ } else {
+ pj_sockaddr bound_addr;
+
+ pj_sockaddr_init(af, &bound_addr, NULL, 0);
+ status = pj_sock_bind(stun_sock->sock_fd, &bound_addr,
+ pj_sockaddr_get_len(&bound_addr));
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Create more useful information string about this transport */
+#if 0
+ {
+ pj_sockaddr bound_addr;
+ int addr_len = sizeof(bound_addr);
+
+ status = pj_sock_getsockname(stun_sock->sock_fd, &bound_addr,
+ &addr_len);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ stun_sock->info = pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+10);
+ pj_sockaddr_print(&bound_addr, stun_sock->info,
+ PJ_INET6_ADDRSTRLEN, 3);
+ }
+#endif
+
+ /* Init active socket configuration */
+ {
+ pj_activesock_cfg activesock_cfg;
+ pj_activesock_cb activesock_cb;
+
+ pj_activesock_cfg_default(&activesock_cfg);
+ activesock_cfg.async_cnt = cfg->async_cnt;
+ activesock_cfg.concurrency = 0;
+
+ /* Create the active socket */
+ pj_bzero(&activesock_cb, sizeof(activesock_cb));
+ activesock_cb.on_data_recvfrom = &on_data_recvfrom;
+ activesock_cb.on_data_sent = &on_data_sent;
+ status = pj_activesock_create(pool, stun_sock->sock_fd,
+ pj_SOCK_DGRAM(),
+ &activesock_cfg, stun_cfg->ioqueue,
+ &activesock_cb, stun_sock,
+ &stun_sock->active_sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Start asynchronous read operations */
+ status = pj_activesock_start_recvfrom(stun_sock->active_sock, pool,
+ cfg->max_pkt_size, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init send keys */
+ pj_ioqueue_op_key_init(&stun_sock->send_key,
+ sizeof(stun_sock->send_key));
+ pj_ioqueue_op_key_init(&stun_sock->int_send_key,
+ sizeof(stun_sock->int_send_key));
+ }
+
+ /* Create STUN session */
+ {
+ pj_stun_session_cb sess_cb;
+
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_request_complete = &sess_on_request_complete;
+ sess_cb.on_send_msg = &sess_on_send_msg;
+ status = pj_stun_session_create(&stun_sock->stun_cfg,
+ stun_sock->obj_name,
+ &sess_cb, PJ_FALSE,
+ &stun_sock->stun_sess);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Associate us with the STUN session */
+ pj_stun_session_set_user_data(stun_sock->stun_sess, stun_sock);
+
+ /* Initialize random numbers to be used as STUN transaction ID for
+ * outgoing Binding request. We use the 80bit number to distinguish
+ * STUN messages we sent with STUN messages that the application sends.
+ * The last 16bit value in the array is a counter.
+ */
+ for (i=0; i<PJ_ARRAY_SIZE(stun_sock->tsx_id); ++i) {
+ stun_sock->tsx_id[i] = (pj_uint16_t) pj_rand();
+ }
+ stun_sock->tsx_id[5] = 0;
+
+
+ /* Init timer entry */
+ stun_sock->ka_timer.cb = &ka_timer_cb;
+ stun_sock->ka_timer.user_data = stun_sock;
+
+ /* Done */
+ *p_stun_sock = stun_sock;
+ return PJ_SUCCESS;
+
+on_error:
+ pj_stun_sock_destroy(stun_sock);
+ return status;
+}
+
+/* Start socket. */
+PJ_DEF(pj_status_t) pj_stun_sock_start( pj_stun_sock *stun_sock,
+ const pj_str_t *domain,
+ pj_uint16_t default_port,
+ pj_dns_resolver *resolver)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(stun_sock && domain && default_port, PJ_EINVAL);
+
+ /* Check whether the domain contains IP address */
+ stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)stun_sock->af;
+ status = pj_inet_pton(stun_sock->af, domain,
+ pj_sockaddr_get_addr(&stun_sock->srv_addr));
+ if (status != PJ_SUCCESS) {
+ stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)0;
+ }
+
+ /* If resolver is set, try to resolve with DNS SRV first. It
+ * will fallback to DNS A/AAAA when no SRV record is found.
+ */
+ if (status != PJ_SUCCESS && resolver) {
+ const pj_str_t res_name = pj_str("_stun._udp.");
+ unsigned opt;
+
+ pj_assert(stun_sock->q == NULL);
+
+ opt = PJ_DNS_SRV_FALLBACK_A;
+ if (stun_sock->af == pj_AF_INET6()) {
+ opt |= (PJ_DNS_SRV_RESOLVE_AAAA | PJ_DNS_SRV_FALLBACK_AAAA);
+ }
+
+ status = pj_dns_srv_resolve(domain, &res_name, default_port,
+ stun_sock->pool, resolver, opt,
+ stun_sock, &dns_srv_resolver_cb,
+ &stun_sock->q);
+
+ /* Processing will resume when the DNS SRV callback is called */
+ return status;
+
+ } else {
+
+ if (status != PJ_SUCCESS) {
+ pj_addrinfo ai;
+ unsigned cnt = 1;
+
+ status = pj_getaddrinfo(stun_sock->af, domain, &cnt, &ai);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_sockaddr_cp(&stun_sock->srv_addr, &ai.ai_addr);
+ }
+
+ pj_sockaddr_set_port(&stun_sock->srv_addr, (pj_uint16_t)default_port);
+
+ /* Start sending Binding request */
+ return get_mapped_addr(stun_sock);
+ }
+}
+
+/* Destroy */
+PJ_DEF(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *stun_sock)
+{
+ if (stun_sock->q) {
+ pj_dns_resolver_cancel_query(stun_sock->q, PJ_FALSE);
+ stun_sock->q = NULL;
+ }
+
+ /* Destroy the active socket first just in case we'll get
+ * stray callback.
+ */
+ if (stun_sock->active_sock != NULL) {
+ pj_activesock_close(stun_sock->active_sock);
+ stun_sock->active_sock = NULL;
+ stun_sock->sock_fd = PJ_INVALID_SOCKET;
+ } else if (stun_sock->sock_fd != PJ_INVALID_SOCKET) {
+ pj_sock_close(stun_sock->sock_fd);
+ stun_sock->sock_fd = PJ_INVALID_SOCKET;
+ }
+
+ if (stun_sock->ka_timer.id != 0) {
+ pj_timer_heap_cancel(stun_sock->stun_cfg.timer_heap,
+ &stun_sock->ka_timer);
+ stun_sock->ka_timer.id = 0;
+ }
+
+ if (stun_sock->stun_sess) {
+ pj_stun_session_destroy(stun_sock->stun_sess);
+ stun_sock->stun_sess = NULL;
+ }
+
+ if (stun_sock->pool) {
+ pj_pool_t *pool = stun_sock->pool;
+ stun_sock->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Associate user data */
+PJ_DEF(pj_status_t) pj_stun_sock_set_user_data( pj_stun_sock *stun_sock,
+ void *user_data)
+{
+ PJ_ASSERT_RETURN(stun_sock, PJ_EINVAL);
+ stun_sock->user_data = user_data;
+ return PJ_SUCCESS;
+}
+
+
+/* Get user data */
+PJ_DEF(void*) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock)
+{
+ PJ_ASSERT_RETURN(stun_sock, NULL);
+ return stun_sock->user_data;
+}
+
+/* Notify application that session has failed */
+static pj_bool_t sess_fail(pj_stun_sock *stun_sock,
+ pj_stun_sock_op op,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_bool_t ret;
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(stun_sock->obj_name, "Session failed because %s failed: %s",
+ pj_stun_sock_op_name(op), errmsg));
+
+ ret = (*stun_sock->cb.on_status)(stun_sock, op, status);
+
+ return ret;
+}
+
+/* DNS resolver callback */
+static void dns_srv_resolver_cb(void *user_data,
+ pj_status_t status,
+ const pj_dns_srv_record *rec)
+{
+ pj_stun_sock *stun_sock = (pj_stun_sock*) user_data;
+
+ /* Clear query */
+ stun_sock->q = NULL;
+
+ /* Handle error */
+ if (status != PJ_SUCCESS) {
+ sess_fail(stun_sock, PJ_STUN_SOCK_DNS_OP, status);
+ return;
+ }
+
+ pj_assert(rec->count);
+ pj_assert(rec->entry[0].server.addr_count);
+
+ PJ_TODO(SUPPORT_IPV6_IN_RESOLVER);
+ pj_assert(stun_sock->af == pj_AF_INET());
+
+ /* Set the address */
+ pj_sockaddr_in_init(&stun_sock->srv_addr.ipv4, NULL,
+ rec->entry[0].port);
+ stun_sock->srv_addr.ipv4.sin_addr = rec->entry[0].server.addr[0];
+
+ /* Start sending Binding request */
+ get_mapped_addr(stun_sock);
+}
+
+
+/* Start sending STUN Binding request */
+static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+
+ /* Increment request counter and create STUN Binding request */
+ ++stun_sock->tsx_id[5];
+ status = pj_stun_session_create_req(stun_sock->stun_sess,
+ PJ_STUN_BINDING_REQUEST,
+ PJ_STUN_MAGIC,
+ (const pj_uint8_t*)stun_sock->tsx_id,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Send request */
+ status=pj_stun_session_send_msg(stun_sock->stun_sess, INTERNAL_MSG_TOKEN,
+ PJ_FALSE, PJ_TRUE, &stun_sock->srv_addr,
+ pj_sockaddr_get_len(&stun_sock->srv_addr),
+ tdata);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ sess_fail(stun_sock, PJ_STUN_SOCK_BINDING_OP, status);
+ return status;
+}
+
+/* Get info */
+PJ_DEF(pj_status_t) pj_stun_sock_get_info( pj_stun_sock *stun_sock,
+ pj_stun_sock_info *info)
+{
+ int addr_len;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(stun_sock && info, PJ_EINVAL);
+
+ /* Copy STUN server address and mapped address */
+ pj_memcpy(&info->srv_addr, &stun_sock->srv_addr,
+ sizeof(pj_sockaddr));
+ pj_memcpy(&info->mapped_addr, &stun_sock->mapped_addr,
+ sizeof(pj_sockaddr));
+
+ /* Retrieve bound address */
+ addr_len = sizeof(info->bound_addr);
+ status = pj_sock_getsockname(stun_sock->sock_fd, &info->bound_addr,
+ &addr_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If socket is bound to a specific interface, then only put that
+ * interface in the alias list. Otherwise query all the interfaces
+ * in the host.
+ */
+ if (pj_sockaddr_has_addr(&info->bound_addr)) {
+ info->alias_cnt = 1;
+ pj_sockaddr_cp(&info->aliases[0], &info->bound_addr);
+ } else {
+ unsigned i;
+
+ /* Enum all IP interfaces in the host */
+ info->alias_cnt = PJ_ARRAY_SIZE(info->aliases);
+ status = pj_enum_ip_interface(stun_sock->af, &info->alias_cnt,
+ info->aliases);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set the port number for each address.
+ */
+ if (stun_sock->af == pj_AF_INET()) {
+ for (i=0; i<info->alias_cnt; ++i) {
+ pj_sockaddr_set_port(&info->aliases[i],
+ pj_sockaddr_get_port(&info->bound_addr));
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Send application data */
+PJ_DEF(pj_status_t) pj_stun_sock_sendto( pj_stun_sock *stun_sock,
+ pj_ioqueue_op_key_t *send_key,
+ const void *pkt,
+ unsigned pkt_len,
+ unsigned flag,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ pj_ssize_t size;
+ PJ_ASSERT_RETURN(stun_sock && pkt && dst_addr && addr_len, PJ_EINVAL);
+
+ if (send_key==NULL)
+ send_key = &stun_sock->send_key;
+
+ size = pkt_len;
+ return pj_activesock_sendto(stun_sock->active_sock, send_key,
+ pkt, &size, flag, dst_addr, addr_len);
+}
+
+/* This callback is called by the STUN session to send packet */
+static pj_status_t sess_on_send_msg(pj_stun_session *sess,
+ void *token,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ pj_stun_sock *stun_sock;
+ pj_ssize_t size;
+
+ stun_sock = (pj_stun_sock *) pj_stun_session_get_user_data(sess);
+
+ pj_assert(token==INTERNAL_MSG_TOKEN);
+ PJ_UNUSED_ARG(token);
+
+ size = pkt_size;
+ return pj_activesock_sendto(stun_sock->active_sock,
+ &stun_sock->int_send_key,
+ pkt, &size, 0, dst_addr, addr_len);
+}
+
+/* This callback is called by the STUN session when outgoing transaction
+ * is complete
+ */
+static void sess_on_request_complete(pj_stun_session *sess,
+ pj_status_t status,
+ void *token,
+ pj_stun_tx_data *tdata,
+ const pj_stun_msg *response,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_sock *stun_sock;
+ const pj_stun_sockaddr_attr *mapped_attr;
+ pj_stun_sock_op op;
+ pj_bool_t mapped_changed;
+ pj_bool_t resched = PJ_TRUE;
+
+ stun_sock = (pj_stun_sock *) pj_stun_session_get_user_data(sess);
+
+ PJ_UNUSED_ARG(tdata);
+ PJ_UNUSED_ARG(token);
+ PJ_UNUSED_ARG(src_addr);
+ PJ_UNUSED_ARG(src_addr_len);
+
+ /* Check if this is a keep-alive or the first Binding request */
+ if (pj_sockaddr_has_addr(&stun_sock->mapped_addr))
+ op = PJ_STUN_SOCK_KEEP_ALIVE_OP;
+ else
+ op = PJ_STUN_SOCK_BINDING_OP;
+
+ /* Handle failure */
+ if (status != PJ_SUCCESS) {
+ resched = sess_fail(stun_sock, op, status);
+ goto on_return;
+ }
+
+ /* Get XOR-MAPPED-ADDRESS, or MAPPED-ADDRESS when XOR-MAPPED-ADDRESS
+ * doesn't exist.
+ */
+ mapped_attr = (const pj_stun_sockaddr_attr*)
+ pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+ 0);
+ if (mapped_attr==NULL) {
+ mapped_attr = (const pj_stun_sockaddr_attr*)
+ pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR,
+ 0);
+ }
+
+ if (mapped_attr == NULL) {
+ resched = sess_fail(stun_sock, op, PJNATH_ESTUNNOMAPPEDADDR);
+ goto on_return;
+ }
+
+ /* Determine if mapped address has changed, and save the new mapped
+ * address and call callback if so
+ */
+ mapped_changed = !pj_sockaddr_has_addr(&stun_sock->mapped_addr) ||
+ pj_sockaddr_cmp(&stun_sock->mapped_addr,
+ &mapped_attr->sockaddr) != 0;
+ if (mapped_changed) {
+ /* Print mapped adress */
+ {
+ char addrinfo[PJ_INET6_ADDRSTRLEN+10];
+ PJ_LOG(4,(stun_sock->obj_name,
+ "STUN mapped address found/changed: %s",
+ pj_sockaddr_print(&mapped_attr->sockaddr,
+ addrinfo, sizeof(addrinfo), 3)));
+ }
+
+ pj_sockaddr_cp(&stun_sock->mapped_addr, &mapped_attr->sockaddr);
+
+ resched = (*stun_sock->cb.on_status)(stun_sock, op, PJ_SUCCESS);
+
+ goto on_return;
+ }
+
+on_return:
+ /* Start/restart keep-alive timer */
+ if (resched)
+ start_ka_timer(stun_sock);
+}
+
+/* Schedule keep-alive timer */
+static void start_ka_timer(pj_stun_sock *stun_sock)
+{
+ if (stun_sock->ka_timer.id != 0) {
+ pj_timer_heap_cancel(stun_sock->stun_cfg.timer_heap,
+ &stun_sock->ka_timer);
+ stun_sock->ka_timer.id = 0;
+ }
+
+ pj_assert(stun_sock->ka_interval != 0);
+ if (stun_sock->ka_interval > 0) {
+ pj_time_val delay;
+
+ delay.sec = stun_sock->ka_interval;
+ delay.msec = 0;
+
+ if (pj_timer_heap_schedule(stun_sock->stun_cfg.timer_heap,
+ &stun_sock->ka_timer,
+ &delay) == PJ_SUCCESS)
+ {
+ stun_sock->ka_timer.id = PJ_TRUE;
+ }
+ }
+}
+
+/* Keep-alive timer callback */
+static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
+{
+ pj_stun_sock *stun_sock;
+
+ stun_sock = (pj_stun_sock *) te->user_data;
+
+ PJ_UNUSED_ARG(th);
+
+ /* Time to send STUN Binding request */
+ if (get_mapped_addr(stun_sock) != PJ_SUCCESS)
+ return;
+
+ /* Next keep-alive timer will be scheduled once the request
+ * is complete.
+ */
+}
+
+/* Callback from active socket when incoming packet is received */
+static pj_bool_t on_data_recvfrom(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ const pj_sockaddr_t *src_addr,
+ int addr_len,
+ pj_status_t status)
+{
+ pj_stun_sock *stun_sock;
+ pj_stun_msg_hdr *hdr;
+ pj_uint16_t type;
+
+ stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock);
+
+ /* Log socket error */
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(stun_sock->obj_name, "recvfrom() error: %s", errmsg));
+ return PJ_TRUE;
+ }
+
+ /* Check that this is STUN message */
+ status = pj_stun_msg_check((const pj_uint8_t*)data, size,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET);
+ if (status != PJ_SUCCESS) {
+ /* Not STUN -- give it to application */
+ goto process_app_data;
+ }
+
+ /* Treat packet as STUN header and copy the STUN message type.
+ * We don't want to access the type directly from the header
+ * since it may not be properly aligned.
+ */
+ hdr = (pj_stun_msg_hdr*) data;
+ pj_memcpy(&type, &hdr->type, 2);
+ type = pj_ntohs(type);
+
+ /* If the packet is a STUN Binding response and part of the
+ * transaction ID matches our internal ID, then this is
+ * our internal STUN message (Binding request or keep alive).
+ * Give it to our STUN session.
+ */
+ if (!PJ_STUN_IS_RESPONSE(type) ||
+ PJ_STUN_GET_METHOD(type) != PJ_STUN_BINDING_METHOD ||
+ pj_memcmp(hdr->tsx_id, stun_sock->tsx_id, 10) != 0)
+ {
+ /* Not STUN Binding response, or STUN transaction ID mismatch.
+ * This is not our message too -- give it to application.
+ */
+ goto process_app_data;
+ }
+
+ /* This is our STUN Binding response. Give it to the STUN session */
+ status = pj_stun_session_on_rx_pkt(stun_sock->stun_sess, data, size,
+ PJ_STUN_IS_DATAGRAM, NULL, NULL,
+ src_addr, addr_len);
+ return status!=PJNATH_ESTUNDESTROYED ? PJ_TRUE : PJ_FALSE;
+
+process_app_data:
+ if (stun_sock->cb.on_rx_data) {
+ pj_bool_t ret;
+
+ ret = (*stun_sock->cb.on_rx_data)(stun_sock, data, size,
+ src_addr, addr_len);
+ return ret;
+ }
+
+ return PJ_TRUE;
+}
+
+/* Callback from active socket about send status */
+static pj_bool_t on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent)
+{
+ pj_stun_sock *stun_sock;
+
+ stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock);
+
+ /* Don't report to callback if this is internal message */
+ if (send_key == &stun_sock->int_send_key) {
+ return PJ_TRUE;
+ }
+
+ /* Report to callback */
+ if (stun_sock->cb.on_data_sent) {
+ pj_bool_t ret;
+
+ /* If app gives NULL send_key in sendto() function, then give
+ * NULL in the callback too
+ */
+ if (send_key == &stun_sock->send_key)
+ send_key = NULL;
+
+ /* Call callback */
+ ret = (*stun_sock->cb.on_data_sent)(stun_sock, send_key, sent);
+
+ return ret;
+ }
+
+ return PJ_TRUE;
+}
+
diff --git a/pjnath/src/pjnath/stun_transaction.c b/pjnath/src/pjnath/stun_transaction.c
index ebee152a..1242dfe7 100644
--- a/pjnath/src/pjnath/stun_transaction.c
+++ b/pjnath/src/pjnath/stun_transaction.c
@@ -31,16 +31,17 @@
struct pj_stun_client_tsx
{
char obj_name[PJ_MAX_OBJ_NAME];
- pj_stun_config *cfg;
pj_stun_tsx_cb cb;
void *user_data;
pj_bool_t complete;
pj_bool_t require_retransmit;
+ unsigned rto_msec;
pj_timer_entry retransmit_timer;
unsigned transmit_count;
pj_time_val retransmit_time;
+ pj_timer_heap_t *timer_heap;
pj_timer_entry destroy_timer;
@@ -70,7 +71,8 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_create(pj_stun_config *cfg,
PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL);
tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx);
- tsx->cfg = cfg;
+ tsx->rto_msec = cfg->rto_msec;
+ tsx->timer_heap = cfg->timer_heap;
pj_memcpy(&tsx->cb, cb, sizeof(*cb));
tsx->retransmit_timer.cb = &retransmit_timer_callback;
@@ -99,22 +101,23 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy(
/* Cancel previously registered timer */
if (tsx->destroy_timer.id != 0) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->destroy_timer);
+ pj_timer_heap_cancel(tsx->timer_heap, &tsx->destroy_timer);
tsx->destroy_timer.id = 0;
}
/* Stop retransmission, just in case */
if (tsx->retransmit_timer.id != 0) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer);
+ pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
tsx->retransmit_timer.id = 0;
}
- status = pj_timer_heap_schedule(tsx->cfg->timer_heap,
+ status = pj_timer_heap_schedule(tsx->timer_heap,
&tsx->destroy_timer, delay);
if (status != PJ_SUCCESS)
return status;
tsx->destroy_timer.id = TIMER_ACTIVE;
+ tsx->cb.on_complete = NULL;
return PJ_SUCCESS;
}
@@ -128,11 +131,11 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx)
PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
if (tsx->retransmit_timer.id != 0) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer);
+ pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
tsx->retransmit_timer.id = 0;
}
if (tsx->destroy_timer.id != 0) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->destroy_timer);
+ pj_timer_heap_cancel(tsx->timer_heap, &tsx->destroy_timer);
tsx->destroy_timer.id = 0;
}
@@ -186,7 +189,7 @@ static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx)
/* Calculate retransmit/timeout delay */
if (tsx->transmit_count == 0) {
tsx->retransmit_time.sec = 0;
- tsx->retransmit_time.msec = tsx->cfg->rto_msec;
+ tsx->retransmit_time.msec = tsx->rto_msec;
} else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT-1) {
unsigned msec;
@@ -205,7 +208,7 @@ static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx)
* cancel it (as opposed to when schedule_timer() failed we cannot
* cancel transmission).
*/;
- status = pj_timer_heap_schedule(tsx->cfg->timer_heap,
+ status = pj_timer_heap_schedule(tsx->timer_heap,
&tsx->retransmit_timer,
&tsx->retransmit_time);
if (status != PJ_SUCCESS) {
@@ -223,9 +226,12 @@ static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx)
/* Send message */
status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size);
- if (status != PJ_SUCCESS) {
+
+ if (status == PJNATH_ESTUNDESTROYED) {
+ /* We've been destroyed, don't access the object. */
+ } else if (status != PJ_SUCCESS) {
if (tsx->retransmit_timer.id != 0) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap,
+ pj_timer_heap_cancel(tsx->timer_heap,
&tsx->retransmit_timer);
tsx->retransmit_timer.id = 0;
}
@@ -279,12 +285,15 @@ static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0);
}
}
+ /* We might have been destroyed, don't try to access the object */
return;
}
tsx->retransmit_timer.id = 0;
status = tsx_transmit_msg(tsx);
- if (status != PJ_SUCCESS) {
+ if (status == PJNATH_ESTUNDESTROYED) {
+ /* We've been destroyed, don't try to access the object */
+ } else if (status != PJ_SUCCESS) {
tsx->retransmit_timer.id = 0;
if (!tsx->complete) {
tsx->complete = PJ_TRUE;
@@ -292,6 +301,7 @@ static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
tsx->cb.on_complete(tsx, status, NULL, NULL, 0);
}
}
+ /* We might have been destroyed, don't try to access the object */
}
}
@@ -305,7 +315,7 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx)
}
if (tsx->retransmit_timer.id != 0) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer);
+ pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
tsx->retransmit_timer.id = 0;
}
@@ -351,7 +361,7 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
* We can cancel retransmit timer now.
*/
if (tsx->retransmit_timer.id) {
- pj_timer_heap_cancel(tsx->cfg->timer_heap, &tsx->retransmit_timer);
+ pj_timer_heap_cancel(tsx->timer_heap, &tsx->retransmit_timer);
tsx->retransmit_timer.id = 0;
}
@@ -384,6 +394,7 @@ PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
if (tsx->cb.on_complete) {
tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len);
}
+ /* We might have been destroyed, don't try to access the object */
}
return PJ_SUCCESS;
diff --git a/pjnath/src/pjnath/turn_session.c b/pjnath/src/pjnath/turn_session.c
index 6c662d66..0176e139 100644
--- a/pjnath/src/pjnath/turn_session.c
+++ b/pjnath/src/pjnath/turn_session.c
@@ -29,8 +29,9 @@
#include <pj/pool.h>
#include <pj/sock.h>
-#define MAX_SRV_CNT 4
-#define REFRESH_SEC_BEFORE 60
+#define PJ_TURN_CHANNEL_MIN 0x4000
+#define PJ_TURN_CHANNEL_MAX 0xFFFE /* inclusive */
+#define PJ_TURN_PEER_HTABLE_SIZE 8
static const char *state_names[] =
{
@@ -66,11 +67,13 @@ struct pj_turn_session
const char *obj_name;
pj_turn_session_cb cb;
void *user_data;
+ pj_stun_config stun_cfg;
pj_lock_t *lock;
int busy;
pj_turn_state_t state;
+ pj_status_t last_status;
pj_bool_t pending_destroy;
pj_bool_t destroy_notified;
@@ -87,7 +90,7 @@ struct pj_turn_session
pj_uint16_t default_port;
pj_uint16_t af;
- pj_turn_tp_type tp_type;
+ pj_turn_tp_type conn_type;
pj_uint16_t srv_addr_cnt;
pj_sockaddr *srv_addr_list;
pj_sockaddr *srv_addr;
@@ -95,6 +98,7 @@ struct pj_turn_session
pj_bool_t pending_alloc;
pj_turn_alloc_param alloc_param;
+ pj_sockaddr mapped_addr;
pj_sockaddr relay_addr;
pj_hash_table_t *peer_table;
@@ -176,13 +180,13 @@ PJ_DEF(const char*) pj_turn_state_name(pj_turn_state_t state)
/*
* Create TURN client session.
*/
-PJ_DEF(pj_status_t) pj_turn_session_create( pj_stun_config *cfg,
+PJ_DEF(pj_status_t) pj_turn_session_create( const pj_stun_config *cfg,
const char *name,
int af,
- pj_turn_tp_type tp_type,
+ pj_turn_tp_type conn_type,
const pj_turn_session_cb *cb,
- void *user_data,
unsigned options,
+ void *user_data,
pj_turn_session **p_sess)
{
pj_pool_t *pool;
@@ -206,11 +210,14 @@ PJ_DEF(pj_status_t) pj_turn_session_create( pj_stun_config *cfg,
sess->obj_name = pool->obj_name;
sess->timer_heap = cfg->timer_heap;
sess->af = (pj_uint16_t)af;
- sess->tp_type = tp_type;
+ sess->conn_type = conn_type;
sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC;
sess->user_data = user_data;
sess->next_ch = PJ_TURN_CHANNEL_MIN;
+ /* Copy STUN session */
+ pj_memcpy(&sess->stun_cfg, cfg, sizeof(pj_stun_config));
+
/* Copy callback */
pj_memcpy(&sess->cb, cb, sizeof(*cb));
@@ -233,8 +240,8 @@ PJ_DEF(pj_status_t) pj_turn_session_create( pj_stun_config *cfg,
stun_cb.on_send_msg = &stun_on_send_msg;
stun_cb.on_request_complete = &stun_on_request_complete;
stun_cb.on_rx_indication = &stun_on_rx_indication;
- status = pj_stun_session_create(cfg, sess->obj_name, &stun_cb, PJ_FALSE,
- &sess->stun);
+ status = pj_stun_session_create(&sess->stun_cfg, sess->obj_name, &stun_cb,
+ PJ_FALSE, &sess->stun);
if (status != PJ_SUCCESS) {
do_destroy(sess);
return status;
@@ -333,9 +340,10 @@ static void sess_shutdown(pj_turn_session *sess,
case PJ_TURN_STATE_NULL:
break;
case PJ_TURN_STATE_RESOLVING:
- pj_assert(sess->dns_async != NULL);
- pj_dns_resolver_cancel_query(sess->dns_async, PJ_FALSE);
- sess->dns_async = NULL;
+ if (sess->dns_async != NULL) {
+ pj_dns_resolver_cancel_query(sess->dns_async, PJ_FALSE);
+ sess->dns_async = NULL;
+ }
break;
case PJ_TURN_STATE_RESOLVED:
break;
@@ -365,13 +373,13 @@ static void sess_shutdown(pj_turn_session *sess,
/* Schedule destroy */
pj_time_val delay = {0, 0};
+ set_state(sess, PJ_TURN_STATE_DESTROYING);
+
if (sess->timer.id != TIMER_NONE) {
pj_timer_heap_cancel(sess->timer_heap, &sess->timer);
sess->timer.id = TIMER_NONE;
}
- set_state(sess, PJ_TURN_STATE_DESTROYING);
-
sess->timer.id = TIMER_DESTROY;
pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay);
}
@@ -400,6 +408,8 @@ PJ_DEF(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess)
*/
PJ_DEF(pj_status_t) pj_turn_session_destroy( pj_turn_session *sess)
{
+ PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
set_state(sess, PJ_TURN_STATE_DEALLOCATED);
sess_shutdown(sess, PJ_SUCCESS);
return PJ_SUCCESS;
@@ -419,15 +429,19 @@ PJ_DEF(pj_status_t) pj_turn_session_get_info( pj_turn_session *sess,
pj_gettimeofday(&now);
info->state = sess->state;
- info->tp_type = sess->tp_type;
+ info->conn_type = sess->conn_type;
info->lifetime = sess->expiry.sec - now.sec;
+ info->last_status = sess->last_status;
if (sess->srv_addr)
pj_memcpy(&info->server, sess->srv_addr, sizeof(info->server));
else
pj_bzero(&info->server, sizeof(info->server));
- pj_memcpy(&info->relay_addr, &sess->relay_addr, sizeof(sess->relay_addr));
+ pj_memcpy(&info->mapped_addr, &sess->mapped_addr,
+ sizeof(sess->mapped_addr));
+ pj_memcpy(&info->relay_addr, &sess->relay_addr,
+ sizeof(sess->relay_addr));
return PJ_SUCCESS;
}
@@ -453,6 +467,19 @@ PJ_DEF(void*) pj_turn_session_get_user_data(pj_turn_session *sess)
}
+/*
+ * Configure message logging. By default all flags are enabled.
+ *
+ * @param sess The TURN client session.
+ * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag
+ */
+PJ_DEF(void) pj_turn_session_set_log( pj_turn_session *sess,
+ unsigned flags)
+{
+ pj_stun_session_set_log(sess->stun, flags);
+}
+
+
/**
* Set the server or domain name of the server.
*/
@@ -461,6 +488,8 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess,
int default_port,
pj_dns_resolver *resolver)
{
+ pj_sockaddr tmp_addr;
+ pj_bool_t is_ip_addr;
pj_status_t status;
PJ_ASSERT_RETURN(sess && domain, PJ_EINVAL);
@@ -468,14 +497,20 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess,
pj_lock_acquire(sess->lock);
- if (resolver) {
+ /* See if "domain" contains just IP address */
+ tmp_addr.addr.sa_family = sess->af;
+ status = pj_inet_pton(sess->af, domain,
+ pj_sockaddr_get_addr(&tmp_addr));
+ is_ip_addr = (status == PJ_SUCCESS);
+
+ if (!is_ip_addr && resolver) {
/* Resolve with DNS SRV resolution, and fallback to DNS A resolution
* if default_port is specified.
*/
unsigned opt = 0;
pj_str_t res_name;
- switch (sess->tp_type) {
+ switch (sess->conn_type) {
case PJ_TURN_TP_UDP:
res_name = pj_str("_turn._udp.");
break;
@@ -501,6 +536,12 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess,
(int)domain->slen, domain->ptr));
set_state(sess, PJ_TURN_STATE_RESOLVING);
+ /* User may have destroyed us in the callback */
+ if (sess->state != PJ_TURN_STATE_RESOLVING) {
+ status = PJ_ECANCELLED;
+ goto on_return;
+ }
+
status = pj_dns_srv_resolve(domain, &res_name, default_port,
sess->pool, resolver, opt, sess,
&dns_srv_resolver_cb, &sess->dns_async);
@@ -520,12 +561,19 @@ PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess,
PJ_ASSERT_RETURN(default_port>0 && default_port<65536, PJ_EINVAL);
sess->default_port = (pj_uint16_t)default_port;
- cnt = MAX_SRV_CNT;
+ cnt = PJ_TURN_MAX_DNS_SRV_CNT;
ai = (pj_addrinfo*)
pj_pool_calloc(sess->pool, cnt, sizeof(pj_addrinfo));
PJ_LOG(5,(sess->obj_name, "Resolving %.*s with DNS A",
(int)domain->slen, domain->ptr));
+ set_state(sess, PJ_TURN_STATE_RESOLVING);
+
+ /* User may have destroyed us in the callback */
+ if (sess->state != PJ_TURN_STATE_RESOLVING) {
+ status = PJ_ECANCELLED;
+ goto on_return;
+ }
status = pj_getaddrinfo(sess->af, domain, &cnt, ai);
if (status != PJ_SUCCESS)
@@ -636,7 +684,7 @@ PJ_DEF(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess,
/* Send request */
set_state(sess, PJ_TURN_STATE_ALLOCATING);
- retransmit = (sess->tp_type == PJ_TURN_TP_UDP);
+ retransmit = (sess->conn_type == PJ_TURN_TP_UDP);
status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE,
retransmit, sess->srv_addr,
pj_sockaddr_get_len(sess->srv_addr),
@@ -681,7 +729,7 @@ static void send_refresh(pj_turn_session *sess, int lifetime)
}
status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE,
- (sess->tp_type==PJ_TURN_TP_UDP),
+ (sess->conn_type==PJ_TURN_TP_UDP),
sess->srv_addr,
pj_sockaddr_get_len(sess->srv_addr),
tdata);
@@ -833,7 +881,7 @@ PJ_DEF(pj_status_t) pj_turn_session_bind_channel(pj_turn_session *sess,
* for future reference when we receive the ChannelBind response.
*/
status = pj_stun_session_send_msg(sess->stun, peer, PJ_FALSE,
- (sess->tp_type==PJ_TURN_TP_UDP),
+ (sess->conn_type==PJ_TURN_TP_UDP),
sess->srv_addr,
pj_sockaddr_get_len(sess->srv_addr),
tdata);
@@ -849,12 +897,12 @@ on_return:
* The packet maybe a STUN packet or ChannelData packet.
*/
PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess,
- const pj_uint8_t *pkt,
- unsigned pkt_len,
- pj_bool_t is_datagram)
+ void *pkt,
+ unsigned pkt_len)
{
pj_bool_t is_stun;
pj_status_t status;
+ pj_bool_t is_datagram;
/* Packet could be ChannelData or STUN message (response or
* indication).
@@ -863,14 +911,16 @@ PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess,
/* Start locking the session */
pj_lock_acquire(sess->lock);
+ is_datagram = (sess->conn_type==PJ_TURN_TP_UDP);
+
/* Quickly check if this is STUN message */
- is_stun = ((pkt[0] & 0xC0) == 0);
+ is_stun = ((((pj_uint8_t*)pkt)[0] & 0xC0) == 0);
if (is_stun) {
/* This looks like STUN, give it to the STUN session */
unsigned options;
- options = PJ_STUN_CHECK_PACKET;
+ options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK;
if (is_datagram)
options |= PJ_STUN_IS_DATAGRAM;
status=pj_stun_session_on_rx_pkt(sess->stun, pkt, pkt_len,
@@ -905,8 +955,8 @@ PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess,
}
/* Notify application */
- (*sess->cb.on_rx_data)(sess, pkt+sizeof(cd), cd.length,
- &peer->addr,
+ (*sess->cb.on_rx_data)(sess, ((pj_uint8_t*)pkt)+sizeof(cd),
+ cd.length, &peer->addr,
pj_sockaddr_get_len(&peer->addr));
status = PJ_SUCCESS;
@@ -953,6 +1003,8 @@ static void on_session_fail( pj_turn_session *sess,
pj_status_t status,
const pj_str_t *reason)
{
+ sess->last_status = status;
+
do {
pj_str_t reason1;
char err_msg[PJ_ERR_MSG_SIZE];
@@ -1010,6 +1062,7 @@ static void on_allocate_success(pj_turn_session *sess,
{
const pj_stun_lifetime_attr *lf_attr;
const pj_stun_relay_addr_attr *raddr_attr;
+ const pj_stun_sockaddr_attr *mapped_attr;
pj_str_t s;
pj_time_val timeout;
@@ -1071,6 +1124,12 @@ static void on_allocate_success(pj_turn_session *sess,
"for now"));
return;
}
+ if (raddr_attr && !pj_sockaddr_has_addr(&raddr_attr->sockaddr)) {
+ on_session_fail(sess, method, PJNATH_EINSTUNMSG,
+ pj_cstr(&s, "Error: Invalid IP address in "
+ "RELAY-ADDRESS attribute"));
+ return;
+ }
/* Save relayed address */
if (raddr_attr) {
@@ -1091,6 +1150,14 @@ static void on_allocate_success(pj_turn_session *sess,
}
}
+ /* Get mapped address */
+ mapped_attr = (const pj_stun_sockaddr_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0);
+ if (mapped_attr) {
+ pj_memcpy(&sess->mapped_addr, &mapped_attr->sockaddr,
+ sizeof(mapped_attr->sockaddr));
+ }
+
/* Success */
/* Cancel existing keep-alive timer, if any */
@@ -1132,6 +1199,17 @@ static void stun_on_request_complete(pj_stun_session *stun,
sess = (pj_turn_session*)pj_stun_session_get_user_data(stun);
if (method == PJ_STUN_ALLOCATE_METHOD) {
+
+ /* Destroy if we have pending destroy request */
+ if (sess->pending_destroy) {
+ if (status == PJ_SUCCESS)
+ sess->state = PJ_TURN_STATE_READY;
+ else
+ sess->state = PJ_TURN_STATE_DEALLOCATED;
+ sess_shutdown(sess, PJ_SUCCESS);
+ return;
+ }
+
/* Handle ALLOCATE response */
if (status==PJ_SUCCESS &&
PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type))
@@ -1298,7 +1376,7 @@ static void dns_srv_resolver_cb(void *user_data,
const pj_dns_srv_record *rec)
{
pj_turn_session *sess = (pj_turn_session*) user_data;
- unsigned i, cnt;
+ unsigned i, cnt, tot_cnt;
/* Clear async resolver */
sess->dns_async = NULL;
@@ -1309,11 +1387,27 @@ static void dns_srv_resolver_cb(void *user_data,
return;
}
+ /* Calculate total number of server entries in the response */
+ tot_cnt = 0;
+ for (i=0; i<rec->count; ++i) {
+ tot_cnt += rec->entry[i].server.addr_count;
+ }
+
+ if (tot_cnt > PJ_TURN_MAX_DNS_SRV_CNT)
+ tot_cnt = PJ_TURN_MAX_DNS_SRV_CNT;
+
+ /* Allocate server entries */
+ sess->srv_addr_list = (pj_sockaddr*)
+ pj_pool_calloc(sess->pool, tot_cnt,
+ sizeof(pj_sockaddr));
+
/* Copy results to server entries */
- for (i=0, cnt=0; i<rec->count && cnt<MAX_SRV_CNT; ++i) {
+ for (i=0, cnt=0; i<rec->count && cnt<PJ_TURN_MAX_DNS_SRV_CNT; ++i) {
unsigned j;
- for (j=0; j<rec->entry[i].server.addr_count && cnt<MAX_SRV_CNT; ++j) {
+ for (j=0; j<rec->entry[i].server.addr_count &&
+ cnt<PJ_TURN_MAX_DNS_SRV_CNT; ++j)
+ {
pj_sockaddr_in *addr = &sess->srv_addr_list[cnt].ipv4;
addr->sin_family = sess->af;
diff --git a/pjnath/src/pjnath/turn_sock.c b/pjnath/src/pjnath/turn_sock.c
index cd2e08d4..e595d271 100644
--- a/pjnath/src/pjnath/turn_sock.c
+++ b/pjnath/src/pjnath/turn_sock.c
@@ -17,6 +17,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjnath/turn_sock.h>
+#include <pj/activesock.h>
#include <pj/assert.h>
#include <pj/errno.h>
#include <pj/lock.h>
@@ -50,11 +51,8 @@ struct pj_turn_sock
int af;
pj_turn_tp_type conn_type;
- pj_sock_t sock;
- pj_ioqueue_key_t *key;
- pj_ioqueue_op_key_t read_key;
+ pj_activesock_t *active_sock;
pj_ioqueue_op_key_t send_key;
- pj_uint8_t pkt[PJ_TURN_MAX_PKT_LEN];
};
@@ -71,18 +69,22 @@ static void turn_on_channel_bound(pj_turn_session *sess,
unsigned addr_len,
unsigned ch_num);
static void turn_on_rx_data(pj_turn_session *sess,
- const pj_uint8_t *pkt,
+ void *pkt,
unsigned pkt_len,
const pj_sockaddr_t *peer_addr,
unsigned addr_len);
static void turn_on_state(pj_turn_session *sess,
pj_turn_state_t old_state,
pj_turn_state_t new_state);
-static void on_read_complete(pj_ioqueue_key_t *key,
- pj_ioqueue_op_key_t *op_key,
- pj_ssize_t bytes_read);
-static void on_connect_complete(pj_ioqueue_key_t *key,
- pj_status_t status);
+
+static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder);
+static pj_bool_t on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status);
+
static void destroy(pj_turn_sock *turn_sock);
@@ -158,7 +160,7 @@ PJ_DEF(pj_status_t) pj_turn_sock_create(pj_stun_config *cfg,
sess_cb.on_rx_data = &turn_on_rx_data;
sess_cb.on_state = &turn_on_state;
status = pj_turn_session_create(cfg, pool->obj_name, af, conn_type,
- &sess_cb, turn_sock, 0, &turn_sock->sess);
+ &sess_cb, 0, turn_sock, &turn_sock->sess);
if (status != PJ_SUCCESS) {
destroy(turn_sock);
return status;
@@ -187,13 +189,9 @@ static void destroy(pj_turn_sock *turn_sock)
turn_sock->sess = NULL;
}
- if (turn_sock->key) {
- pj_ioqueue_unregister(turn_sock->key);
- turn_sock->key = NULL;
- turn_sock->sock = 0;
- } else if (turn_sock->sock) {
- pj_sock_close(turn_sock->sock);
- turn_sock->sock = 0;
+ if (turn_sock->active_sock) {
+ pj_activesock_close(turn_sock->active_sock);
+ turn_sock->active_sock = NULL;
}
if (turn_sock->lock) {
@@ -271,7 +269,8 @@ static void sess_fail(pj_turn_sock *turn_sock, const char *title,
pj_status_t status)
{
show_err(turn_sock, title, status);
- pj_turn_session_destroy(turn_sock->sess);
+ if (turn_sock->sess)
+ pj_turn_session_destroy(turn_sock->sess);
}
/*
@@ -280,6 +279,7 @@ static void sess_fail(pj_turn_sock *turn_sock, const char *title,
PJ_DEF(pj_status_t) pj_turn_sock_set_user_data( pj_turn_sock *turn_sock,
void *user_data)
{
+ PJ_ASSERT_RETURN(turn_sock, PJ_EINVAL);
turn_sock->user_data = user_data;
return PJ_SUCCESS;
}
@@ -289,6 +289,7 @@ PJ_DEF(pj_status_t) pj_turn_sock_set_user_data( pj_turn_sock *turn_sock,
*/
PJ_DEF(void*) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock)
{
+ PJ_ASSERT_RETURN(turn_sock, NULL);
return turn_sock->user_data;
}
@@ -296,7 +297,7 @@ PJ_DEF(void*) pj_turn_sock_get_user_data(pj_turn_sock *turn_sock)
* Get info.
*/
PJ_DEF(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock,
- pj_turn_session_info *info)
+ pj_turn_session_info *info)
{
PJ_ASSERT_RETURN(turn_sock && info, PJ_EINVAL);
@@ -309,15 +310,41 @@ PJ_DEF(pj_status_t) pj_turn_sock_get_info(pj_turn_sock *turn_sock,
}
}
+/**
+ * Lock the TURN socket. Application may need to call this function to
+ * synchronize access to other objects to avoid deadlock.
+ */
+PJ_DEF(pj_status_t) pj_turn_sock_lock(pj_turn_sock *turn_sock)
+{
+ return pj_lock_acquire(turn_sock->lock);
+}
+
+/**
+ * Unlock the TURN socket.
+ */
+PJ_DEF(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock)
+{
+ return pj_lock_release(turn_sock->lock);
+}
+
+/*
+ * Set STUN message logging for this TURN session.
+ */
+PJ_DEF(void) pj_turn_sock_set_log( pj_turn_sock *turn_sock,
+ unsigned flags)
+{
+ pj_turn_session_set_log(turn_sock->sess, flags);
+}
+
/*
* Initialize.
*/
-PJ_DEF(pj_status_t) pj_turn_sock_init(pj_turn_sock *turn_sock,
- const pj_str_t *domain,
- int default_port,
- pj_dns_resolver *resolver,
- const pj_stun_auth_cred *cred,
- const pj_turn_alloc_param *param)
+PJ_DEF(pj_status_t) pj_turn_sock_alloc(pj_turn_sock *turn_sock,
+ const pj_str_t *domain,
+ int default_port,
+ pj_dns_resolver *resolver,
+ const pj_stun_auth_cred *cred,
+ const pj_turn_alloc_param *param)
{
pj_status_t status;
@@ -392,16 +419,16 @@ PJ_DEF(pj_status_t) pj_turn_sock_bind_channel( pj_turn_sock *turn_sock,
/*
* Notification when outgoing TCP socket has been connected.
*/
-static void on_connect_complete(pj_ioqueue_key_t *key,
- pj_status_t status)
+static pj_bool_t on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status)
{
pj_turn_sock *turn_sock;
- turn_sock = (pj_turn_sock*) pj_ioqueue_get_user_data(key);
+ turn_sock = (pj_turn_sock*) pj_activesock_get_user_data(asock);
if (status != PJ_SUCCESS) {
sess_fail(turn_sock, "TCP connect() error", status);
- return;
+ return PJ_FALSE;
}
if (turn_sock->conn_type != PJ_TURN_TP_UDP) {
@@ -409,8 +436,8 @@ static void on_connect_complete(pj_ioqueue_key_t *key,
}
/* Kick start pending read operation */
- pj_ioqueue_op_key_init(&turn_sock->read_key, sizeof(turn_sock->read_key));
- on_read_complete(turn_sock->key, &turn_sock->read_key, INIT);
+ status = pj_activesock_start_read(asock, turn_sock->pool,
+ PJ_TURN_MAX_PKT_LEN, 0);
/* Init send_key */
pj_ioqueue_op_key_init(&turn_sock->send_key, sizeof(turn_sock->send_key));
@@ -419,56 +446,43 @@ static void on_connect_complete(pj_ioqueue_key_t *key,
status = pj_turn_session_alloc(turn_sock->sess, &turn_sock->alloc_param);
if (status != PJ_SUCCESS) {
sess_fail(turn_sock, "Error sending ALLOCATE", status);
- return;
+ return PJ_FALSE;
}
+
+ return PJ_TRUE;
}
/*
* Notification from ioqueue when incoming UDP packet is received.
*/
-static void on_read_complete(pj_ioqueue_key_t *key,
- pj_ioqueue_op_key_t *op_key,
- pj_ssize_t bytes_read)
+static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
{
- enum { MAX_RETRY = 10 };
pj_turn_sock *turn_sock;
- int retry = 0;
- pj_status_t status;
+ pj_bool_t ret = PJ_TRUE;
- turn_sock = (pj_turn_sock*) pj_ioqueue_get_user_data(key);
+ turn_sock = (pj_turn_sock*) pj_activesock_get_user_data(asock);
pj_lock_acquire(turn_sock->lock);
- do {
- if (bytes_read == INIT) {
- /* Special instruction to initialize pending read() */
- } else if (bytes_read > 0 && turn_sock->sess) {
- /* Report incoming packet to TURN session */
- pj_turn_session_on_rx_pkt(turn_sock->sess, turn_sock->pkt,
- bytes_read,
- turn_sock->conn_type == PJ_TURN_TP_UDP);
- } else if (bytes_read <= 0 && turn_sock->conn_type != PJ_TURN_TP_UDP) {
- sess_fail(turn_sock, "TCP connection closed", -bytes_read);
- goto on_return;
- }
-
- /* Read next packet */
- bytes_read = sizeof(turn_sock->pkt);
- status = pj_ioqueue_recv(turn_sock->key, op_key,
- turn_sock->pkt, &bytes_read, 0);
-
- if (status != PJ_EPENDING && status != PJ_SUCCESS) {
- char errmsg[PJ_ERR_MSG_SIZE];
-
- pj_strerror(status, errmsg, sizeof(errmsg));
- sess_fail(turn_sock, "Socket recv() error", status);
- goto on_return;
- }
-
- } while (status != PJ_EPENDING && status != PJ_ECANCELLED &&
- ++retry < MAX_RETRY);
+ if (status == PJ_SUCCESS && turn_sock->sess) {
+ /* Report incoming packet to TURN session */
+ PJ_TODO(REPORT_PARSED_LEN);
+ pj_turn_session_on_rx_pkt(turn_sock->sess, data, size);
+ } else if (status != PJ_SUCCESS &&
+ turn_sock->conn_type != PJ_TURN_TP_UDP)
+ {
+ sess_fail(turn_sock, "TCP connection closed", status);
+ ret = PJ_FALSE;
+ goto on_return;
+ }
on_return:
pj_lock_release(turn_sock->lock);
+
+ return ret;
}
@@ -482,7 +496,7 @@ static pj_status_t turn_on_send_pkt(pj_turn_session *sess,
unsigned dst_addr_len)
{
pj_turn_sock *turn_sock = (pj_turn_sock*)
- pj_turn_session_get_user_data(sess);
+ pj_turn_session_get_user_data(sess);
pj_ssize_t len = pkt_len;
pj_status_t status;
@@ -495,8 +509,8 @@ static pj_status_t turn_on_send_pkt(pj_turn_session *sess,
PJ_UNUSED_ARG(dst_addr);
PJ_UNUSED_ARG(dst_addr_len);
- status = pj_ioqueue_send(turn_sock->key, &turn_sock->send_key,
- pkt, &len, 0);
+ status = pj_activesock_send(turn_sock->active_sock, &turn_sock->send_key,
+ pkt, &len, 0);
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
show_err(turn_sock, "socket send()", status);
}
@@ -524,7 +538,7 @@ static void turn_on_channel_bound(pj_turn_session *sess,
* Callback from TURN session upon incoming data.
*/
static void turn_on_rx_data(pj_turn_session *sess,
- const pj_uint8_t *pkt,
+ void *pkt,
unsigned pkt_len,
const pj_sockaddr_t *peer_addr,
unsigned addr_len)
@@ -559,7 +573,19 @@ static void turn_on_state(pj_turn_session *sess,
return;
}
- if (new_state == PJ_TURN_STATE_RESOLVED) {
+ /* Notify app first */
+ if (turn_sock->cb.on_state) {
+ (*turn_sock->cb.on_state)(turn_sock, old_state, new_state);
+ }
+
+ /* Make sure user hasn't destroyed us in the callback */
+ if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) {
+ pj_turn_session_info info;
+ pj_turn_session_get_info(turn_sock->sess, &info);
+ new_state = info.state;
+ }
+
+ if (turn_sock->sess && new_state == PJ_TURN_STATE_RESOLVED) {
/*
* Once server has been resolved, initiate outgoing TCP
* connection to the server.
@@ -567,19 +593,16 @@ static void turn_on_state(pj_turn_session *sess,
pj_turn_session_info info;
char addrtxt[PJ_INET6_ADDRSTRLEN+8];
int sock_type;
- pj_ioqueue_callback ioq_cb;
+ pj_sock_t sock;
+ pj_activesock_cb asock_cb;
/* Close existing connection, if any. This happens when
* we're switching to alternate TURN server when either TCP
* connection or ALLOCATE request failed.
*/
- if (turn_sock->key) {
- pj_ioqueue_unregister(turn_sock->key);
- turn_sock->key = NULL;
- turn_sock->sock = 0;
- } else if (turn_sock->sock) {
- pj_sock_close(turn_sock->sock);
- turn_sock->sock = 0;
+ if (turn_sock->active_sock) {
+ pj_activesock_close(turn_sock->active_sock);
+ turn_sock->active_sock = NULL;
}
/* Get server address from session info */
@@ -591,20 +614,21 @@ static void turn_on_state(pj_turn_session *sess,
sock_type = pj_SOCK_STREAM();
/* Init socket */
- status = pj_sock_socket(turn_sock->af, sock_type, 0,
- &turn_sock->sock);
+ status = pj_sock_socket(turn_sock->af, sock_type, 0, &sock);
if (status != PJ_SUCCESS) {
pj_turn_sock_destroy(turn_sock);
return;
}
- /* Register to ioqeuue */
- pj_bzero(&ioq_cb, sizeof(ioq_cb));
- ioq_cb.on_read_complete = &on_read_complete;
- ioq_cb.on_connect_complete = &on_connect_complete;
- status = pj_ioqueue_register_sock(turn_sock->pool, turn_sock->cfg.ioqueue,
- turn_sock->sock, turn_sock,
- &ioq_cb, &turn_sock->key);
+ /* Create active socket */
+ pj_bzero(&asock_cb, sizeof(asock_cb));
+ asock_cb.on_data_read = &on_data_read;
+ asock_cb.on_connect_complete = &on_connect_complete;
+ status = pj_activesock_create(turn_sock->pool, sock,
+ sock_type, NULL,
+ turn_sock->cfg.ioqueue, &asock_cb,
+ turn_sock,
+ &turn_sock->active_sock);
if (status != PJ_SUCCESS) {
pj_turn_sock_destroy(turn_sock);
return;
@@ -616,10 +640,12 @@ static void turn_on_state(pj_turn_session *sess,
sizeof(addrtxt), 3)));
/* Initiate non-blocking connect */
- status = pj_ioqueue_connect(turn_sock->key, &info.server,
- pj_sockaddr_get_len(&info.server));
+ status=pj_activesock_start_connect(turn_sock->active_sock,
+ turn_sock->pool,
+ &info.server,
+ pj_sockaddr_get_len(&info.server));
if (status == PJ_SUCCESS) {
- on_connect_complete(turn_sock->key, PJ_SUCCESS);
+ on_connect_complete(turn_sock->active_sock, PJ_SUCCESS);
} else if (status != PJ_EPENDING) {
pj_turn_sock_destroy(turn_sock);
return;
@@ -630,10 +656,6 @@ static void turn_on_state(pj_turn_session *sess,
*/
}
- if (turn_sock->cb.on_state) {
- (*turn_sock->cb.on_state)(turn_sock, old_state, new_state);
- }
-
if (new_state >= PJ_TURN_STATE_DESTROYING && turn_sock->sess) {
pj_time_val delay = {0, 0};
diff --git a/pjnath/src/pjturn-client/client_main.c b/pjnath/src/pjturn-client/client_main.c
index ea6ffc39..823cf497 100644
--- a/pjnath/src/pjturn-client/client_main.c
+++ b/pjnath/src/pjturn-client/client_main.c
@@ -68,7 +68,7 @@ static struct options
static int worker_thread(void *unused);
static void turn_on_rx_data(pj_turn_sock *relay,
- const pj_uint8_t *pkt,
+ void *pkt,
unsigned pkt_len,
const pj_sockaddr_t *peer_addr,
unsigned addr_len);
@@ -274,7 +274,7 @@ static pj_status_t create_relay(void)
}
srv = pj_str(o.srv_addr);
- CHECK( pj_turn_sock_init(g.relay, /* the relay */
+ CHECK(pj_turn_sock_alloc(g.relay, /* the relay */
&srv, /* srv addr */
(o.srv_port?atoi(o.srv_port):PJ_STUN_PORT),/* def port */
NULL, /* resolver */
@@ -294,7 +294,7 @@ static void destroy_relay(void)
static void turn_on_rx_data(pj_turn_sock *relay,
- const pj_uint8_t *pkt,
+ void *pkt,
unsigned pkt_len,
const pj_sockaddr_t *peer_addr,
unsigned addr_len)
diff --git a/pjnath/src/pjturn-srv/auth.c b/pjnath/src/pjturn-srv/auth.c
index d2527dfa..4e0e10a4 100644
--- a/pjnath/src/pjturn-srv/auth.c
+++ b/pjnath/src/pjturn-srv/auth.c
@@ -35,8 +35,7 @@ static struct cred_t
{
{ "100", "100" },
{ "700", "700" },
- { "701", "701" },
- { "702", "702" }
+ { "701", "701" }
};
#define THE_NONCE "pjnath"