From f3ab456a17af1c89a6e3be4d20c5944853df1cb0 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Mon, 7 Jan 2013 14:24:28 -0600 Subject: Import pjproject-2.0.1 --- pjnath/src/pjnath-test/ice_test.c | 878 +++++++++++++++++++++++ pjnath/src/pjnath-test/main.c | 62 ++ pjnath/src/pjnath-test/main_win32.c | 1 + pjnath/src/pjnath-test/server.c | 754 ++++++++++++++++++++ pjnath/src/pjnath-test/server.h | 110 +++ pjnath/src/pjnath-test/sess_auth.c | 1146 +++++++++++++++++++++++++++++++ pjnath/src/pjnath-test/stun.c | 983 ++++++++++++++++++++++++++ pjnath/src/pjnath-test/stun_sock_test.c | 849 +++++++++++++++++++++++ pjnath/src/pjnath-test/test.c | 212 ++++++ pjnath/src/pjnath-test/test.h | 63 ++ pjnath/src/pjnath-test/turn_sock_test.c | 516 ++++++++++++++ 11 files changed, 5574 insertions(+) create mode 100644 pjnath/src/pjnath-test/ice_test.c create mode 100644 pjnath/src/pjnath-test/main.c create mode 100644 pjnath/src/pjnath-test/main_win32.c create mode 100644 pjnath/src/pjnath-test/server.c create mode 100644 pjnath/src/pjnath-test/server.h create mode 100644 pjnath/src/pjnath-test/sess_auth.c create mode 100644 pjnath/src/pjnath-test/stun.c create mode 100644 pjnath/src/pjnath-test/stun_sock_test.c create mode 100644 pjnath/src/pjnath-test/test.c create mode 100644 pjnath/src/pjnath-test/test.h create mode 100644 pjnath/src/pjnath-test/turn_sock_test.c (limited to 'pjnath/src/pjnath-test') 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 + * + * 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; icfg.comp_cnt; ++i) { + unsigned cnt = PJ_ARRAY_SIZE(rcand) - rcand_cnt; + status = pj_ice_strans_enum_cands(remote->ice, i+1, &cnt, rcand+rcand_cnt); + if (status != PJ_SUCCESS) { + app_perror(INDENT "err: pj_ice_strans_enum_cands()", status); + return status; + } + rcand_cnt += cnt; + } + + 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; iice, i+1); + if (c1 == NULL) { + PJ_LOG(3,("", INDENT "err: unable to get valid pair for ice1 " + "component %d", i+1)); + return start_err - 2; + } + + 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 (; icfg.comp_cnt>i && + pj_ice_strans_get_valid_pair(ept1->ice, i+1) != NULL) + { + PJ_LOG(3,("", INDENT "err: ice1 shouldn't have valid pair " + "for component %d", i+1)); + return start_err - 8; + } + if (ept2->cfg.comp_cnt>i && + pj_ice_strans_get_valid_pair(ept2->ice, i+1) != NULL) + { + PJ_LOG(3,("", INDENT "err: ice2 shouldn't have valid pair " + "for component %d", i+1)); + return start_err - 9; + } + } + + 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; ititle)); + + /* For each test item, test with various answer delay */ + for (d=0; dua1.answer_delay = delay[d]; + cfg->ua2.answer_delay = delay[d]; + + /* For each test item, test with role conflict scenarios */ + for (j=0; jua1.role = role[j].ua1; + cfg->ua2.role = role[j].ua2; + + /* For each test item, test with different number of components */ + for (k1=1; k1<=2; ++k1) { + unsigned k2; + + cfg->ua1.comp_cnt = k1; + + for (k2=1; k2<=2; ++k2) { + char title[120]; + + sprintf(title, + "%s/%s, %dms answer delay, %d vs %d components", + pj_ice_sess_role_name(role[j].ua1), + pj_ice_sess_role_name(role[j].ua2), + delay[d], k1, k2); + + cfg->ua2.comp_cnt = k2; + rc = perform_test(title, &stun_cfg, cfg->server_flag, + &cfg->ua1, &cfg->ua2); + if (rc != 0) + goto on_return; + } + } + } + } + } + +on_return: + 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 + * + * 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 +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 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "server.h" +#include "test.h" + +#define THIS_FILE "server.c" +#define MAX_STUN_PKT 1500 +#define TURN_NONCE "thenonce" + +static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); +static pj_bool_t turn_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); +static pj_bool_t alloc_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status); + +pj_status_t create_test_server(pj_stun_config *stun_cfg, + pj_uint32_t flags, + const char *domain, + test_server **p_test_srv) +{ + pj_pool_t *pool; + test_server *test_srv; + pj_sockaddr hostip; + char strbuf[100]; + pj_status_t status; + + PJ_ASSERT_RETURN(stun_cfg && domain && p_test_srv, PJ_EINVAL); + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + return status; + + pool = pj_pool_create(mem, THIS_FILE, 512, 512, NULL); + test_srv = (test_server*) PJ_POOL_ZALLOC_T(pool, test_server); + test_srv->pool = pool; + test_srv->flags = flags; + test_srv->stun_cfg = stun_cfg; + + pj_strdup2(pool, &test_srv->domain, domain); + test_srv->username = pj_str(TURN_USERNAME); + test_srv->passwd = pj_str(TURN_PASSWD); + + pj_ioqueue_op_key_init(&test_srv->send_key, sizeof(test_srv->send_key)); + + if (flags & CREATE_DNS_SERVER) { + status = pj_dns_server_create(mem, test_srv->stun_cfg->ioqueue, + pj_AF_INET(), DNS_SERVER_PORT, + 0, &test_srv->dns_server); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + /* Add DNS A record for the domain, for fallback */ + if (flags & CREATE_A_RECORD_FOR_DOMAIN) { + pj_dns_parsed_rr rr; + pj_str_t res_name; + pj_in_addr ip_addr; + + pj_strdup2(pool, &res_name, domain); + ip_addr = hostip.ipv4.sin_addr; + pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + } + + } + + if (flags & CREATE_STUN_SERVER) { + pj_activesock_cb stun_sock_cb; + pj_sockaddr bound_addr; + + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_data_recvfrom = &stun_on_data_recvfrom; + + pj_sockaddr_in_init(&bound_addr.ipv4, NULL, STUN_SERVER_PORT); + + status = pj_activesock_create_udp(pool, &bound_addr, NULL, + test_srv->stun_cfg->ioqueue, + &stun_sock_cb, test_srv, + &test_srv->stun_sock, NULL); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + status = pj_activesock_start_recvfrom(test_srv->stun_sock, pool, + MAX_STUN_PKT, 0); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + if (test_srv->dns_server && (flags & CREATE_STUN_SERVER_DNS_SRV)) { + pj_str_t res_name, target; + pj_dns_parsed_rr rr; + pj_in_addr ip_addr; + + /* Add DNS entries: + * _stun._udp.domain 60 IN SRV 0 0 PORT stun.domain. + * stun.domain IN A 127.0.0.1 + */ + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "_stun._udp.%s", domain); + pj_strdup2(pool, &res_name, strbuf); + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "stun.%s", domain); + pj_strdup2(pool, &target, strbuf); + pj_dns_init_srv_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, 0, 0, + STUN_SERVER_PORT, &target); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + + res_name = target; + ip_addr = hostip.ipv4.sin_addr; + pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + } + + } + + if (flags & CREATE_TURN_SERVER) { + pj_activesock_cb turn_sock_cb; + pj_sockaddr bound_addr; + + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_data_recvfrom = &turn_on_data_recvfrom; + + pj_sockaddr_in_init(&bound_addr.ipv4, NULL, TURN_SERVER_PORT); + + status = pj_activesock_create_udp(pool, &bound_addr, NULL, + test_srv->stun_cfg->ioqueue, + &turn_sock_cb, test_srv, + &test_srv->turn_sock, NULL); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + status = pj_activesock_start_recvfrom(test_srv->turn_sock, pool, + MAX_STUN_PKT, 0); + if (status != PJ_SUCCESS) { + destroy_test_server(test_srv); + return status; + } + + if (test_srv->dns_server && (flags & CREATE_TURN_SERVER_DNS_SRV)) { + pj_str_t res_name, target; + pj_dns_parsed_rr rr; + pj_in_addr ip_addr; + + /* Add DNS entries: + * _turn._udp.domain 60 IN SRV 0 0 PORT turn.domain. + * turn.domain IN A 127.0.0.1 + */ + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "_turn._udp.%s", domain); + pj_strdup2(pool, &res_name, strbuf); + pj_ansi_snprintf(strbuf, sizeof(strbuf), + "turn.%s", domain); + pj_strdup2(pool, &target, strbuf); + pj_dns_init_srv_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, 0, 0, + TURN_SERVER_PORT, &target); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + + res_name = target; + ip_addr = hostip.ipv4.sin_addr; + pj_dns_init_a_rr(&rr, &res_name, PJ_DNS_CLASS_IN, 60, &ip_addr); + pj_dns_server_add_rec(test_srv->dns_server, 1, &rr); + } + } + + *p_test_srv = test_srv; + return PJ_SUCCESS; +} + +void destroy_test_server(test_server *test_srv) +{ + unsigned i; + + PJ_ASSERT_ON_FAIL(test_srv, return); + + for (i=0; iturn_alloc_cnt; ++i) { + pj_activesock_close(test_srv->turn_alloc[i].sock); + pj_pool_release(test_srv->turn_alloc[i].pool); + } + test_srv->turn_alloc_cnt = 0; + + if (test_srv->turn_sock) { + pj_activesock_close(test_srv->turn_sock); + test_srv->turn_sock = NULL; + } + + if (test_srv->stun_sock) { + pj_activesock_close(test_srv->stun_sock); + test_srv->stun_sock = NULL; + } + + if (test_srv->dns_server) { + pj_dns_server_destroy(test_srv->dns_server); + test_srv->dns_server = NULL; + } + + if (test_srv->pool) { + pj_pool_t *pool = test_srv->pool; + test_srv->pool = NULL; + pj_pool_release(pool); + } +} + +static pj_bool_t stun_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + test_server *test_srv; + pj_stun_msg *req, *resp = NULL; + pj_pool_t *pool; + pj_ssize_t len; + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + test_srv = (test_server*) pj_activesock_get_user_data(asock); + pool = pj_pool_create(test_srv->stun_cfg->pf, NULL, 512, 512, NULL); + + status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size, + PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET, + &req, NULL, NULL); + if (status != PJ_SUCCESS) + goto on_return; + + if (req->hdr.type != PJ_STUN_BINDING_REQUEST) { + pj_stun_msg_create_response(pool, req, PJ_STUN_SC_BAD_REQUEST, + NULL, &resp); + goto send_pkt; + } + + status = pj_stun_msg_create_response(pool, req, 0, NULL, &resp); + if (status != PJ_SUCCESS) + goto on_return; + + pj_stun_msg_add_sockaddr_attr(pool, resp, PJ_STUN_ATTR_XOR_MAPPED_ADDR, + PJ_TRUE, src_addr, addr_len); + +send_pkt: + status = pj_stun_msg_encode(resp, (pj_uint8_t*)data, MAX_STUN_PKT, + 0, NULL, &size); + if (status != PJ_SUCCESS) + goto on_return; + + len = size; + status = pj_activesock_sendto(asock, &test_srv->send_key, data, &len, + 0, src_addr, addr_len); + +on_return: + pj_pool_release(pool); + return PJ_TRUE; +} + + +static pj_stun_msg* create_success_response(test_server *test_srv, + turn_allocation *alloc, + pj_stun_msg *req, + pj_pool_t *pool, + unsigned lifetime, + pj_str_t *auth_key) +{ + pj_stun_msg *resp; + pj_str_t tmp; + pj_status_t status; + + /* Create response */ + status = pj_stun_msg_create_response(pool, req, 0, NULL, &resp); + if (status != PJ_SUCCESS) { + return NULL; + } + /* Add TURN_NONCE */ + pj_stun_msg_add_string_attr(pool, resp, PJ_STUN_ATTR_NONCE, pj_cstr(&tmp, TURN_NONCE)); + /* Add LIFETIME */ + pj_stun_msg_add_uint_attr(pool, resp, PJ_STUN_ATTR_LIFETIME, lifetime); + if (lifetime != 0) { + /* Add 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; iturn_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; iperm_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; iattr_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; jperm_cnt; ++j) { + if (pj_sockaddr_cmp(&alloc->perm[j], &pa->sockaddr)==0) + break; + } + + if (j==alloc->perm_cnt && alloc->perm_cnt < MAX_TURN_PERM) { + 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; jperm_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; jperm_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; iperm_cnt; ++i) { + if (pj_sockaddr_get_len(&alloc->perm[i]) == (unsigned)addr_len && + pj_memcmp(pj_sockaddr_get_addr(&alloc->perm[i]), + pj_sockaddr_get_addr(src_addr), + addr_len) == 0) + { + break; + } + } + if (i==alloc->perm_cnt) { + PJ_LOG(5,("", "Client %s received %d bytes unauthorized data from peer %s", + client_info, size, peer_info)); + if (alloc->perm_cnt == 0) + PJ_LOG(5,("", "Client %s has no permission", client_info)); + return PJ_TRUE; + } + + /* Format a Data indication */ + pa = (pj_stun_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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJNATH_TEST_SERVER_H__ +#define __PJNATH_TEST_SERVER_H__ + +#include +#include +#include + +#define DNS_SERVER_PORT 55533 +#define STUN_SERVER_PORT 33478 +#define TURN_SERVER_PORT 33479 + +#define TURN_USERNAME "auser" +#define TURN_PASSWD "apass" + +#define MAX_TURN_ALLOC 16 +#define MAX_TURN_PERM 16 + +enum test_server_flags +{ + CREATE_DNS_SERVER = (1 << 0), + CREATE_A_RECORD_FOR_DOMAIN = (1 << 1), + + CREATE_STUN_SERVER = (1 << 5), + CREATE_STUN_SERVER_DNS_SRV = (1 << 6), + + CREATE_TURN_SERVER = (1 << 10), + CREATE_TURN_SERVER_DNS_SRV = (1 << 11), + +}; + +typedef struct test_server test_server; + +/* TURN allocation */ +typedef struct turn_allocation +{ + test_server *test_srv; + pj_pool_t *pool; + pj_activesock_t *sock; + pj_ioqueue_op_key_t send_key; + pj_sockaddr client_addr; + pj_sockaddr alloc_addr; + unsigned perm_cnt; + pj_sockaddr perm[MAX_TURN_PERM]; + 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 + * + * 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 + * + * 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; ititle)); + + 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; ipdu, 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; iattr_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 + * + * 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 + * + * 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 + +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, ¤t_state); + + if (current_state.timer_cnt > initial_st->timer_cnt) { + PJ_LOG(3,("", " error: possibly leaking timer")); + rc |= ERR_TIMER_LEAK; + } + + if (current_state.pool_used_cnt > initial_st->pool_used_cnt) { + PJ_LOG(3,("", " error: possibly leaking memory")); + PJ_LOG(3,("", " dumping memory pool:")); + pj_pool_factory_dump(mem, PJ_TRUE); + rc |= ERR_MEMORY_LEAK; + } + + return rc; +} + + +#define DO_TEST(test) do { \ + PJ_LOG(3, ("test", "Running %s...", #test)); \ + rc = test; \ + 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + +#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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include "server.h" + +#define SRV_DOMAIN "pjsip.lab.domain" +#define KA_INTERVAL 50 + +struct test_result +{ + unsigned state_called; + unsigned rx_data_cnt; +}; + +struct test_session +{ + pj_pool_t *pool; + pj_stun_config *stun_cfg; + pj_turn_sock *turn_sock; + pj_dns_resolver *resolver; + test_server *test_srv; + + pj_bool_t destroy_called; + int destroy_on_state; + struct test_result result; +}; + +struct test_session_cfg +{ + struct { + pj_bool_t enable_dns_srv; + int destroy_on_state; + } client; + + struct { + pj_uint32_t flags; + pj_bool_t respond_allocate; + pj_bool_t respond_refresh; + } srv; +}; + +static void turn_on_rx_data(pj_turn_sock *turn_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *peer_addr, + unsigned addr_len); +static void turn_on_state(pj_turn_sock *turn_sock, + pj_turn_state_t old_state, + pj_turn_state_t new_state); + +static void destroy_session(struct test_session *sess) +{ + if (sess->resolver) { + pj_dns_resolver_destroy(sess->resolver, PJ_TRUE); + sess->resolver = NULL; + } + + if (sess->turn_sock) { + if (!sess->destroy_called) { + sess->destroy_called = PJ_TRUE; + pj_turn_sock_destroy(sess->turn_sock); + } + sess->turn_sock = NULL; + } + + if (sess->test_srv) { + destroy_test_server(sess->test_srv); + sess->test_srv = NULL; + } + + if (sess->pool) { + pj_pool_release(sess->pool); + } +} + + + +static int create_test_session(pj_stun_config *stun_cfg, + const struct test_session_cfg *cfg, + struct test_session **p_sess) +{ + struct test_session *sess; + pj_pool_t *pool; + pj_turn_sock_cb turn_sock_cb; + pj_turn_alloc_param alloc_param; + pj_stun_auth_cred cred; + pj_status_t status; + + /* Create client */ + pool = pj_pool_create(mem, "turnclient", 512, 512, NULL); + sess = PJ_POOL_ZALLOC_T(pool, struct test_session); + sess->pool = pool; + sess->stun_cfg = stun_cfg; + sess->destroy_on_state = cfg->client.destroy_on_state; + + pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb)); + turn_sock_cb.on_rx_data = &turn_on_rx_data; + turn_sock_cb.on_state = &turn_on_state; + status = pj_turn_sock_create(sess->stun_cfg, pj_AF_INET(), PJ_TURN_TP_UDP, + &turn_sock_cb, 0, sess, &sess->turn_sock); + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -20; + } + + /* Create test server */ + status = create_test_server(sess->stun_cfg, cfg->srv.flags, + SRV_DOMAIN, &sess->test_srv); + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -30; + } + + sess->test_srv->turn_respond_allocate = cfg->srv.respond_allocate; + sess->test_srv->turn_respond_refresh = cfg->srv.respond_refresh; + + /* Create client resolver */ + status = pj_dns_resolver_create(mem, "resolver", 0, sess->stun_cfg->timer_heap, + sess->stun_cfg->ioqueue, &sess->resolver); + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -40; + + } else { + pj_str_t dns_srv = pj_str("127.0.0.1"); + pj_uint16_t dns_srv_port = (pj_uint16_t) DNS_SERVER_PORT; + status = pj_dns_resolver_set_ns(sess->resolver, 1, &dns_srv, &dns_srv_port); + + if (status != PJ_SUCCESS) { + destroy_session(sess); + return -50; + } + } + + /* Init TURN credential */ + pj_bzero(&cred, sizeof(cred)); + cred.type = PJ_STUN_AUTH_CRED_STATIC; + cred.data.static_cred.realm = pj_str(SRV_DOMAIN); + cred.data.static_cred.username = pj_str(TURN_USERNAME); + cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; + cred.data.static_cred.data = pj_str(TURN_PASSWD); + + /* Init TURN allocate parameter */ + pj_turn_alloc_param_default(&alloc_param); + alloc_param.ka_interval = KA_INTERVAL; + + /* Start the client */ + if (cfg->client.enable_dns_srv) { + /* Use DNS SRV to resolve server, may fallback to DNS A */ + pj_str_t domain = pj_str(SRV_DOMAIN); + status = pj_turn_sock_alloc(sess->turn_sock, &domain, TURN_SERVER_PORT, + sess->resolver, &cred, &alloc_param); + + } else { + /* Explicitly specify server address */ + pj_str_t host = pj_str("127.0.0.1"); + status = pj_turn_sock_alloc(sess->turn_sock, &host, TURN_SERVER_PORT, + NULL, &cred, &alloc_param); + + } + + if (status != PJ_SUCCESS) { + if (cfg->client.destroy_on_state >= PJ_TURN_STATE_READY) { + destroy_session(sess); + return -70; + } + } + + *p_sess = sess; + return 0; +} + + +static void turn_on_rx_data(pj_turn_sock *turn_sock, + void *pkt, + unsigned pkt_len, + const pj_sockaddr_t *peer_addr, + unsigned addr_len) +{ + struct test_session *sess; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(peer_addr); + PJ_UNUSED_ARG(addr_len); + + sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock); + if (sess == NULL) + return; + + sess->result.rx_data_cnt++; +} + + +static void turn_on_state(pj_turn_sock *turn_sock, + pj_turn_state_t old_state, + pj_turn_state_t new_state) +{ + struct test_session *sess; + unsigned i, mask; + + PJ_UNUSED_ARG(old_state); + + sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock); + if (sess == NULL) + return; + + /* This state must not be called before */ + pj_assert((sess->result.state_called & (1< old_state); + + /* must not call any greater state before */ + mask = 0; + for (i=new_state+1; i<31; ++i) mask |= (1 << i); + + pj_assert((sess->result.state_called & mask) == 0); + + sess->result.state_called |= (1 << new_state); + + if (new_state >= sess->destroy_on_state && !sess->destroy_called) { + sess->destroy_called = PJ_TRUE; + pj_turn_sock_destroy(turn_sock); + } + + if (new_state >= PJ_TURN_STATE_DESTROYING) { + pj_turn_sock_set_user_data(sess->turn_sock, NULL); + sess->turn_sock = NULL; + } +} + + +///////////////////////////////////////////////////////////////////// + +static int state_progression_test(pj_stun_config *stun_cfg) +{ + struct test_session_cfg test_cfg = + { + { /* Client cfg */ + /* DNS SRV */ /* Destroy on state */ + PJ_TRUE, 0xFFFF + }, + { /* Server cfg */ + 0xFFFFFFFF, /* flags */ + PJ_TRUE, /* respond to allocate */ + PJ_TRUE /* respond to refresh */ + } + }; + struct test_session *sess; + unsigned i; + int rc; + + PJ_LOG(3,("", " state progression tests")); + + for (i=0; i<=1; ++i) { + enum { TIMEOUT = 60 }; + pjlib_state pjlib_state; + pj_turn_session_info info; + struct test_result result; + pj_time_val tstart; + + PJ_LOG(3,("", " %s DNS SRV resolution", + (i==0? "without" : "with"))); + + capture_pjlib_state(stun_cfg, &pjlib_state); + + test_cfg.client.enable_dns_srv = i; + + rc = create_test_session(stun_cfg, &test_cfg, &sess); + if (rc != 0) + return rc; + + pj_bzero(&info, sizeof(info)); + + /* Wait until state is READY */ + pj_gettimeofday(&tstart); + while (sess->turn_sock) { + pj_time_val now; + + poll_events(stun_cfg, 10, PJ_FALSE); + rc = pj_turn_sock_get_info(sess->turn_sock, &info); + if (rc!=PJ_SUCCESS) + break; + + if (info.state >= PJ_TURN_STATE_READY) + break; + + pj_gettimeofday(&now); + if (now.sec - tstart.sec > TIMEOUT) { + PJ_LOG(3,("", " timed-out")); + break; + } + } + + if (info.state != PJ_TURN_STATE_READY) { + PJ_LOG(3,("", " error: state is not READY")); + destroy_session(sess); + return -130; + } + + /* Deallocate */ + pj_turn_sock_destroy(sess->turn_sock); + + /* Wait for couple of seconds. + * We can't poll the session info since the session may have + * been destroyed + */ + poll_events(stun_cfg, 2000, PJ_FALSE); + sess->turn_sock = NULL; + pj_memcpy(&result, &sess->result, sizeof(result)); + destroy_session(sess); + + /* Check the result */ + if ((result.state_called & (1<turn_sock) { + pj_time_val now; + + poll_events(stun_cfg, 100, PJ_FALSE); + + pj_gettimeofday(&now); + if (now.sec - tstart.sec > TIMEOUT) { + rc = -7; + break; + } + } + + } else { + pj_gettimeofday(&tstart); + rc = 0; + while (sess->turn_sock) { + pj_time_val now; + + poll_events(stun_cfg, 1, PJ_FALSE); + + pj_turn_sock_get_info(sess->turn_sock, &info); + + if (info.state >= target_state) { + pj_turn_sock_destroy(sess->turn_sock); + break; + } + + pj_gettimeofday(&now); + if (now.sec - tstart.sec > TIMEOUT) { + rc = -8; + break; + } + } + } + + + if (rc != 0) { + PJ_LOG(3,("", " error: timeout")); + return rc; + } + + poll_events(stun_cfg, 1000, PJ_FALSE); + destroy_session(sess); + + rc = check_pjlib_state(stun_cfg, &pjlib_state); + if (rc != 0) { + PJ_LOG(3,("", " error: memory/timer-heap leak detected")); + return rc; + } + } + + return 0; +} + + +///////////////////////////////////////////////////////////////////// + +int turn_sock_test(void) +{ + pj_pool_t *pool; + pj_stun_config stun_cfg; + int i, rc = 0; + + pool = pj_pool_create(mem, "turntest", 512, 512, NULL); + rc = create_stun_config(pool, &stun_cfg); + if (rc != PJ_SUCCESS) { + pj_pool_release(pool); + return -2; + } + + rc = state_progression_test(&stun_cfg); + if (rc != 0) + goto on_return; + + for (i=0; i<=1; ++i) { + int j; + for (j=0; j<=1; ++j) { + rc = destroy_test(&stun_cfg, i, j); + if (rc != 0) + goto on_return; + } + } + +on_return: + destroy_stun_config(&stun_cfg); + pj_pool_release(pool); + return rc; +} + -- cgit v1.2.3