summaryrefslogtreecommitdiff
path: root/pjnath/src/pjnath-test
diff options
context:
space:
mode:
Diffstat (limited to 'pjnath/src/pjnath-test')
-rw-r--r--pjnath/src/pjnath-test/ice_test.c878
-rw-r--r--pjnath/src/pjnath-test/main.c62
-rw-r--r--pjnath/src/pjnath-test/main_win32.c1
-rw-r--r--pjnath/src/pjnath-test/server.c754
-rw-r--r--pjnath/src/pjnath-test/server.h110
-rw-r--r--pjnath/src/pjnath-test/sess_auth.c1146
-rw-r--r--pjnath/src/pjnath-test/stun.c983
-rw-r--r--pjnath/src/pjnath-test/stun_sock_test.c849
-rw-r--r--pjnath/src/pjnath-test/test.c212
-rw-r--r--pjnath/src/pjnath-test/test.h63
-rw-r--r--pjnath/src/pjnath-test/turn_sock_test.c516
11 files changed, 5574 insertions, 0 deletions
diff --git a/pjnath/src/pjnath-test/ice_test.c b/pjnath/src/pjnath-test/ice_test.c
new file mode 100644
index 0000000..fe0ee8d
--- /dev/null
+++ b/pjnath/src/pjnath-test/ice_test.c
@@ -0,0 +1,878 @@
+/* $Id: ice_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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"
+
+enum
+{
+ NO = 0,
+ YES = 1,
+ SRV = 3,
+};
+
+#define NODELAY 0xFFFFFFFF
+#define SRV_DOMAIN "pjsip.lab.domain"
+
+#define INDENT " "
+
+/* Client flags */
+enum
+{
+ WRONG_TURN = 1,
+ DEL_ON_ERR = 2,
+};
+
+
+/* Test results */
+struct test_result
+{
+ pj_status_t init_status; /* init successful? */
+ pj_status_t nego_status; /* negotiation successful? */
+ unsigned rx_cnt[4]; /* Number of data received */
+};
+
+
+/* Test session configuration */
+struct test_cfg
+{
+ 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_bool_t nom_regular; /* Use regular nomination? */
+};
+
+/* 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 */
+};
+
+/* The test session */
+struct test_sess
+{
+ pj_pool_t *pool;
+ pj_stun_config *stun_cfg;
+ pj_dns_resolver *resolver;
+
+ test_server *server;
+
+ unsigned server_flag;
+ struct ice_ept caller;
+ struct ice_ept callee;
+};
+
+
+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 *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;
+
+ 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;
+ }
+
+ if (ept->cfg.enable_host == 0) {
+ ice_cfg.stun.max_host_cands = 0;
+ } else {
+ //ice_cfg.stun.no_host_cands = PJ_FALSE;
+ ice_cfg.stun.loop_addr = PJ_TRUE;
+ }
+
+
+ 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);
+ }
+
+ /* 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);
+
+ /* 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)
+{
+ pj_pool_t *pool;
+ struct test_sess *sess;
+ pj_str_t ns_ip;
+ pj_uint16_t ns_port;
+ unsigned flags;
+ pj_status_t status;
+
+ /* 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;
+
+ pj_memcpy(&sess->callee.cfg, callee_cfg, sizeof(*callee_cfg));
+ sess->callee.result.init_status = sess->callee.result.nego_status = PJ_EPENDING;
+
+ /* 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 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;
+ }
+
+ 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;
+ }
+
+ /* 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;
+ }
+
+ /* 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;
+}
+
+/* Destroy test session */
+static void destroy_sess(struct test_sess *sess, unsigned wait_msec)
+{
+ 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;
+ }
+
+ poll_events(sess->stun_cfg, wait_msec, PJ_FALSE);
+
+ if (sess->resolver) {
+ pj_dns_resolver_destroy(sess->resolver, PJ_FALSE);
+ sess->resolver = NULL;
+ }
+
+ if (sess->server) {
+ destroy_test_server(sess->server);
+ sess->server = NULL;
+ }
+
+ if (sess->pool) {
+ pj_pool_t *pool = sess->pool;
+ sess->pool = NULL;
+ pj_pool_release(pool);
+ }
+}
+
+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)
+{
+ struct ice_ept *ept;
+
+ PJ_UNUSED_ARG(pkt);
+ PJ_UNUSED_ARG(size);
+ PJ_UNUSED_ARG(src_addr);
+ PJ_UNUSED_ARG(src_addr_len);
+
+ ept = (struct ice_ept*) pj_ice_strans_get_user_data(ice_st);
+ ept->result.rx_cnt[comp_id]++;
+}
+
+
+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");
+ }
+}
+
+
+/* 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;
+
+ /* 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;
+ }
+
+ 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;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* 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;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ /* 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;
+ }
+ }
+
+ return 0;
+}
+
+
+#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;
+
+ PJ_LOG(3,("", INDENT "%s", title));
+
+ capture_pjlib_state(stun_cfg, &pjlib_state);
+
+ rc = create_sess(stun_cfg, server_flag, caller_cfg, callee_cfg, &sess);
+ if (rc != 0)
+ return rc;
+
+#define ALL_READY (sess->caller.result.init_status!=PJ_EPENDING && \
+ sess->callee.result.init_status!=PJ_EPENDING)
+
+ /* Wait until both ICE transports are initialized */
+ WAIT_UNTIL(30, ALL_READY, rc);
+
+ if (!ALL_READY) {
+ PJ_LOG(3,("", INDENT "err: init timed-out"));
+ destroy_sess(sess, 500);
+ return -100;
+ }
+
+ 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;
+ }
+
+ /* Failure condition */
+ if (sess->caller.result.init_status != PJ_SUCCESS ||
+ sess->callee.result.init_status != PJ_SUCCESS)
+ {
+ rc = 0;
+ goto on_return;
+ }
+
+ /* 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;
+ }
+
+ /* 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;
+ }
+
+ /* Start ICE on callee */
+ rc = start_ice(&sess->callee, &sess->caller);
+ if (rc != PJ_SUCCESS) {
+ destroy_sess(sess, 500);
+ return -120;
+ }
+
+ /* Wait for callee's answer_delay */
+ poll_events(stun_cfg, sess->callee.cfg.answer_delay, PJ_FALSE);
+
+ /* Start ICE on caller */
+ rc = start_ice(&sess->caller, &sess->callee);
+ if (rc != PJ_SUCCESS) {
+ destroy_sess(sess, 500);
+ return -130;
+ }
+
+ /* 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;
+ }
+
+ 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 (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;
+ }
+
+ /* 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;
+ }
+ rc = check_pair(&sess->callee, &sess->caller, -180);
+ if (rc != 0) {
+ destroy_sess(sess, 500);
+ return rc;
+ }
+
+ /* Looks like everything is okay */
+
+ /* 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;
+ }
+
+on_return:
+ /* Wait.. */
+ poll_events(stun_cfg, 500, PJ_FALSE);
+
+ /* Now destroy everything */
+ destroy_sess(sess, 500);
+
+ /* Flush events */
+ poll_events(stun_cfg, 100, PJ_FALSE);
+
+ rc = check_pjlib_state(stun_cfg, &pjlib_state);
+ if (rc != 0) {
+ return rc;
+ }
+
+ return 0;
+}
+
+#define ROLE1 PJ_ICE_SESS_ROLE_CONTROLLED
+#define ROLE2 PJ_ICE_SESS_ROLE_CONTROLLING
+
+int ice_test(void)
+{
+ pj_pool_t *pool;
+ 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[] =
+ {
+ /* 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, 512, 512, NULL);
+ rc = create_stun_config(pool, &stun_cfg);
+ if (rc != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return -7;
+ }
+
+ /* 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 = 2;
+ cfg.ua2.comp_cnt = 2;
+ rc = perform_test("Basic with host candidates, 2 components",
+ &stun_cfg, cfg.server_flag,
+ &cfg.ua1, &cfg.ua2);
+ 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 = 2;
+ cfg.ua2.comp_cnt = 2;
+
+ rc = perform_test("Basic with srflx candidates, 2 components",
+ &stun_cfg, cfg.server_flag,
+ &cfg.ua1, &cfg.ua2);
+ if (rc != 0)
+ 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 = 2;
+ cfg.ua2.comp_cnt = 2;
+
+ rc = perform_test("Basic with relay candidates, 2 components",
+ &stun_cfg, cfg.server_flag,
+ &cfg.ua1, &cfg.ua2);
+ if (rc != 0)
+ goto on_return;
+ }
+
+ /* 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;
+ }
+
+ /* 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, 2, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}},
+ {ROLE2, 2, 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:
+ destroy_stun_config(&stun_cfg);
+ pj_pool_release(pool);
+ return rc;
+}
+
diff --git a/pjnath/src/pjnath-test/main.c b/pjnath/src/pjnath-test/main.c
new file mode 100644
index 0000000..c12d09d
--- /dev/null
+++ b/pjnath/src/pjnath-test/main.c
@@ -0,0 +1,62 @@
+/* $Id: main.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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"
+
+#if defined(PJ_SUNOS) && PJ_SUNOS!=0
+#include <signal.h>
+static void init_signals()
+{
+ struct sigaction act;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = SIG_IGN;
+
+ sigaction(SIGALRM, &act, NULL);
+}
+
+#else
+#define init_signals()
+#endif
+
+#define boost()
+
+int main(int argc, char *argv[])
+{
+ int rc;
+
+ PJ_UNUSED_ARG(argc);
+ PJ_UNUSED_ARG(argv);
+
+ boost();
+ init_signals();
+
+ rc = test_main();
+
+ if (argc == 2 && pj_ansi_strcmp(argv[1], "-i")==0) {
+ char buf[10];
+
+ puts("Press <ENTER> to exit");
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ return rc;
+ }
+
+ return rc;
+}
+
diff --git a/pjnath/src/pjnath-test/main_win32.c b/pjnath/src/pjnath-test/main_win32.c
new file mode 100644
index 0000000..3043a39
--- /dev/null
+++ b/pjnath/src/pjnath-test/main_win32.c
@@ -0,0 +1 @@
+#include "../../pjlib/src/pjlib-test/main_win32.c"
diff --git a/pjnath/src/pjnath-test/server.c b/pjnath/src/pjnath-test/server.c
new file mode 100644
index 0000000..4d8565e
--- /dev/null
+++ b/pjnath/src/pjnath-test/server.c
@@ -0,0 +1,754 @@
+/* $Id: server.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 XOR-RELAYED-ADDRESS */
+ pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_XOR_RELAYED_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);
+
+ /* 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 (pj_stun_msg_check((pj_uint8_t*)data, size, PJ_STUN_NO_FINGERPRINT_CHECK)!=PJ_SUCCESS) {
+ /* Not STUN message, this probably is a ChannelData */
+ pj_turn_channel_data cd;
+ const pj_turn_channel_data *pcd = (const pj_turn_channel_data*)data;
+ pj_ssize_t sent;
+
+ if (i==test_srv->turn_alloc_cnt) {
+ /* Invalid data */
+ PJ_LOG(1,(THIS_FILE,
+ "TURN Server received strayed data"));
+ goto on_return;
+ }
+
+ alloc = &test_srv->turn_alloc[i];
+
+ cd.ch_number = pj_ntohs(pcd->ch_number);
+ cd.length = pj_ntohs(pcd->length);
+
+ /* For UDP check the packet length */
+ if (size < cd.length+sizeof(cd)) {
+ PJ_LOG(1,(THIS_FILE,
+ "TURN Server: ChannelData discarded: UDP size error"));
+ goto on_return;
+ }
+
+ /* Lookup peer */
+ for (i=0; i<alloc->perm_cnt; ++i) {
+ if (alloc->chnum[i] == cd.ch_number)
+ break;
+ }
+
+ if (i==alloc->perm_cnt) {
+ PJ_LOG(1,(THIS_FILE,
+ "TURN Server: ChannelData discarded: invalid channel number"));
+ goto on_return;
+ }
+
+ /* Relay the data to peer */
+ sent = cd.length;
+ pj_activesock_sendto(alloc->sock, &alloc->send_key,
+ pcd+1, &sent, 0,
+ &alloc->perm[i],
+ pj_sockaddr_get_len(&alloc->perm[i]));
+
+ /* Done */
+ goto on_return;
+ }
+
+ 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;
+ }
+
+ 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_XOR_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_CREATE_PERM_REQUEST) {
+ for (i=0; i<req->attr_count; ++i) {
+ if (req->attr[i]->type == PJ_STUN_ATTR_XOR_PEER_ADDR) {
+ pj_stun_xor_peer_addr_attr *pa = (pj_stun_xor_peer_addr_attr*)req->attr[i];
+ unsigned j;
+
+ 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) {
+ char peer_info[PJ_INET6_ADDRSTRLEN];
+ pj_sockaddr_print(&pa->sockaddr, peer_info, sizeof(peer_info), 3);
+
+ 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));
+ }
+
+ }
+ }
+ resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key);
+ } else if (req->hdr.type == PJ_STUN_SEND_INDICATION) {
+ pj_stun_xor_peer_addr_attr *pa;
+ pj_stun_data_attr *da;
+
+ test_srv->turn_stat.rx_send_ind_cnt++;
+
+ pa = (pj_stun_xor_peer_addr_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_XOR_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) {
+ PJ_LOG(5,("", "SendIndication to %s is rejected (no permission)",
+ peer_info, client_info, alloc->perm_cnt));
+ } else {
+ 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 (req->hdr.type == PJ_STUN_CHANNEL_BIND_REQUEST) {
+ pj_stun_xor_peer_addr_attr *pa;
+ pj_stun_channel_number_attr *cna;
+ unsigned j, cn;
+
+ pa = (pj_stun_xor_peer_addr_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_XOR_PEER_ADDR, 0);
+ cna = (pj_stun_channel_number_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_CHANNEL_NUMBER, 0);
+ cn = PJ_STUN_GET_CH_NB(cna->value);
+
+ resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key);
+
+ for (j=0; j<alloc->perm_cnt; ++j) {
+ if (pj_sockaddr_cmp(&alloc->perm[j], &pa->sockaddr)==0)
+ break;
+ }
+
+ if (i==alloc->perm_cnt) {
+ if (alloc->perm_cnt==MAX_TURN_PERM) {
+ pj_stun_msg_create_response(pool, req, PJ_STUN_SC_INSUFFICIENT_CAPACITY, NULL, &resp);
+ goto send_pkt;
+ }
+ pj_sockaddr_cp(&alloc->perm[i], &pa->sockaddr);
+ ++alloc->perm_cnt;
+ }
+ alloc->chnum[i] = cn;
+
+ resp = create_success_response(test_srv, alloc, req, pool, 0, &auth_key);
+
+ } 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_xor_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_xor_peer_addr_attr*)
+ pj_stun_msg_find_attr(alloc->data_ind, PJ_STUN_ATTR_XOR_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 = (pj_uint8_t*)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 0000000..f8093bb
--- /dev/null
+++ b/pjnath/src/pjnath-test/server.h
@@ -0,0 +1,110 @@
+/* $Id: server.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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];
+ unsigned chnum[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
new file mode 100644
index 0000000..05a6209
--- /dev/null
+++ b/pjnath/src/pjnath-test/sess_auth.c
@@ -0,0 +1,1146 @@
+/* $Id: sess_auth.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 "sess_auth.c"
+
+#define REALM "STUN session test"
+#define USERNAME "theusername"
+#define PASSWORD "thepassword"
+#define NONCE "thenonce"
+
+
+/* STUN config */
+static pj_stun_config stun_cfg;
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// SERVER PART
+//
+
+
+/* Server instance */
+static struct server
+{
+ pj_pool_t *pool;
+ pj_sockaddr addr;
+ pj_stun_session *sess;
+
+ pj_bool_t responding;
+ unsigned recv_count;
+ pj_stun_auth_type auth_type;
+
+ pj_sock_t sock;
+
+ pj_bool_t quit;
+ pj_thread_t *thread;
+} *server;
+
+
+static pj_status_t server_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_ssize_t len = pkt_size;
+
+ PJ_UNUSED_ARG(sess);
+ PJ_UNUSED_ARG(token);
+
+ return pj_sock_sendto(server->sock, pkt, &len, 0, dst_addr, addr_len);
+}
+
+static pj_status_t server_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_rx_data *rdata,
+ void *token,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ PJ_UNUSED_ARG(pkt);
+ PJ_UNUSED_ARG(pkt_len);
+ PJ_UNUSED_ARG(token);
+
+ return pj_stun_session_respond(sess, rdata, 0, NULL, NULL, PJ_TRUE,
+ src_addr, src_addr_len);
+}
+
+
+static pj_status_t server_get_auth(void *user_data,
+ pj_pool_t *pool,
+ pj_str_t *realm,
+ pj_str_t *nonce)
+{
+ PJ_UNUSED_ARG(user_data);
+ PJ_UNUSED_ARG(pool);
+
+ if (server->auth_type == PJ_STUN_AUTH_SHORT_TERM) {
+ realm->slen = nonce->slen = 0;
+ } else {
+ *realm = pj_str(REALM);
+ *nonce = pj_str(NONCE);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t server_get_password( const pj_stun_msg *msg,
+ void *user_data,
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ pj_pool_t *pool,
+ pj_stun_passwd_type *data_type,
+ pj_str_t *data)
+{
+ PJ_UNUSED_ARG(msg);
+ PJ_UNUSED_ARG(user_data);
+ PJ_UNUSED_ARG(pool);
+
+ if (server->auth_type == PJ_STUN_AUTH_SHORT_TERM) {
+ if (realm && realm->slen) {
+ PJ_LOG(4,(THIS_FILE, " server expecting short term"));
+ return -1;
+ }
+ } else {
+ if (realm==NULL || realm->slen==0) {
+ PJ_LOG(4,(THIS_FILE, " realm not present"));
+ return -1;
+ }
+ }
+
+ if (pj_strcmp2(username, USERNAME) != 0) {
+ PJ_LOG(4,(THIS_FILE, " wrong username"));
+ return -1;
+ }
+
+ *data_type = PJ_STUN_PASSWD_PLAIN;
+ *data = pj_str(PASSWORD);
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_bool_t server_verify_nonce(const pj_stun_msg *msg,
+ void *user_data,
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ const pj_str_t *nonce)
+{
+ PJ_UNUSED_ARG(msg);
+ PJ_UNUSED_ARG(user_data);
+ PJ_UNUSED_ARG(realm);
+ PJ_UNUSED_ARG(username);
+
+ if (pj_strcmp2(nonce, NONCE) != 0)
+ return PJ_FALSE;
+
+ return PJ_TRUE;
+}
+
+
+static int server_thread(void *unused)
+{
+ PJ_UNUSED_ARG(unused);
+
+ PJ_LOG(5,("", " server thread started"));
+
+ while (!server->quit) {
+ pj_fd_set_t readset;
+ pj_time_val delay = {0, 10};
+
+ PJ_FD_ZERO(&readset);
+ PJ_FD_SET(server->sock, &readset);
+
+ if (pj_sock_select(server->sock+1, &readset, NULL, NULL, &delay)==1 &&
+ PJ_FD_ISSET(server->sock, &readset))
+ {
+ char pkt[1000];
+ pj_ssize_t len;
+ pj_status_t status;
+ pj_sockaddr src_addr;
+ int src_addr_len;
+
+ len = sizeof(pkt);
+ src_addr_len = sizeof(src_addr);
+
+ status = pj_sock_recvfrom(server->sock, pkt, &len, 0, &src_addr, &src_addr_len);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ /* Increment server's receive count */
+ server->recv_count++;
+
+ /* Only pass to server if we allow to respond */
+ if (!server->responding)
+ continue;
+
+ pj_stun_session_on_rx_pkt(server->sess, pkt, len,
+ PJ_STUN_CHECK_PACKET | PJ_STUN_IS_DATAGRAM,
+ NULL, NULL, &src_addr, src_addr_len);
+ }
+ }
+
+ return 0;
+}
+
+
+/* Destroy server */
+static void destroy_server(void)
+{
+ if (server->thread) {
+ server->quit = PJ_TRUE;
+ pj_thread_join(server->thread);
+ pj_thread_destroy(server->thread);
+ }
+
+ if (server->sock) {
+ pj_sock_close(server->sock);
+ }
+
+ if (server->sess) {
+ pj_stun_session_destroy(server->sess);
+ }
+
+ pj_pool_release(server->pool);
+ server = NULL;
+}
+
+/* Instantiate standard server */
+static int create_std_server(pj_stun_auth_type auth_type,
+ pj_bool_t responding)
+{
+ pj_pool_t *pool;
+ pj_stun_session_cb sess_cb;
+ pj_stun_auth_cred cred;
+ pj_status_t status;
+
+ /* Create server */
+ pool = pj_pool_create(mem, "server", 1000, 1000, NULL);
+ server = PJ_POOL_ZALLOC_T(pool, struct server);
+ server->pool = pool;
+ server->auth_type = auth_type;
+ server->responding = responding;
+
+ /* Create STUN session */
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_rx_request = &server_on_rx_request;
+ sess_cb.on_send_msg = &server_send_msg;
+ status = pj_stun_session_create(&stun_cfg, "server", &sess_cb, PJ_FALSE, &server->sess);
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -10;
+ }
+
+ /* Configure credential */
+ pj_bzero(&cred, sizeof(cred));
+ cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
+ cred.data.dyn_cred.get_auth = &server_get_auth;
+ cred.data.dyn_cred.get_password = &server_get_password;
+ cred.data.dyn_cred.verify_nonce = &server_verify_nonce;
+ status = pj_stun_session_set_credential(server->sess, auth_type, &cred);
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -20;
+ }
+
+ /* Create socket */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &server->sock);
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -30;
+ }
+
+ /* Bind */
+ pj_sockaddr_in_init(&server->addr.ipv4, NULL, 0);
+ status = pj_sock_bind(server->sock, &server->addr, pj_sockaddr_get_len(&server->addr));
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -40;
+ } else {
+ /* Get the bound IP address */
+ int namelen = sizeof(server->addr);
+ pj_sockaddr addr;
+
+ status = pj_sock_getsockname(server->sock, &server->addr, &namelen);
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -43;
+ }
+
+ status = pj_gethostip(pj_AF_INET(), &addr);
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -45;
+ }
+
+ pj_sockaddr_copy_addr(&server->addr, &addr);
+ }
+
+
+ /* Create worker thread */
+ status = pj_thread_create(pool, "server", &server_thread, 0, 0, 0, &server->thread);
+ if (status != PJ_SUCCESS) {
+ destroy_server();
+ return -30;
+ }
+
+ return 0;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// CLIENT PART
+//
+
+static struct client
+{
+ pj_pool_t *pool;
+ pj_stun_session *sess;
+ pj_sem_t *test_complete;
+ pj_sock_t sock;
+
+ pj_bool_t responding;
+ unsigned recv_count;
+
+ pj_status_t response_status;
+ pj_stun_msg *response;
+
+ pj_bool_t quit;
+ pj_thread_t *thread;
+} *client;
+
+
+static pj_status_t client_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_ssize_t len = pkt_size;
+
+ PJ_UNUSED_ARG(sess);
+ PJ_UNUSED_ARG(token);
+
+ return pj_sock_sendto(client->sock, pkt, &len, 0, dst_addr, addr_len);
+}
+
+
+static void client_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_UNUSED_ARG(sess);
+ PJ_UNUSED_ARG(token);
+ PJ_UNUSED_ARG(tdata);
+ PJ_UNUSED_ARG(src_addr);
+ PJ_UNUSED_ARG(src_addr_len);
+
+ client->response_status = status;
+ if (response)
+ client->response = pj_stun_msg_clone(client->pool, response);
+
+ pj_sem_post(client->test_complete);
+}
+
+
+static int client_thread(void *unused)
+{
+ PJ_UNUSED_ARG(unused);
+
+ while (!client->quit) {
+ pj_fd_set_t readset;
+ pj_time_val delay = {0, 10};
+
+ /* Also poll the timer heap */
+ pj_timer_heap_poll(stun_cfg.timer_heap, NULL);
+
+ /* Poll client socket */
+ PJ_FD_ZERO(&readset);
+ PJ_FD_SET(client->sock, &readset);
+
+ if (pj_sock_select(client->sock+1, &readset, NULL, NULL, &delay)==1 &&
+ PJ_FD_ISSET(client->sock, &readset))
+ {
+ char pkt[1000];
+ pj_ssize_t len;
+ pj_status_t status;
+ pj_sockaddr src_addr;
+ int src_addr_len;
+
+ len = sizeof(pkt);
+ src_addr_len = sizeof(src_addr);
+
+ status = pj_sock_recvfrom(client->sock, pkt, &len, 0, &src_addr, &src_addr_len);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ /* Increment client's receive count */
+ client->recv_count++;
+
+ /* Only pass to client if we allow to respond */
+ if (!client->responding)
+ continue;
+
+ pj_stun_session_on_rx_pkt(client->sess, pkt, len,
+ PJ_STUN_CHECK_PACKET | PJ_STUN_IS_DATAGRAM,
+ NULL, NULL, &src_addr, src_addr_len);
+ }
+
+ }
+
+ return 0;
+}
+
+
+static void destroy_client_server(void)
+{
+ if (client->thread) {
+ client->quit = 1;
+ pj_thread_join(client->thread);
+ pj_thread_destroy(client->thread);
+ }
+
+ if (client->sess)
+ pj_stun_session_destroy(client->sess);
+
+ if (client->sock)
+ pj_sock_close(client->sock);
+
+ if (client->test_complete)
+ pj_sem_destroy(client->test_complete);
+
+ if (server)
+ destroy_server();
+}
+
+static int run_client_test(const char *title,
+
+ pj_bool_t server_responding,
+ pj_stun_auth_type server_auth_type,
+
+ pj_stun_auth_type client_auth_type,
+ const char *realm,
+ const char *username,
+ const char *nonce,
+ const char *password,
+ pj_bool_t dummy_mi,
+
+ pj_bool_t expected_error,
+ pj_status_t expected_code,
+ const char *expected_realm,
+ const char *expected_nonce,
+
+ int (*more_check)(void))
+{
+ pj_pool_t *pool;
+ pj_stun_session_cb sess_cb;
+ pj_stun_auth_cred cred;
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+ int rc = 0;
+
+ PJ_LOG(3,(THIS_FILE, " %s test", title));
+
+ /* Create client */
+ pool = pj_pool_create(mem, "client", 1000, 1000, NULL);
+ client = PJ_POOL_ZALLOC_T(pool, struct client);
+ client->pool = pool;
+ client->responding = PJ_TRUE;
+
+ /* Create STUN session */
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_request_complete = &client_on_request_complete;
+ sess_cb.on_send_msg = &client_send_msg;
+ status = pj_stun_session_create(&stun_cfg, "client", &sess_cb, PJ_FALSE, &client->sess);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -200;
+ }
+
+ /* Create semaphore */
+ status = pj_sem_create(pool, "client", 0, 1, &client->test_complete);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -205;
+ }
+
+ /* Create client socket */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &client->sock);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -210;
+ }
+
+ /* Bind client socket */
+ status = pj_sock_bind_in(client->sock, 0, 0);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -220;
+ }
+
+ /* Create client thread */
+ status = pj_thread_create(pool, "client", &client_thread, NULL, 0, 0, &client->thread);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -230;
+ }
+
+ /* Initialize credential */
+ pj_bzero(&cred, sizeof(cred));
+ cred.type = PJ_STUN_AUTH_CRED_STATIC;
+ if (realm) cred.data.static_cred.realm = pj_str((char*)realm);
+ if (username) cred.data.static_cred.username = pj_str((char*)username);
+ if (nonce) cred.data.static_cred.nonce = pj_str((char*)nonce);
+ if (password) cred.data.static_cred.data = pj_str((char*)password);
+ cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+ status = pj_stun_session_set_credential(client->sess, client_auth_type, &cred);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -240;
+ }
+
+ /* Create the server */
+ status = create_std_server(server_auth_type, server_responding);
+ if (status != 0) {
+ destroy_client_server();
+ return status;
+ }
+
+ /* Create request */
+ status = pj_stun_session_create_req(client->sess, PJ_STUN_BINDING_REQUEST,
+ PJ_STUN_MAGIC, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -250;
+ }
+
+ /* Add our own attributes if client authentication is set to none */
+ if (client_auth_type == PJ_STUN_AUTH_NONE) {
+ pj_str_t tmp;
+ if (realm)
+ pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_REALM, pj_cstr(&tmp, realm));
+ if (username)
+ pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_USERNAME, pj_cstr(&tmp, username));
+ if (nonce)
+ pj_stun_msg_add_string_attr(tdata->pool, tdata->msg, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, nonce));
+ if (password) {
+ // ignored
+ }
+ if (dummy_mi) {
+ pj_stun_msgint_attr *mi;
+
+ pj_stun_msgint_attr_create(tdata->pool, &mi);
+ pj_stun_msg_add_attr(tdata->msg, &mi->hdr);
+ }
+
+ }
+
+ /* Send the request */
+ status = pj_stun_session_send_msg(client->sess, NULL, PJ_FALSE, PJ_TRUE, &server->addr,
+ pj_sockaddr_get_len(&server->addr), tdata);
+ if (status != PJ_SUCCESS) {
+ destroy_client_server();
+ return -270;
+ }
+
+ /* Wait until test complete */
+ pj_sem_wait(client->test_complete);
+
+
+ /* Verify response */
+ if (expected_error) {
+ if (expected_code != client->response_status) {
+ char e1[PJ_ERR_MSG_SIZE], e2[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(expected_code, e1, sizeof(e1));
+ pj_strerror(client->response_status, e2, sizeof(e2));
+
+ PJ_LOG(3,(THIS_FILE, " err: expecting %d (%s) but got %d (%s) response",
+ expected_code, e1, client->response_status, e2));
+ rc = -500;
+ }
+
+ } else {
+ int res_code = 0;
+ pj_stun_realm_attr *arealm;
+ pj_stun_nonce_attr *anonce;
+
+ if (client->response_status != 0) {
+ PJ_LOG(3,(THIS_FILE, " err: expecting successful operation but got error %d",
+ client->response_status));
+ rc = -600;
+ goto done;
+ }
+
+ if (PJ_STUN_IS_ERROR_RESPONSE(client->response->hdr.type)) {
+ pj_stun_errcode_attr *aerr = NULL;
+
+ aerr = (pj_stun_errcode_attr*)
+ pj_stun_msg_find_attr(client->response,
+ PJ_STUN_ATTR_ERROR_CODE, 0);
+ if (aerr == NULL) {
+ PJ_LOG(3,(THIS_FILE, " err: received error response without ERROR-CODE"));
+ rc = -610;
+ goto done;
+ }
+
+ res_code = aerr->err_code;
+ } else {
+ res_code = 0;
+ }
+
+ /* Check that code matches */
+ if (expected_code != res_code) {
+ PJ_LOG(3,(THIS_FILE, " err: expecting response code %d but got %d",
+ expected_code, res_code));
+ rc = -620;
+ goto done;
+ }
+
+ /* Find REALM and NONCE attributes */
+ arealm = (pj_stun_realm_attr*)
+ pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0);
+ anonce = (pj_stun_nonce_attr*)
+ pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0);
+
+ if (expected_realm) {
+ if (arealm == NULL) {
+ PJ_LOG(3,(THIS_FILE, " err: expecting REALM in esponse"));
+ rc = -630;
+ goto done;
+ }
+ if (pj_strcmp2(&arealm->value, expected_realm)!=0) {
+ PJ_LOG(3,(THIS_FILE, " err: REALM mismatch in response"));
+ rc = -640;
+ goto done;
+ }
+ } else {
+ if (arealm != NULL) {
+ PJ_LOG(3,(THIS_FILE, " err: non expecting REALM in response"));
+ rc = -650;
+ goto done;
+ }
+ }
+
+ if (expected_nonce) {
+ if (anonce == NULL) {
+ PJ_LOG(3,(THIS_FILE, " err: expecting NONCE in esponse"));
+ rc = -660;
+ goto done;
+ }
+ if (pj_strcmp2(&anonce->value, expected_nonce)!=0) {
+ PJ_LOG(3,(THIS_FILE, " err: NONCE mismatch in response"));
+ rc = -670;
+ goto done;
+ }
+ } else {
+ if (anonce != NULL) {
+ PJ_LOG(3,(THIS_FILE, " err: non expecting NONCE in response"));
+ rc = -680;
+ goto done;
+ }
+ }
+ }
+
+ /* Our tests are okay so far. Let caller do some more tests if
+ * it wants to.
+ */
+ if (rc==0 && more_check) {
+ rc = (*more_check)();
+ }
+
+
+done:
+ destroy_client_server();
+ return rc;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// More verification
+//
+
+/* Retransmission test */
+static int retransmit_check(void)
+{
+
+ if (server->recv_count != PJ_STUN_MAX_TRANSMIT_COUNT) {
+ PJ_LOG(3,("", " expecting %d retransmissions, got %d",
+ PJ_STUN_MAX_TRANSMIT_COUNT, server->recv_count));
+ return -700;
+ }
+ if (client->recv_count != 0)
+ return -710;
+
+ return 0;
+}
+
+static int long_term_check1(void)
+{
+ /* SHOULD NOT contain USERNAME or MESSAGE-INTEGRITY */
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_USERNAME, 0))
+ return -800;
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0))
+ return -800;
+
+ return 0;
+}
+
+static int long_term_check2(void)
+{
+ /* response SHOULD NOT include a USERNAME, NONCE, REALM or
+ * MESSAGE-INTEGRITY attribute.
+ */
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_USERNAME, 0))
+ return -900;
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0))
+ return -910;
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0))
+ return -920;
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0))
+ return -930;
+
+ return 0;
+}
+
+static int long_term_check3(void)
+{
+ /* response SHOULD NOT include a USERNAME, NONCE, and REALM */
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_USERNAME, 0))
+ return -1000;
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_NONCE, 0))
+ return -1010;
+ if (pj_stun_msg_find_attr(client->response, PJ_STUN_ATTR_REALM, 0))
+ return -1020;
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// TEST MAIN
+//
+
+
+int sess_auth_test(void)
+{
+ pj_pool_t *pool;
+ int rc;
+
+ PJ_LOG(3,(THIS_FILE, " STUN session authentication test"));
+
+ /* Init STUN config */
+ pj_stun_config_init(&stun_cfg, mem, 0, NULL, NULL);
+
+ /* Create pool and timer heap */
+ pool = pj_pool_create(mem, "authtest", 200, 200, NULL);
+ if (pj_timer_heap_create(pool, 20, &stun_cfg.timer_heap)) {
+ pj_pool_release(pool);
+ return -5;
+ }
+
+ /* Basic retransmission test */
+ rc = run_client_test("Retransmission", // title
+ PJ_FALSE, // server responding
+ PJ_STUN_AUTH_NONE, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ NULL, // realm
+ NULL, // username
+ NULL, // nonce
+ NULL, // password
+ PJ_FALSE, // dummy MI
+ PJ_TRUE, // expected error
+ PJNATH_ESTUNTIMEDOUT,// expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ &retransmit_check // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /*
+ * Short term credential.
+ * draft-ietf-behave-rfc3489bis-15#section-10.1.2
+ */
+
+ /*
+ * If the message does not contain both a MESSAGE-INTEGRITY and a
+ * USERNAME attribute, If the message is a request, the server MUST
+ * reject the request with an error response. This response MUST
+ * use an error code of 400 (Bad Request).
+ */
+ rc = run_client_test("Missing MESSAGE-INTEGRITY (short term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_SHORT_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ NULL, // realm
+ NULL, // username
+ NULL, // nonce
+ NULL, // password
+ PJ_FALSE, // dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(400),// expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ NULL // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* If the USERNAME does not contain a username value currently valid
+ * within the server: If the message is a request, the server MUST
+ * reject the request with an error response. This response MUST use
+ * an error code of 401 (Unauthorized).
+ */
+ rc = run_client_test("USERNAME mismatch (short term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_SHORT_TERM, // server auth
+ PJ_STUN_AUTH_SHORT_TERM, // client auth
+ NULL, // realm
+ "anotheruser", // username
+ NULL, // nonce
+ "anotherpass", // password
+ PJ_FALSE, // dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(401),// expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ NULL // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* Using the password associated with the username, compute the value
+ * for the message-integrity as described in Section 15.4. If the
+ * resulting value does not match the contents of the MESSAGE-
+ * INTEGRITY attribute:
+ *
+ * - If the message is a request, the server MUST reject the request
+ * with an error response. This response MUST use an error code
+ * of 401 (Unauthorized).
+ */
+ rc = run_client_test("MESSAGE-INTEGRITY mismatch (short term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_SHORT_TERM, // server auth
+ PJ_STUN_AUTH_SHORT_TERM, // client auth
+ NULL, // realm
+ USERNAME, // username
+ NULL, // nonce
+ "anotherpass", // password
+ PJ_FALSE, // dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(401),// expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ NULL // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* USERNAME is not present, server must respond with 400 (Bad
+ * Request).
+ */
+ rc = run_client_test("Missing USERNAME (short term)",// title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_SHORT_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ NULL, // realm
+ NULL, // username
+ NULL, // nonce
+ NULL, // password
+ PJ_TRUE, // dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(400), // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ NULL // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* Successful short term authentication */
+ rc = run_client_test("Successful scenario (short term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_SHORT_TERM, // server auth
+ PJ_STUN_AUTH_SHORT_TERM, // client auth
+ NULL, // realm
+ USERNAME, // username
+ NULL, // nonce
+ PASSWORD, // password
+ PJ_FALSE, // dummy MI
+ PJ_FALSE, // expected error
+ PJ_SUCCESS, // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ NULL // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /*
+ * (our own) Extended tests for long term credential
+ */
+
+ /* When server wants to use short term credential, but request has
+ * REALM, reject with .... 401 ???
+ */
+ rc = run_client_test("Unwanted REALM (short term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_SHORT_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ REALM, // realm
+ USERNAME, // username
+ NULL, // nonce
+ PASSWORD, // password
+ PJ_TRUE, // dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(401), // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ &long_term_check2 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+
+ /*
+ * Long term credential.
+ * draft-ietf-behave-rfc3489bis-15#section-10.2.2
+ */
+
+ /* If the message does not contain a MESSAGE-INTEGRITY attribute, the
+ * server MUST generate an error response with an error code of 401
+ * (Unauthorized). This response MUST include a REALM value. It is
+ * RECOMMENDED that the REALM value be the domain name of the
+ * provider of the STUN server. The response MUST include a NONCE,
+ * selected by the server. The response SHOULD NOT contain a
+ * USERNAME or MESSAGE-INTEGRITY attribute.
+ */
+ rc = run_client_test("Missing M-I (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ NULL, // client realm
+ NULL, // client username
+ NULL, // client nonce
+ NULL, // client password
+ PJ_FALSE, // client dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(401), // expected code
+ REALM, // expected realm
+ NONCE, // expected nonce
+ &long_term_check1 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* If the message contains a MESSAGE-INTEGRITY attribute, but is
+ * missing the USERNAME, REALM or NONCE attributes, the server MUST
+ * generate an error response with an error code of 400 (Bad
+ * Request). This response SHOULD NOT include a USERNAME, NONCE,
+ * REALM or MESSAGE-INTEGRITY attribute.
+ */
+ /* Missing USERNAME */
+ rc = run_client_test("Missing USERNAME (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ REALM, // client realm
+ NULL, // client username
+ NONCE, // client nonce
+ PASSWORD, // client password
+ PJ_TRUE, // client dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(400), // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ &long_term_check2 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* Missing REALM */
+ rc = run_client_test("Missing REALM (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ NULL, // client realm
+ USERNAME, // client username
+ NONCE, // client nonce
+ PASSWORD, // client password
+ PJ_TRUE, // client dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(400), // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ &long_term_check2 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* Missing NONCE */
+ rc = run_client_test("Missing NONCE (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_NONE, // client auth
+ REALM, // client realm
+ USERNAME, // client username
+ NULL, // client nonce
+ PASSWORD, // client password
+ PJ_TRUE, // client dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(400), // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ &long_term_check2 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* If the NONCE is no longer valid, the server MUST generate an error
+ * response with an error code of 438 (Stale Nonce). This response
+ * MUST include a NONCE and REALM attribute and SHOULD NOT incude the
+ * USERNAME or MESSAGE-INTEGRITY attribute. Servers can invalidate
+ * nonces in order to provide additional security. See Section 4.3
+ * of [RFC2617] for guidelines.
+ */
+ // how??
+
+ /* If the username in the USERNAME attribute is not valid, the server
+ * MUST generate an error response with an error code of 401
+ * (Unauthorized). This response MUST include a REALM value. It is
+ * RECOMMENDED that the REALM value be the domain name of the
+ * provider of the STUN server. The response MUST include a NONCE,
+ * selected by the server. The response SHOULD NOT contain a
+ * USERNAME or MESSAGE-INTEGRITY attribute.
+ */
+ rc = run_client_test("Invalid username (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_LONG_TERM, // client auth
+ REALM, // client realm
+ "anotheruser", // client username
+ "a nonce", // client nonce
+ "somepassword", // client password
+ PJ_FALSE, // client dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(401), // expected code
+ REALM, // expected realm
+ NONCE, // expected nonce
+ &long_term_check1 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /* Successful long term authentication */
+ rc = run_client_test("Successful scenario (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_LONG_TERM, // client auth
+ REALM, // client realm
+ USERNAME, // client username
+ "anothernonce", // client nonce
+ PASSWORD, // client password
+ PJ_FALSE, // client dummy MI
+ PJ_FALSE, // expected error
+ 0, // expected code
+ NULL, // expected realm
+ NULL, // expected nonce
+ &long_term_check3 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+
+ /*
+ * (our own) Extended tests for long term credential
+ */
+
+ /* If REALM doesn't match, server must respond with 401
+ */
+#if 0
+ // STUN session now will just use the realm sent in the
+ // response, so this test will fail because it will
+ // authenticate successfully.
+
+ rc = run_client_test("Invalid REALM (long term)", // title
+ PJ_TRUE, // server responding
+ PJ_STUN_AUTH_LONG_TERM, // server auth
+ PJ_STUN_AUTH_LONG_TERM, // client auth
+ "anotherrealm", // client realm
+ USERNAME, // client username
+ NONCE, // client nonce
+ PASSWORD, // client password
+ PJ_FALSE, // client dummy MI
+ PJ_TRUE, // expected error
+ PJ_STATUS_FROM_STUN_CODE(401), // expected code
+ REALM, // expected realm
+ NONCE, // expected nonce
+ &long_term_check1 // more check
+ );
+ if (rc != 0) {
+ goto done;
+ }
+#endif
+
+ /* Invalid HMAC */
+
+ /* Valid static short term, without NONCE */
+
+ /* Valid static short term, WITH NONCE */
+
+ /* Valid static long term (with NONCE */
+
+ /* Valid dynamic short term (without NONCE) */
+
+ /* Valid dynamic short term (with NONCE) */
+
+ /* Valid dynamic long term (with NONCE) */
+
+
+done:
+ pj_timer_heap_destroy(stun_cfg.timer_heap);
+ pj_pool_release(pool);
+ return rc;
+}
diff --git a/pjnath/src/pjnath-test/stun.c b/pjnath/src/pjnath-test/stun.c
new file mode 100644
index 0000000..092f90e
--- /dev/null
+++ b/pjnath/src/pjnath-test/stun.c
@@ -0,0 +1,983 @@
+/* $Id: stun.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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.c"
+
+static pj_stun_msg* create1(pj_pool_t*);
+static int verify1(pj_stun_msg*);
+static int verify2(pj_stun_msg*);
+static int verify5(pj_stun_msg*);
+
+static struct test
+{
+ const char *title;
+ char *pdu;
+ unsigned pdu_len;
+ pj_stun_msg* (*create)(pj_pool_t*);
+ pj_status_t expected_status;
+ int (*verify)(pj_stun_msg*);
+} tests[] =
+{
+ {
+ "Invalid message type",
+ "\x11\x01\x00\x00\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ 20,
+ NULL,
+ PJNATH_EINSTUNMSGTYPE,
+ NULL
+ },
+ {
+ "Short message (1) (partial header)",
+ "\x00\x01",
+ 2,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (2) (partial header)",
+ "\x00\x01\x00\x00\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00",
+ 16,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (3), (missing attribute)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+ 20,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (4), (partial attribute header)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28",
+ 22,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (5), (partial attribute header)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00",
+ 23,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (6), (partial attribute header)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04",
+ 24,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Short message (7), (partial attribute body)",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00",
+ 27,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Message length in header is too long",
+ "\x00\x01\xff\xff\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00",
+ 27,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Message length in header is shorter",
+ "\x00\x01\x00\x04\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Invalid magic",
+ "\x00\x01\x00\x08\x00\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJ_SUCCESS,
+ NULL
+ },
+ {
+ "Character beyond message",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00\x0a",
+ 29,
+ NULL,
+ PJNATH_EINSTUNMSGLEN,
+ NULL
+ },
+ {
+ "Respond unknown mandatory attribute with 420 and "
+ "UNKNOWN-ATTRIBUTES attribute",
+ NULL,
+ 0,
+ &create1,
+ 0,
+ &verify1
+ },
+ {
+ "Unknown but non-mandatory should be okay",
+ "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\xff\x00\x03\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJ_SUCCESS,
+ &verify2
+ },
+ {
+ "String attr length larger than message",
+ "\x00\x01\x00\x08\x00\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x06\x00\xff\x00\x00\x00\x00",
+ 28,
+ NULL,
+ PJNATH_ESTUNINATTRLEN,
+ NULL
+ },
+ {
+ "Attribute other than FINGERPRINT after MESSAGE-INTEGRITY is allowed",
+ "\x00\x01\x00\x20\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x08\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // M-I
+ "\x80\x24\x00\x04\x00\x00\x00\x00", // REFRESH-INTERVAL
+ 52,
+ NULL,
+ PJ_SUCCESS,
+ NULL
+ },
+ {
+ "Attribute between MESSAGE-INTEGRITY and FINGERPRINT is allowed",
+ "\x00\x01\x00\x28\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x08\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // M-I
+ "\x80\x24\x00\x04\x00\x00\x00\x00" // REFRESH-INTERVAL
+ "\x80\x28\x00\x04\xc7\xde\xdd\x65", // FINGERPRINT
+ 60,
+ NULL,
+ PJ_SUCCESS,
+ &verify5
+ },
+ {
+ "Attribute past FINGERPRINT is not allowed",
+ "\x00\x01\x00\x10\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x80\x28\x00\x04\x00\x00\x00\x00"
+ "\x80\x24\x00\x04\x00\x00\x00\x00",
+ 36,
+ NULL,
+ PJNATH_ESTUNFINGERPOS,
+ NULL
+ }
+};
+
+static const char *err(pj_status_t status)
+{
+ static char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ return errmsg;
+}
+
+static const pj_str_t USERNAME = {"user", 4};
+static const pj_str_t PASSWORD = {"password", 8};
+
+static int decode_test(void)
+{
+ unsigned i;
+ pj_pool_t *pool;
+ int rc = 0;
+
+ pool = pj_pool_create(mem, "decode_test", 1024, 1024, NULL);
+
+ PJ_LOG(3,(THIS_FILE, " STUN decode test"));
+
+ for (i=0; i<PJ_ARRAY_SIZE(tests); ++i) {
+ struct test *t = &tests[i];
+ pj_stun_msg *msg, *msg2;
+ pj_uint8_t buf[1500];
+ pj_str_t key;
+ pj_size_t len;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " %s", t->title));
+
+ if (t->pdu) {
+ status = pj_stun_msg_decode(pool, (pj_uint8_t*)t->pdu, t->pdu_len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg, NULL, NULL);
+
+ /* Check expected decode result */
+ if (t->expected_status != status) {
+ PJ_LOG(1,(THIS_FILE, " expecting status %d, got %d",
+ t->expected_status, status));
+ rc = -10;
+ goto on_return;
+ }
+
+ } else {
+ msg = t->create(pool);
+ status = PJ_SUCCESS;
+ }
+
+ if (status != PJ_SUCCESS)
+ continue;
+
+ /* Try to encode message */
+ pj_stun_create_key(pool, &key, NULL, &USERNAME, PJ_STUN_PASSWD_PLAIN, &PASSWORD);
+ status = pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, " encode error: %s", err(status)));
+ rc = -40;
+ goto on_return;
+ }
+
+ /* Try to decode it once more */
+ status = pj_stun_msg_decode(pool, buf, len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg2, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, " subsequent decoding failed: %s", err(status)));
+ rc = -50;
+ goto on_return;
+ }
+
+ /* Verify */
+ if (t->verify) {
+ rc = t->verify(msg);
+ if (rc != 0) {
+ goto on_return;
+ }
+ }
+ }
+
+on_return:
+ pj_pool_release(pool);
+ if (rc == 0)
+ PJ_LOG(3,(THIS_FILE, "...success!"));
+ return rc;
+}
+
+/* Create 420 response */
+static pj_stun_msg* create1(pj_pool_t *pool)
+{
+ char *pdu = "\x00\x01\x00\x08\x21\x12\xa4\x42"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\xff\x00\x04\x00\x00\x00\x00";
+ unsigned pdu_len = 28;
+ pj_stun_msg *msg, *res;
+ pj_status_t status;
+
+ status = pj_stun_msg_decode(pool, (pj_uint8_t*)pdu, pdu_len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg, NULL, &res);
+ pj_assert(status != PJ_SUCCESS);
+ pj_assert(res != NULL);
+
+ return res;
+}
+
+/* Error response MUST have ERROR-CODE attribute */
+/* 420 response MUST contain UNKNOWN-ATTRIBUTES */
+static int verify1(pj_stun_msg *msg)
+{
+ pj_stun_errcode_attr *aerr;
+ pj_stun_unknown_attr *aunk;
+
+ if (!PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) {
+ PJ_LOG(1,(THIS_FILE, " expecting error message"));
+ return -100;
+ }
+
+ aerr = (pj_stun_errcode_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
+ if (aerr == NULL) {
+ PJ_LOG(1,(THIS_FILE, " missing ERROR-CODE attribute"));
+ return -110;
+ }
+
+ if (aerr->err_code != 420) {
+ PJ_LOG(1,(THIS_FILE, " expecting 420 error"));
+ return -120;
+ }
+
+ aunk = (pj_stun_unknown_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
+ if (aunk == NULL) {
+ PJ_LOG(1,(THIS_FILE, " missing UNKNOWN-ATTRIBUTE attribute"));
+ return -130;
+ }
+
+ if (aunk->attr_count != 1) {
+ PJ_LOG(1,(THIS_FILE, " expecting one unknown attribute"));
+ return -140;
+ }
+
+ if (aunk->attrs[0] != 0xff) {
+ PJ_LOG(1,(THIS_FILE, " expecting 0xff as unknown attribute"));
+ return -150;
+ }
+
+ return 0;
+}
+
+/* Attribute count should be zero since unknown attribute is not parsed */
+static int verify2(pj_stun_msg *msg)
+{
+ pj_stun_binary_attr *bin_attr;
+
+ if (msg->attr_count != 1) {
+ PJ_LOG(1,(THIS_FILE, " expecting one attribute count"));
+ return -200;
+ }
+
+ bin_attr = (pj_stun_binary_attr*)msg->attr[0];
+ if (bin_attr->hdr.type != 0x80ff) {
+ PJ_LOG(1,(THIS_FILE, " expecting attribute type 0x80ff"));
+ return -210;
+ }
+ if (bin_attr->hdr.length != 3) {
+ PJ_LOG(1,(THIS_FILE, " expecting attribute length = 4"));
+ return -220;
+ }
+ if (bin_attr->magic != PJ_STUN_MAGIC) {
+ PJ_LOG(1,(THIS_FILE, " expecting PJ_STUN_MAGIC for unknown attr"));
+ return -230;
+ }
+ if (bin_attr->length != 3) {
+ PJ_LOG(1,(THIS_FILE, " expecting data length 4"));
+ return -240;
+ }
+
+ return 0;
+}
+
+
+/* Attribute between MESSAGE-INTEGRITY and FINGERPRINT is allowed */
+static int verify5(pj_stun_msg *msg)
+{
+ if (msg->attr_count != 3) {
+ PJ_LOG(1,(THIS_FILE, " expecting 3 attribute count"));
+ return -500;
+ }
+
+ if (msg->attr[0]->type != PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ PJ_LOG(1,(THIS_FILE, " expecting MESSAGE-INTEGRITY"));
+ return -510;
+ }
+ if (msg->attr[1]->type != PJ_STUN_ATTR_REFRESH_INTERVAL) {
+ PJ_LOG(1,(THIS_FILE, " expecting REFRESH-INTERVAL"));
+ return -520;
+ }
+ if (msg->attr[2]->type != PJ_STUN_ATTR_FINGERPRINT) {
+ PJ_LOG(1,(THIS_FILE, " expecting FINGERPRINT"));
+ return -530;
+ }
+
+ return 0;
+}
+
+
+static int decode_verify(void)
+{
+ /* Decode all attribute types */
+ return 0;
+}
+
+/*
+ * Test vectors, from:
+ * http://tools.ietf.org/html/draft-denis-behave-rfc3489bis-test-vectors-02
+ */
+typedef struct test_vector test_vector;
+
+static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v);
+static pj_stun_msg* create_msgint2(pj_pool_t *pool, test_vector *v);
+static pj_stun_msg* create_msgint3(pj_pool_t *pool, test_vector *v);
+
+enum
+{
+ USE_MESSAGE_INTEGRITY = 1,
+ USE_FINGERPRINT = 2
+};
+
+static struct test_vector
+{
+ unsigned msg_type;
+ char *tsx_id;
+ char *pdu;
+ unsigned pdu_len;
+ unsigned options;
+ char *username;
+ char *password;
+ char *realm;
+ char *nonce;
+ pj_stun_msg* (*create)(pj_pool_t*, test_vector*);
+} test_vectors[] =
+{
+ {
+ PJ_STUN_BINDING_REQUEST,
+ "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
+ "\x00\x01\x00\x44\x21\x12\xa4\x42\xb7\xe7"
+ "\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
+ "\x00\x24\x00\x04\x6e\x00\x01\xff\x80\x29"
+ "\x00\x08\x93\x2f\xf9\xb1\x51\x26\x3b\x36"
+ "\x00\x06\x00\x09\x65\x76\x74\x6a\x3a\x68"
+ "\x36\x76\x59\x20\x20\x20\x00\x08\x00\x14"
+ "\x62\x4e\xeb\xdc\x3c\xc9\x2d\xd8\x4b\x74"
+ "\xbf\x85\xd1\xc0\xf5\xde\x36\x87\xbd\x33"
+ "\x80\x28\x00\x04\xad\x8a\x85\xff",
+ 88,
+ USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
+ "evtj:h6vY",
+ "VOkJxbRl1RmTxUk/WvJxBt",
+ "",
+ "",
+ &create_msgint1
+ }
+ /* disabled: see http://trac.pjsip.org/repos/ticket/960
+ ,
+ {
+ PJ_STUN_BINDING_RESPONSE,
+ "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
+ "\x01\x01\x00\x3c"
+ "\x21\x12\xa4\x42"
+ "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
+ "\x80\x22\x00\x0b"
+ "\x74\x65\x73\x74\x20\x76\x65\x63\x74\x6f\x72\x20"
+ "\x00\x20\x00\x08"
+ "\x00\x01\xa1\x47\xe1\x12\xa6\x43"
+ "\x00\x08\x00\x14"
+ "\x2b\x91\xf5\x99\xfd\x9e\x90\xc3\x8c\x74\x89\xf9"
+ "\x2a\xf9\xba\x53\xf0\x6b\xe7\xd7"
+ "\x80\x28\x00\x04"
+ "\xc0\x7d\x4c\x96",
+ 80,
+ USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
+ "evtj:h6vY",
+ "VOkJxbRl1RmTxUk/WvJxBt",
+ "",
+ "",
+ &create_msgint2
+ }
+ */
+
+ /* disabled: see http://trac.pjsip.org/repos/ticket/960
+#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6!=0
+ ,
+ {
+ PJ_STUN_BINDING_RESPONSE,
+ "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
+ "\x01\x01\x00\x48" // Response type and message length
+ "\x21\x12\xa4\x42" // Message cookie
+ "\xb7\xe7\xa7\x01" // }
+ "\xbc\x34\xd6\x86" // } Transaction ID
+ "\xfa\x87\xdf\xae" // }
+
+ "\x80\x22\x00\x0b" // SOFTWARE, length=11
+ "\x74\x65\x73\x74"
+ "\x20\x76\x65\x63"
+ "\x74\x6f\x72\x20"
+ "\x00\x20\x00\x14" // XOR-MAPPED-ADDRESS
+ "\x00\x02\xa1\x47"
+ "\x01\x13\xa9\xfa"
+ "\xa5\xd3\xf1\x79"
+ "\xbc\x25\xf4\xb5"
+ "\xbe\xd2\xb9\xd9"
+ "\x00\x08\x00\x14" // MESSAGE-INTEGRITY attribute header
+ "\xa3\x82\x95\x4e" // }
+ "\x4b\xe6\x7b\xf1" // }
+ "\x17\x84\xc9\x7c" // } HMAC-SHA1 fingerprint
+ "\x82\x92\xc2\x75" // }
+ "\xbf\xe3\xed\x41" // }
+ "\x80\x28\x00\x04" // FINGERPRINT attribute header
+ "\xc8\xfb\x0b\x4c" // CRC32 fingerprint
+ ,
+ 92,
+ USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
+ "evtj:h6vY",
+ "VOkJxbRl1RmTxUk/WvJxBt",
+ "",
+ "",
+ &create_msgint3
+ }
+#endif
+ */
+};
+
+
+static char* print_binary(const pj_uint8_t *data, unsigned data_len)
+{
+ static char buf[1500];
+ unsigned length = sizeof(buf);
+ char *p = buf;
+ unsigned i;
+
+ for (i=0; i<data_len;) {
+ unsigned j;
+
+ pj_ansi_snprintf(p, 1500-(p-buf),
+ "%04d-%04d ",
+ i, (i+20 < data_len) ? i+20 : data_len);
+ p += 12;
+
+ for (j=0; j<20 && i<data_len && p<(buf+length-10); ++j, ++i) {
+ pj_ansi_sprintf(p, "%02x ", (*data) & 0xFF);
+ p += 3;
+ data++;
+ }
+
+ pj_ansi_sprintf(p, "\n");
+ p++;
+ }
+
+ return buf;
+}
+
+static int cmp_buf(const pj_uint8_t *s1, const pj_uint8_t *s2, unsigned len)
+{
+ unsigned i;
+ for (i=0; i<len; ++i) {
+ if (s1[i] != s2[i])
+ return i;
+ }
+
+ return -1;
+}
+
+static int fingerprint_test_vector()
+{
+ pj_pool_t *pool;
+ pj_status_t status;
+ unsigned i;
+ int rc = 0;
+
+ /* To avoid function not referenced warnings */
+ (void)create_msgint2;
+ (void)create_msgint3;
+
+ PJ_LOG(3,(THIS_FILE, " draft-denis-behave-rfc3489bis-test-vectors-02"));
+
+ pool = pj_pool_create(mem, "fingerprint", 1024, 1024, NULL);
+
+ for (i=0; i<PJ_ARRAY_SIZE(test_vectors); ++i) {
+ struct test_vector *v;
+ pj_stun_msg *ref_msg, *msg;
+ pj_size_t parsed_len;
+ pj_size_t len;
+ unsigned pos;
+ pj_uint8_t buf[1500];
+ char print[1500];
+ pj_str_t key;
+
+ PJ_LOG(3,(THIS_FILE, " Running test %d/%d", i,
+ PJ_ARRAY_SIZE(test_vectors)));
+
+ v = &test_vectors[i];
+
+ /* Print reference message */
+ PJ_LOG(4,(THIS_FILE, "Reference message PDU:\n%s",
+ print_binary((pj_uint8_t*)v->pdu, v->pdu_len)));
+
+ /* Try to parse the reference message first */
+ status = pj_stun_msg_decode(pool, (pj_uint8_t*)v->pdu, v->pdu_len,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &ref_msg, &parsed_len, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(THIS_FILE, " Error decoding reference message"));
+ rc = -1010;
+ goto on_return;
+ }
+
+ if (parsed_len != v->pdu_len) {
+ PJ_LOG(1,(THIS_FILE, " Parsed len error"));
+ rc = -1020;
+ goto on_return;
+ }
+
+ /* Print the reference message */
+ pj_stun_msg_dump(ref_msg, print, sizeof(print), NULL);
+ PJ_LOG(4,(THIS_FILE, "Reference message:\n%s", print));
+
+ /* Create our message */
+ msg = v->create(pool, v);
+ if (msg == NULL) {
+ PJ_LOG(1,(THIS_FILE, " Error creating stun message"));
+ rc = -1030;
+ goto on_return;
+ }
+
+ /* Encode message */
+ if (v->options & USE_MESSAGE_INTEGRITY) {
+ pj_str_t s1, s2, r;
+
+ pj_stun_create_key(pool, &key, pj_cstr(&r, v->realm),
+ pj_cstr(&s1, v->username),
+ PJ_STUN_PASSWD_PLAIN,
+ pj_cstr(&s2, v->password));
+ pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
+
+ } else {
+ pj_stun_msg_encode(msg, buf, sizeof(buf), 0, NULL, &len);
+ }
+
+ /* Print our raw message */
+ PJ_LOG(4,(THIS_FILE, "Message PDU:\n%s",
+ print_binary((pj_uint8_t*)buf, len)));
+
+ /* Print our message */
+ pj_stun_msg_dump(msg, print, sizeof(print), NULL);
+ PJ_LOG(4,(THIS_FILE, "Message is:\n%s", print));
+
+ /* Compare message length */
+ if (len != v->pdu_len) {
+ PJ_LOG(1,(THIS_FILE, " Message length mismatch"));
+ rc = -1050;
+ goto on_return;
+ }
+
+ pos = cmp_buf(buf, (const pj_uint8_t*)v->pdu, len);
+ if (pos != (unsigned)-1) {
+ PJ_LOG(1,(THIS_FILE, " Message mismatch at byte %d", pos));
+ rc = -1060;
+ goto on_return;
+ }
+
+ /* Authenticate the request/response */
+ if (v->options & USE_MESSAGE_INTEGRITY) {
+ if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+ pj_stun_auth_cred cred;
+ pj_status_t status;
+
+ pj_bzero(&cred, sizeof(cred));
+ cred.type = PJ_STUN_AUTH_CRED_STATIC;
+ cred.data.static_cred.realm = pj_str(v->realm);
+ cred.data.static_cred.username = pj_str(v->username);
+ cred.data.static_cred.data = pj_str(v->password);
+ cred.data.static_cred.nonce = pj_str(v->nonce);
+
+ status = pj_stun_authenticate_request(buf, len, msg,
+ &cred, pool, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE,
+ " Request authentication failed: %s",
+ errmsg));
+ rc = -1070;
+ goto on_return;
+ }
+
+ } else if (PJ_STUN_IS_RESPONSE(msg->hdr.type)) {
+ pj_status_t status;
+ status = pj_stun_authenticate_response(buf, len, msg, &key);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE,
+ " Response authentication failed: %s",
+ errmsg));
+ rc = -1080;
+ goto on_return;
+ }
+ }
+ }
+ }
+
+
+on_return:
+ pj_pool_release(pool);
+ return rc;
+}
+
+static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v)
+{
+ pj_stun_msg *msg;
+ pj_timestamp u64;
+ pj_str_t s1;
+ pj_status_t status;
+
+ status = pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
+ (pj_uint8_t*)v->tsx_id, &msg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_PRIORITY,
+ 0x6e0001ff);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ u64.u32.hi = 0x932ff9b1;
+ u64.u32.lo = 0x51263b36;
+ status = pj_stun_msg_add_uint64_attr(pool, msg,
+ PJ_STUN_ATTR_ICE_CONTROLLED, &u64);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_USERNAME,
+ pj_cstr(&s1, v->username));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_msgint_attr(pool, msg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return msg;
+
+on_error:
+ app_perror(" error: create_msgint1()", status);
+ return NULL;
+}
+
+static pj_stun_msg* create_msgint2(pj_pool_t *pool, test_vector *v)
+{
+ pj_stun_msg *msg;
+ pj_sockaddr_in mapped_addr;
+ pj_str_t s1;
+ pj_status_t status;
+
+ status = pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
+ (pj_uint8_t*)v->tsx_id, &msg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE,
+ pj_cstr(&s1, "test vector"));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_sockaddr_in_init(&mapped_addr, pj_cstr(&s1, "192.0.2.1"),
+ 32853);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_sockaddr_attr(pool, msg,
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+ PJ_TRUE, &mapped_addr,
+ sizeof(pj_sockaddr_in));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_msgint_attr(pool, msg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return msg;
+
+on_error:
+ app_perror(" error: create_msgint2()", status);
+ return NULL;
+}
+
+
+static pj_stun_msg* create_msgint3(pj_pool_t *pool, test_vector *v)
+{
+ pj_stun_msg *msg;
+ pj_sockaddr mapped_addr;
+ pj_str_t s1;
+ pj_status_t status;
+
+ status = pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
+ (pj_uint8_t*)v->tsx_id, &msg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE,
+ pj_cstr(&s1, "test vector"));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_sockaddr_init(pj_AF_INET6(), &mapped_addr,
+ pj_cstr(&s1, "2001:db8:1234:5678:11:2233:4455:6677"),
+ 32853);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_sockaddr_attr(pool, msg,
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+ PJ_TRUE, &mapped_addr,
+ sizeof(pj_sockaddr));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_msgint_attr(pool, msg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return msg;
+
+on_error:
+ app_perror(" error: create_msgint3()", status);
+ return NULL;
+}
+
+
+/* Compare two messages */
+static int cmp_msg(const pj_stun_msg *msg1, const pj_stun_msg *msg2)
+{
+ unsigned i;
+
+ if (msg1->hdr.type != msg2->hdr.type)
+ return -10;
+ if (msg1->hdr.length != msg2->hdr.length)
+ return -20;
+ if (msg1->hdr.magic != msg2->hdr.magic)
+ return -30;
+ if (pj_memcmp(msg1->hdr.tsx_id, msg2->hdr.tsx_id, sizeof(msg1->hdr.tsx_id)))
+ return -40;
+ if (msg1->attr_count != msg2->attr_count)
+ return -50;
+
+ for (i=0; i<msg1->attr_count; ++i) {
+ const pj_stun_attr_hdr *a1 = msg1->attr[i];
+ const pj_stun_attr_hdr *a2 = msg2->attr[i];
+
+ if (a1->type != a2->type)
+ return -60;
+ if (a1->length != a2->length)
+ return -70;
+ }
+
+ return 0;
+}
+
+/* Decode and authenticate message with unknown non-mandatory attribute */
+static int handle_unknown_non_mandatory(void)
+{
+ pj_pool_t *pool = pj_pool_create(mem, NULL, 1000, 1000, NULL);
+ pj_stun_msg *msg0, *msg1, *msg2;
+ pj_uint8_t data[] = { 1, 2, 3, 4, 5, 6};
+ pj_uint8_t packet[500];
+ pj_stun_auth_cred cred;
+ pj_size_t len;
+ pj_status_t rc;
+
+ PJ_LOG(3,(THIS_FILE, " handling unknown non-mandatory attr"));
+
+ PJ_LOG(3,(THIS_FILE, " encoding"));
+ rc = pj_stun_msg_create(pool, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, NULL, &msg0);
+ rc += pj_stun_msg_add_string_attr(pool, msg0, PJ_STUN_ATTR_USERNAME, &USERNAME);
+ rc += pj_stun_msg_add_binary_attr(pool, msg0, 0x80ff, data, sizeof(data));
+ rc += pj_stun_msg_add_msgint_attr(pool, msg0);
+ rc += pj_stun_msg_encode(msg0, packet, sizeof(packet), 0, &PASSWORD, &len);
+
+#if 0
+ if (1) {
+ unsigned i;
+ puts("");
+ printf("{ ");
+ for (i=0; i<len; ++i) printf("0x%02x, ", packet[i]);
+ puts(" }");
+ }
+#endif
+
+ PJ_LOG(3,(THIS_FILE, " decoding"));
+ rc += pj_stun_msg_decode(pool, packet, len, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ &msg1, NULL, NULL);
+
+ rc += cmp_msg(msg0, msg1);
+
+ pj_bzero(&cred, sizeof(cred));
+ cred.type = PJ_STUN_AUTH_CRED_STATIC;
+ cred.data.static_cred.username = USERNAME;
+ cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+ cred.data.static_cred.data = PASSWORD;
+
+ PJ_LOG(3,(THIS_FILE, " authenticating"));
+ rc += pj_stun_authenticate_request(packet, len, msg1, &cred, pool, NULL, NULL);
+
+ PJ_LOG(3,(THIS_FILE, " clone"));
+ msg2 = pj_stun_msg_clone(pool, msg1);
+ rc += cmp_msg(msg0, msg2);
+
+ pj_pool_release(pool);
+
+ return rc==0 ? 0 : -4410;
+}
+
+
+int stun_test(void)
+{
+ int pad, rc;
+
+ pad = pj_stun_set_padding_char(32);
+
+ rc = decode_test();
+ if (rc != 0)
+ goto on_return;
+
+ rc = decode_verify();
+ if (rc != 0)
+ goto on_return;
+
+ rc = fingerprint_test_vector();
+ if (rc != 0)
+ goto on_return;
+
+ rc = handle_unknown_non_mandatory();
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pj_stun_set_padding_char(pad);
+ return rc;
+}
+
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 0000000..7a309ea
--- /dev/null
+++ b/pjnath/src/pjnath-test/stun_sock_test.c
@@ -0,0 +1,849 @@
+/* $Id: stun_sock_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 = (struct stun_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 = (struct stun_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 = (struct stun_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
+ */
+ /* No longer valid due to this ticket:
+ * http://trac.pjsip.org/repos/ticket/742
+
+ 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_MAPPED_ADDR_CHANGE) {
+ 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
new file mode 100644
index 0000000..081df25
--- /dev/null
+++ b/pjnath/src/pjnath-test/test.c
@@ -0,0 +1,212 @@
+/* $Id: test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib.h>
+
+void app_perror(const char *msg, pj_status_t rc)
+{
+ char errbuf[256];
+
+ PJ_CHECK_STACK();
+
+ pj_strerror(rc, errbuf, sizeof(errbuf));
+ 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; \
+ PJ_LOG(3, ("test", \
+ "%s(%d)", \
+ (char*)(rc ? "..ERROR" : "..success"), rc)); \
+ if (rc!=0) goto on_return; \
+ } while (0)
+
+
+pj_pool_factory *mem;
+
+int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME |
+ PJ_LOG_HAS_MICRO_SEC;
+
+static int test_inner(void)
+{
+ pj_caching_pool caching_pool;
+ int rc = 0;
+
+ mem = &caching_pool.factory;
+
+#if 1
+ pj_log_set_level(3);
+ pj_log_set_decor(param_log_decor);
+#endif
+
+ rc = pj_init();
+ if (rc != 0) {
+ app_perror("pj_init() error!!", rc);
+ return rc;
+ }
+
+ pj_dump_config();
+ pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy, 0 );
+
+ pjlib_util_init();
+ pjnath_init();
+
+#if INCLUDE_STUN_TEST
+ DO_TEST(stun_test());
+ DO_TEST(sess_auth_test());
+#endif
+
+#if INCLUDE_ICE_TEST
+ 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;
+}
+
+int test_main(void)
+{
+ PJ_USE_EXCEPTION;
+
+ PJ_TRY {
+ return test_inner();
+ }
+ PJ_CATCH_ANY {
+ int id = PJ_GET_EXCEPTION();
+ PJ_LOG(3,("test", "FATAL: unhandled exception id %d (%s)",
+ id, pj_exception_id_name(id)));
+ }
+ PJ_END;
+
+ return -1;
+}
+
diff --git a/pjnath/src/pjnath-test/test.h b/pjnath/src/pjnath-test/test.h
new file mode 100644
index 0000000..bbba992
--- /dev/null
+++ b/pjnath/src/pjnath-test/test.h
@@ -0,0 +1,63 @@
+/* $Id: test.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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 <pjlib.h>
+#include <pjlib-util.h>
+#include <pjnath.h>
+
+#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 0000000..f2be81d
--- /dev/null
+++ b/pjnath/src/pjnath-test/turn_sock_test.c
@@ -0,0 +1,516 @@
+/* $Id: turn_sock_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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;
+}
+