initial commit
This commit is contained in:
commit
636959b37b
2474 files changed
+1343178
No files matched your search
@@ -0,0 +1,374 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "test.h"
|
||||
|
||||
#if INCLUDE_CONCUR_TEST
|
||||
|
||||
#define THIS_FILE "concur_test.c"
|
||||
|
||||
/****************************************************************************/
|
||||
#define WORKER_THREAD_CNT 4
|
||||
#define SERVER_THREAD_CNT 4
|
||||
#define MAX_SOCK_CLIENTS (PJ_IOQUEUE_MAX_HANDLES/2)
|
||||
|
||||
struct stun_test_session
|
||||
{
|
||||
pj_stun_config stun_cfg;
|
||||
|
||||
pj_lock_t *lock;
|
||||
|
||||
pj_thread_t *worker_threads[WORKER_THREAD_CNT];
|
||||
|
||||
pj_sock_t server_sock;
|
||||
int server_port;
|
||||
pj_thread_t *server_threads[SERVER_THREAD_CNT];
|
||||
pj_event_t *server_event;
|
||||
|
||||
pj_bool_t thread_quit_flag;
|
||||
|
||||
/* Test parameters: */
|
||||
struct {
|
||||
int client_got_response;
|
||||
|
||||
pj_bool_t server_wait_for_event;
|
||||
pj_bool_t server_drop_request;
|
||||
int client_sleep_after_start;
|
||||
int client_sleep_before_destroy;
|
||||
} param;
|
||||
};
|
||||
|
||||
static int server_thread_proc(void *p)
|
||||
{
|
||||
struct stun_test_session *test_sess = (struct stun_test_session*)p;
|
||||
pj_pool_t *pool;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_LOG(4,(THIS_FILE, "Server thread running"));
|
||||
|
||||
pool = pj_pool_create(test_sess->stun_cfg.pf, "server", 512, 512, NULL);
|
||||
|
||||
while (!test_sess->thread_quit_flag) {
|
||||
pj_time_val timeout = {0, 10};
|
||||
pj_fd_set_t rdset;
|
||||
int n;
|
||||
|
||||
/* Serve client */
|
||||
PJ_FD_ZERO(&rdset);
|
||||
PJ_FD_SET(test_sess->server_sock, &rdset);
|
||||
n = pj_sock_select((int)test_sess->server_sock+1, &rdset,
|
||||
NULL, NULL, &timeout);
|
||||
if (n==1 && PJ_FD_ISSET(test_sess->server_sock, &rdset)) {
|
||||
pj_uint8_t pkt[512];
|
||||
pj_ssize_t pkt_len;
|
||||
pj_size_t res_len;
|
||||
pj_sockaddr client_addr;
|
||||
int addr_len;
|
||||
|
||||
pj_stun_msg *stun_req, *stun_res;
|
||||
|
||||
pj_pool_reset(pool);
|
||||
|
||||
/* Got query */
|
||||
pkt_len = sizeof(pkt);
|
||||
addr_len = sizeof(client_addr);
|
||||
status = pj_sock_recvfrom(test_sess->server_sock, pkt, &pkt_len,
|
||||
0, &client_addr, &addr_len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
status = pj_stun_msg_decode(pool, pkt, pkt_len,
|
||||
PJ_STUN_IS_DATAGRAM,
|
||||
&stun_req, NULL, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(1,(THIS_FILE, status, "STUN request decode error"));
|
||||
continue;
|
||||
}
|
||||
|
||||
status = pj_stun_msg_create_response(pool, stun_req,
|
||||
PJ_STUN_SC_BAD_REQUEST, NULL,
|
||||
&stun_res);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(1,(THIS_FILE, status, "STUN create response error"));
|
||||
continue;
|
||||
}
|
||||
|
||||
status = pj_stun_msg_encode(stun_res, pkt, sizeof(pkt), 0,
|
||||
NULL, &res_len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(1,(THIS_FILE, status, "STUN encode error"));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ignore request */
|
||||
if (test_sess->param.server_drop_request)
|
||||
continue;
|
||||
|
||||
/* Wait for signal to continue */
|
||||
if (test_sess->param.server_wait_for_event)
|
||||
pj_event_wait(test_sess->server_event);
|
||||
|
||||
pkt_len = res_len;
|
||||
pj_sock_sendto(test_sess->server_sock, pkt, &pkt_len, 0,
|
||||
&client_addr, pj_sockaddr_get_len(&client_addr));
|
||||
}
|
||||
}
|
||||
|
||||
pj_pool_release(pool);
|
||||
|
||||
PJ_LOG(4,(THIS_FILE, "Server thread quitting"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int worker_thread_proc(void *p)
|
||||
{
|
||||
struct stun_test_session *test_sess = (struct stun_test_session*)p;
|
||||
|
||||
PJ_LOG(4,(THIS_FILE, "Worker thread running"));
|
||||
|
||||
while (!test_sess->thread_quit_flag) {
|
||||
pj_time_val timeout = {0, 10};
|
||||
pj_timer_heap_poll(test_sess->stun_cfg.timer_heap, NULL);
|
||||
pj_ioqueue_poll(test_sess->stun_cfg.ioqueue, &timeout);
|
||||
}
|
||||
|
||||
PJ_LOG(4,(THIS_FILE, "Worker thread quitting"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pj_bool_t stun_sock_on_status(pj_stun_sock *stun_sock,
|
||||
pj_stun_sock_op op,
|
||||
pj_status_t status)
|
||||
{
|
||||
struct stun_test_session *test_sess = (struct stun_test_session*)pj_stun_sock_get_user_data(stun_sock);
|
||||
|
||||
PJ_UNUSED_ARG(op);
|
||||
PJ_UNUSED_ARG(status);
|
||||
|
||||
test_sess->param.client_got_response++;
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
static int stun_destroy_test_session(struct stun_test_session *test_sess)
|
||||
{
|
||||
|
||||
unsigned i;
|
||||
pj_stun_sock_cb stun_cb;
|
||||
pj_status_t status;
|
||||
pj_stun_sock *stun_sock[MAX_SOCK_CLIENTS];
|
||||
|
||||
pj_bzero(&stun_cb, sizeof(stun_cb));
|
||||
stun_cb.on_status = &stun_sock_on_status;
|
||||
|
||||
pj_event_reset(test_sess->server_event);
|
||||
|
||||
/* Create all clients first */
|
||||
for (i=0; i<MAX_SOCK_CLIENTS; ++i) {
|
||||
char name[10];
|
||||
sprintf(name, "stun%02d", i);
|
||||
status = pj_stun_sock_create(&test_sess->stun_cfg, name, pj_AF_INET(),
|
||||
&stun_cb, NULL, test_sess,
|
||||
&stun_sock[i]);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(1,(THIS_FILE, status, "Error creating stun socket"));
|
||||
return -10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start resolution */
|
||||
for (i=0; i<MAX_SOCK_CLIENTS; ++i) {
|
||||
pj_str_t server_ip = pj_str("127.0.0.1");
|
||||
status = pj_stun_sock_start(stun_sock[i], &server_ip,
|
||||
(pj_uint16_t)test_sess->server_port, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(1,(THIS_FILE, status, "Error starting stun socket"));
|
||||
return -20;
|
||||
}
|
||||
}
|
||||
|
||||
/* settle down */
|
||||
pj_thread_sleep(test_sess->param.client_sleep_after_start);
|
||||
|
||||
/* Resume server threads */
|
||||
pj_event_set(test_sess->server_event);
|
||||
|
||||
pj_thread_sleep(test_sess->param.client_sleep_before_destroy);
|
||||
|
||||
/* Destroy clients */
|
||||
for (i=0; i<MAX_SOCK_CLIENTS; ++i) {
|
||||
status = pj_stun_sock_destroy(stun_sock[i]);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(1,(THIS_FILE, status, "Error destroying stun socket"));
|
||||
}
|
||||
}
|
||||
|
||||
/* Give some time to ioqueue to free sockets */
|
||||
pj_thread_sleep(PJ_IOQUEUE_KEY_FREE_DELAY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stun_destroy_test(void)
|
||||
{
|
||||
enum { LOOP = 500 };
|
||||
struct stun_test_session test_sess;
|
||||
pj_sockaddr bind_addr;
|
||||
int addr_len;
|
||||
pj_caching_pool cp;
|
||||
pj_pool_t *pool;
|
||||
unsigned i;
|
||||
pj_status_t status;
|
||||
int rc = 0;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " STUN destroy concurrency test"));
|
||||
|
||||
pj_bzero(&test_sess, sizeof(test_sess));
|
||||
|
||||
pj_caching_pool_init(&cp, NULL, 0);
|
||||
pool = pj_pool_create(&cp.factory, "testsess", 512, 512, NULL);
|
||||
|
||||
pj_stun_config_init(&test_sess.stun_cfg, &cp.factory, 0, NULL, NULL);
|
||||
|
||||
status = pj_timer_heap_create(pool, 1023, &test_sess.stun_cfg.timer_heap);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
status = pj_lock_create_recursive_mutex(pool, NULL, &test_sess.lock);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
pj_timer_heap_set_lock(test_sess.stun_cfg.timer_heap, test_sess.lock, PJ_TRUE);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
status = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &test_sess.stun_cfg.ioqueue);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &test_sess.server_sock);
|
||||
pj_sockaddr_init(pj_AF_INET(), &bind_addr, NULL, 0);
|
||||
status = pj_sock_bind(test_sess.server_sock, &bind_addr, pj_sockaddr_get_len(&bind_addr));
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
/* Set socket to nonblocking to avoid stuck in recv/recvfrom() on concurrent events */
|
||||
app_set_sock_nb(test_sess.server_sock);
|
||||
|
||||
addr_len = sizeof(bind_addr);
|
||||
status = pj_sock_getsockname(test_sess.server_sock, &bind_addr, &addr_len);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
test_sess.server_port = pj_sockaddr_get_port(&bind_addr);
|
||||
|
||||
status = pj_event_create(pool, NULL, PJ_TRUE, PJ_FALSE, &test_sess.server_event);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
for (i=0; i<SERVER_THREAD_CNT; ++i) {
|
||||
status = pj_thread_create(pool, NULL,
|
||||
&server_thread_proc, &test_sess,
|
||||
0, 0, &test_sess.server_threads[i]);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
}
|
||||
|
||||
for (i=0; i<WORKER_THREAD_CNT; ++i) {
|
||||
status = pj_thread_create(pool, NULL,
|
||||
&worker_thread_proc, &test_sess,
|
||||
0, 0, &test_sess.worker_threads[i]);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
}
|
||||
PJ_UNUSED_ARG(status);
|
||||
|
||||
/* Test 1: Main thread calls destroy while callback is processing response */
|
||||
PJ_LOG(3,(THIS_FILE, " Destroy in main thread while callback is running"));
|
||||
for (i=0; i<LOOP; ++i) {
|
||||
int sleep = pj_rand() % 5;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " Try %-3d of %d", i+1, LOOP));
|
||||
|
||||
/* Test 1: destroy at the same time when receiving response */
|
||||
pj_bzero(&test_sess.param, sizeof(test_sess.param));
|
||||
test_sess.param.client_sleep_after_start = 20;
|
||||
test_sess.param.client_sleep_before_destroy = sleep;
|
||||
test_sess.param.server_wait_for_event = PJ_TRUE;
|
||||
stun_destroy_test_session(&test_sess);
|
||||
PJ_LOG(3,(THIS_FILE,
|
||||
" stun test a: sleep delay:%d: clients with response: %d",
|
||||
sleep, test_sess.param.client_got_response));
|
||||
|
||||
/* Test 2: destroy at the same time with STUN retransmit timer */
|
||||
test_sess.param.server_drop_request = PJ_TRUE;
|
||||
test_sess.param.client_sleep_after_start = 0;
|
||||
test_sess.param.client_sleep_before_destroy = PJ_STUN_RTO_VALUE;
|
||||
test_sess.param.server_wait_for_event = PJ_FALSE;
|
||||
stun_destroy_test_session(&test_sess);
|
||||
PJ_LOG(3,(THIS_FILE, " stun test b: retransmit concurrency"));
|
||||
|
||||
/* Test 3: destroy at the same time with receiving response
|
||||
* AND STUN retransmit timer */
|
||||
test_sess.param.client_got_response = 0;
|
||||
test_sess.param.server_drop_request = PJ_FALSE;
|
||||
test_sess.param.client_sleep_after_start = PJ_STUN_RTO_VALUE;
|
||||
test_sess.param.client_sleep_before_destroy = 0;
|
||||
test_sess.param.server_wait_for_event = PJ_TRUE;
|
||||
stun_destroy_test_session(&test_sess);
|
||||
PJ_LOG(3,(THIS_FILE,
|
||||
" stun test c: clients with response: %d",
|
||||
test_sess.param.client_got_response));
|
||||
|
||||
pj_thread_sleep(10);
|
||||
|
||||
ice_one_conc_test(&test_sess.stun_cfg, PJ_FALSE);
|
||||
|
||||
pj_thread_sleep(10);
|
||||
}
|
||||
|
||||
/* Avoid compiler warning */
|
||||
goto on_return;
|
||||
|
||||
|
||||
on_return:
|
||||
test_sess.thread_quit_flag = PJ_TRUE;
|
||||
|
||||
for (i=0; i<SERVER_THREAD_CNT; ++i) {
|
||||
pj_thread_join(test_sess.server_threads[i]);
|
||||
}
|
||||
|
||||
for (i=0; i<WORKER_THREAD_CNT; ++i) {
|
||||
pj_thread_join(test_sess.worker_threads[i]);
|
||||
}
|
||||
|
||||
pj_event_destroy(test_sess.server_event);
|
||||
pj_sock_close(test_sess.server_sock);
|
||||
pj_ioqueue_destroy(test_sess.stun_cfg.ioqueue);
|
||||
pj_timer_heap_destroy(test_sess.stun_cfg.timer_heap);
|
||||
|
||||
pj_pool_release(pool);
|
||||
pj_caching_pool_destroy(&cp);
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " Done. rc=%d", rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int concur_test(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
rc += stun_destroy_test();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* INCLUDE_CONCUR_TEST */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,87 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "test.h"
|
||||
|
||||
#if defined(PJ_SUNOS) && PJ_SUNOS!=0
|
||||
|
||||
#include <signal.h>
|
||||
static void init_signals()
|
||||
{
|
||||
struct sigaction act;
|
||||
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.sa_handler = SIG_IGN;
|
||||
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
||||
#elif PJ_LINUX || PJ_DARWINOS
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
static void print_stack(int sig)
|
||||
{
|
||||
void *array[16];
|
||||
size_t size;
|
||||
|
||||
size = backtrace(array, 16);
|
||||
fprintf(stderr, "Error: signal %d:\n", sig);
|
||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void init_signals()
|
||||
{
|
||||
signal(SIGSEGV, &print_stack);
|
||||
signal(SIGABRT, &print_stack);
|
||||
}
|
||||
|
||||
#else
|
||||
#define init_signals()
|
||||
#endif
|
||||
|
||||
#define boost()
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int rc;
|
||||
|
||||
PJ_UNUSED_ARG(argc);
|
||||
PJ_UNUSED_ARG(argv);
|
||||
|
||||
boost();
|
||||
init_signals();
|
||||
|
||||
rc = test_main();
|
||||
|
||||
if (argc == 2 && pj_ansi_strcmp(argv[1], "-i")==0) {
|
||||
char buf[10];
|
||||
|
||||
puts("Press <ENTER> to exit");
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "../../pjlib/src/pjlib-test/main_win32.c"
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,124 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __PJNATH_TEST_SERVER_H__
|
||||
#define __PJNATH_TEST_SERVER_H__
|
||||
|
||||
#include <pjnath.h>
|
||||
#include <pjlib-util.h>
|
||||
#include <pjlib.h>
|
||||
|
||||
#define DNS_SERVER_PORT 55533
|
||||
#define STUN_SERVER_PORT 33478
|
||||
#define TURN_SERVER_PORT 33479
|
||||
|
||||
#define TURN_USERNAME "auser"
|
||||
#define TURN_PASSWD "apass"
|
||||
|
||||
#define MAX_TURN_ALLOC 16
|
||||
#define MAX_TURN_PERM 16
|
||||
|
||||
enum test_server_flags
|
||||
{
|
||||
CREATE_DNS_SERVER = (1 << 0),
|
||||
CREATE_A_RECORD_FOR_DOMAIN = (1 << 1),
|
||||
|
||||
CREATE_STUN_SERVER = (1 << 4),
|
||||
CREATE_STUN_SERVER_DNS_SRV = (1 << 5),
|
||||
|
||||
CREATE_TURN_SERVER = (1 << 8),
|
||||
CREATE_TURN_SERVER_DNS_SRV = (1 << 9),
|
||||
|
||||
SERVER_IPV4 = (1 << 12),
|
||||
SERVER_IPV6 = (1 << 13),
|
||||
|
||||
TURN_UDP = (1 << 16),
|
||||
TURN_TCP = (1 << 17),
|
||||
TURN_TLS = (1 << 18)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
pj_ssl_sock_t *ssl_srv_sock;
|
||||
|
||||
pj_ssl_sock_t *ssl_cl_sock;
|
||||
|
||||
pj_activesock_t *cl_turn_sock;
|
||||
|
||||
pj_sockaddr remote_addr;
|
||||
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__ */
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,986 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "test.h"
|
||||
|
||||
#define THIS_FILE "stun.c"
|
||||
|
||||
static pj_stun_msg* create1(pj_pool_t*);
|
||||
static int verify1(pj_stun_msg*);
|
||||
static int verify2(pj_stun_msg*);
|
||||
static int verify5(pj_stun_msg*);
|
||||
|
||||
static struct test
|
||||
{
|
||||
const char *title;
|
||||
char *pdu;
|
||||
unsigned pdu_len;
|
||||
pj_stun_msg* (*create)(pj_pool_t*);
|
||||
pj_status_t expected_status;
|
||||
int (*verify)(pj_stun_msg*);
|
||||
} tests[] =
|
||||
{
|
||||
{
|
||||
"Invalid message type",
|
||||
"\x11\x01\x00\x00\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
20,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGTYPE,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (1) (partial header)",
|
||||
"\x00\x01",
|
||||
2,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (2) (partial header)",
|
||||
"\x00\x01\x00\x00\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
16,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (3), (missing attribute)",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
20,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (4), (partial attribute header)",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28",
|
||||
22,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (5), (partial attribute header)",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00",
|
||||
23,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (6), (partial attribute header)",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04",
|
||||
24,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Short message (7), (partial attribute body)",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04\x00\x00\x00",
|
||||
27,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Message length in header is too long",
|
||||
"\x00\x01\xff\xff\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04\x00\x00\x00",
|
||||
27,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Message length in header is shorter",
|
||||
"\x00\x01\x00\x04\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04\x00\x00\x00\x00",
|
||||
28,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Invalid magic",
|
||||
"\x00\x01\x00\x08\x00\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04\x00\x00\x00\x00",
|
||||
28,
|
||||
NULL,
|
||||
PJ_SUCCESS,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Character beyond message",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04\x00\x00\x00\x00\x0a",
|
||||
29,
|
||||
NULL,
|
||||
PJNATH_EINSTUNMSGLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Respond unknown mandatory attribute with 420 and "
|
||||
"UNKNOWN-ATTRIBUTES attribute",
|
||||
NULL,
|
||||
0,
|
||||
&create1,
|
||||
0,
|
||||
&verify1
|
||||
},
|
||||
{
|
||||
"Unknown but non-mandatory should be okay",
|
||||
"\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\xff\x00\x03\x00\x00\x00\x00",
|
||||
28,
|
||||
NULL,
|
||||
PJ_SUCCESS,
|
||||
&verify2
|
||||
},
|
||||
{
|
||||
"String attr length larger than message",
|
||||
"\x00\x01\x00\x08\x00\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x06\x00\xff\x00\x00\x00\x00",
|
||||
28,
|
||||
NULL,
|
||||
PJNATH_ESTUNINATTRLEN,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Attribute other than FINGERPRINT after MESSAGE-INTEGRITY is allowed",
|
||||
"\x00\x01\x00\x20\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x08\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // M-I
|
||||
"\x80\x24\x00\x04\x00\x00\x00\x00", // REFRESH-INTERVAL
|
||||
52,
|
||||
NULL,
|
||||
PJ_SUCCESS,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Attribute between MESSAGE-INTEGRITY and FINGERPRINT is allowed",
|
||||
"\x00\x01\x00\x28\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x08\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // M-I
|
||||
"\x80\x24\x00\x04\x00\x00\x00\x00" // REFRESH-INTERVAL
|
||||
"\x80\x28\x00\x04\xc7\xde\xdd\x65", // FINGERPRINT
|
||||
60,
|
||||
NULL,
|
||||
PJ_SUCCESS,
|
||||
&verify5
|
||||
},
|
||||
{
|
||||
"Attribute past FINGERPRINT is not allowed",
|
||||
"\x00\x01\x00\x10\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x80\x28\x00\x04\x00\x00\x00\x00"
|
||||
"\x80\x24\x00\x04\x00\x00\x00\x00",
|
||||
36,
|
||||
NULL,
|
||||
PJNATH_ESTUNFINGERPOS,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
static const char *err(pj_status_t status)
|
||||
{
|
||||
static char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
static const pj_str_t USERNAME = {"user", 4};
|
||||
static const pj_str_t PASSWORD = {"password", 8};
|
||||
|
||||
static int decode_test(void)
|
||||
{
|
||||
unsigned i;
|
||||
pj_pool_t *pool;
|
||||
int rc = 0;
|
||||
|
||||
pool = pj_pool_create(mem, "decode_test", 1024, 1024, NULL);
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " STUN decode test"));
|
||||
|
||||
for (i=0; i<PJ_ARRAY_SIZE(tests); ++i) {
|
||||
struct test *t = &tests[i];
|
||||
pj_stun_msg *msg, *msg2;
|
||||
pj_uint8_t buf[1500];
|
||||
pj_str_t key;
|
||||
pj_size_t len;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " %s", t->title));
|
||||
|
||||
if (t->pdu) {
|
||||
status = pj_stun_msg_decode(pool, (pj_uint8_t*)t->pdu, t->pdu_len,
|
||||
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
|
||||
&msg, NULL, NULL);
|
||||
|
||||
/* Check expected decode result */
|
||||
if (t->expected_status != status) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting status %d, got %d",
|
||||
t->expected_status, status));
|
||||
rc = -10;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
} else {
|
||||
msg = t->create(pool);
|
||||
status = PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if (status != PJ_SUCCESS)
|
||||
continue;
|
||||
|
||||
/* Try to encode message */
|
||||
pj_stun_create_key(pool, &key, NULL, &USERNAME, PJ_STUN_PASSWD_PLAIN, &PASSWORD);
|
||||
status = pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_LOG(1,(THIS_FILE, " encode error: %s", err(status)));
|
||||
rc = -40;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Try to decode it once more */
|
||||
status = pj_stun_msg_decode(pool, buf, len,
|
||||
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
|
||||
&msg2, NULL, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_LOG(1,(THIS_FILE, " subsequent decoding failed: %s", err(status)));
|
||||
rc = -50;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Verify */
|
||||
if (t->verify) {
|
||||
rc = t->verify(msg);
|
||||
if (rc != 0) {
|
||||
goto on_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
on_return:
|
||||
pj_pool_release(pool);
|
||||
if (rc == 0)
|
||||
PJ_LOG(3,(THIS_FILE, "...success!"));
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Create 420 response */
|
||||
static pj_stun_msg* create1(pj_pool_t *pool)
|
||||
{
|
||||
char *pdu = "\x00\x01\x00\x08\x21\x12\xa4\x42"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\xff\x00\x04\x00\x00\x00\x00";
|
||||
unsigned pdu_len = 28;
|
||||
pj_stun_msg *msg, *res;
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_stun_msg_decode(pool, (pj_uint8_t*)pdu, pdu_len,
|
||||
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
|
||||
&msg, NULL, &res);
|
||||
pj_assert(status != PJ_SUCCESS);
|
||||
PJ_UNUSED_ARG(status);
|
||||
pj_assert(res != NULL);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Error response MUST have ERROR-CODE attribute */
|
||||
/* 420 response MUST contain UNKNOWN-ATTRIBUTES */
|
||||
static int verify1(pj_stun_msg *msg)
|
||||
{
|
||||
pj_stun_errcode_attr *aerr;
|
||||
pj_stun_unknown_attr *aunk;
|
||||
|
||||
if (!PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting error message"));
|
||||
return -100;
|
||||
}
|
||||
|
||||
aerr = (pj_stun_errcode_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
|
||||
if (aerr == NULL) {
|
||||
PJ_LOG(1,(THIS_FILE, " missing ERROR-CODE attribute"));
|
||||
return -110;
|
||||
}
|
||||
|
||||
if (aerr->err_code != 420) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting 420 error"));
|
||||
return -120;
|
||||
}
|
||||
|
||||
aunk = (pj_stun_unknown_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
|
||||
if (aunk == NULL) {
|
||||
PJ_LOG(1,(THIS_FILE, " missing UNKNOWN-ATTRIBUTE attribute"));
|
||||
return -130;
|
||||
}
|
||||
|
||||
if (aunk->attr_count != 1) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting one unknown attribute"));
|
||||
return -140;
|
||||
}
|
||||
|
||||
if (aunk->attrs[0] != 0xff) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting 0xff as unknown attribute"));
|
||||
return -150;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Attribute count should be zero since unknown attribute is not parsed */
|
||||
static int verify2(pj_stun_msg *msg)
|
||||
{
|
||||
pj_stun_binary_attr *bin_attr;
|
||||
|
||||
if (msg->attr_count != 1) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting one attribute count"));
|
||||
return -200;
|
||||
}
|
||||
|
||||
bin_attr = (pj_stun_binary_attr*)msg->attr[0];
|
||||
if (bin_attr->hdr.type != 0x80ff) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting attribute type 0x80ff"));
|
||||
return -210;
|
||||
}
|
||||
if (bin_attr->hdr.length != 3) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting attribute length = 4"));
|
||||
return -220;
|
||||
}
|
||||
if (bin_attr->magic != PJ_STUN_MAGIC) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting PJ_STUN_MAGIC for unknown attr"));
|
||||
return -230;
|
||||
}
|
||||
if (bin_attr->length != 3) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting data length 4"));
|
||||
return -240;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Attribute between MESSAGE-INTEGRITY and FINGERPRINT is allowed */
|
||||
static int verify5(pj_stun_msg *msg)
|
||||
{
|
||||
if (msg->attr_count != 3) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting 3 attribute count"));
|
||||
return -500;
|
||||
}
|
||||
|
||||
if (msg->attr[0]->type != PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting MESSAGE-INTEGRITY"));
|
||||
return -510;
|
||||
}
|
||||
if (msg->attr[1]->type != PJ_STUN_ATTR_REFRESH_INTERVAL) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting REFRESH-INTERVAL"));
|
||||
return -520;
|
||||
}
|
||||
if (msg->attr[2]->type != PJ_STUN_ATTR_FINGERPRINT) {
|
||||
PJ_LOG(1,(THIS_FILE, " expecting FINGERPRINT"));
|
||||
return -530;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int decode_verify(void)
|
||||
{
|
||||
/* Decode all attribute types */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test vectors, from:
|
||||
* http://tools.ietf.org/html/draft-denis-behave-rfc3489bis-test-vectors-02
|
||||
*/
|
||||
typedef struct test_vector test_vector;
|
||||
|
||||
static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v);
|
||||
static pj_stun_msg* create_msgint2(pj_pool_t *pool, test_vector *v);
|
||||
static pj_stun_msg* create_msgint3(pj_pool_t *pool, test_vector *v);
|
||||
|
||||
enum
|
||||
{
|
||||
USE_MESSAGE_INTEGRITY = 1,
|
||||
USE_FINGERPRINT = 2
|
||||
};
|
||||
|
||||
static struct test_vector
|
||||
{
|
||||
unsigned msg_type;
|
||||
char *tsx_id;
|
||||
char *pdu;
|
||||
unsigned pdu_len;
|
||||
unsigned options;
|
||||
char *username;
|
||||
char *password;
|
||||
char *realm;
|
||||
char *nonce;
|
||||
pj_stun_msg* (*create)(pj_pool_t*, test_vector*);
|
||||
} test_vectors[] =
|
||||
{
|
||||
{
|
||||
PJ_STUN_BINDING_REQUEST,
|
||||
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
|
||||
"\x00\x01\x00\x44\x21\x12\xa4\x42\xb7\xe7"
|
||||
"\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
|
||||
"\x00\x24\x00\x04\x6e\x00\x01\xff\x80\x29"
|
||||
"\x00\x08\x93\x2f\xf9\xb1\x51\x26\x3b\x36"
|
||||
"\x00\x06\x00\x09\x65\x76\x74\x6a\x3a\x68"
|
||||
"\x36\x76\x59\x20\x20\x20\x00\x08\x00\x14"
|
||||
"\x62\x4e\xeb\xdc\x3c\xc9\x2d\xd8\x4b\x74"
|
||||
"\xbf\x85\xd1\xc0\xf5\xde\x36\x87\xbd\x33"
|
||||
"\x80\x28\x00\x04\xad\x8a\x85\xff",
|
||||
88,
|
||||
USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
|
||||
"evtj:h6vY",
|
||||
"VOkJxbRl1RmTxUk/WvJxBt",
|
||||
"",
|
||||
"",
|
||||
&create_msgint1
|
||||
}
|
||||
/* disabled: see http://trac.pjsip.org/repos/ticket/960
|
||||
,
|
||||
{
|
||||
PJ_STUN_BINDING_RESPONSE,
|
||||
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
|
||||
"\x01\x01\x00\x3c"
|
||||
"\x21\x12\xa4\x42"
|
||||
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
|
||||
"\x80\x22\x00\x0b"
|
||||
"\x74\x65\x73\x74\x20\x76\x65\x63\x74\x6f\x72\x20"
|
||||
"\x00\x20\x00\x08"
|
||||
"\x00\x01\xa1\x47\xe1\x12\xa6\x43"
|
||||
"\x00\x08\x00\x14"
|
||||
"\x2b\x91\xf5\x99\xfd\x9e\x90\xc3\x8c\x74\x89\xf9"
|
||||
"\x2a\xf9\xba\x53\xf0\x6b\xe7\xd7"
|
||||
"\x80\x28\x00\x04"
|
||||
"\xc0\x7d\x4c\x96",
|
||||
80,
|
||||
USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
|
||||
"evtj:h6vY",
|
||||
"VOkJxbRl1RmTxUk/WvJxBt",
|
||||
"",
|
||||
"",
|
||||
&create_msgint2
|
||||
}
|
||||
*/
|
||||
|
||||
/* disabled: see http://trac.pjsip.org/repos/ticket/960
|
||||
#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6!=0
|
||||
,
|
||||
{
|
||||
PJ_STUN_BINDING_RESPONSE,
|
||||
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae",
|
||||
"\x01\x01\x00\x48" // Response type and message length
|
||||
"\x21\x12\xa4\x42" // Message cookie
|
||||
"\xb7\xe7\xa7\x01" // }
|
||||
"\xbc\x34\xd6\x86" // } Transaction ID
|
||||
"\xfa\x87\xdf\xae" // }
|
||||
|
||||
"\x80\x22\x00\x0b" // SOFTWARE, length=11
|
||||
"\x74\x65\x73\x74"
|
||||
"\x20\x76\x65\x63"
|
||||
"\x74\x6f\x72\x20"
|
||||
"\x00\x20\x00\x14" // XOR-MAPPED-ADDRESS
|
||||
"\x00\x02\xa1\x47"
|
||||
"\x01\x13\xa9\xfa"
|
||||
"\xa5\xd3\xf1\x79"
|
||||
"\xbc\x25\xf4\xb5"
|
||||
"\xbe\xd2\xb9\xd9"
|
||||
"\x00\x08\x00\x14" // MESSAGE-INTEGRITY attribute header
|
||||
"\xa3\x82\x95\x4e" // }
|
||||
"\x4b\xe6\x7b\xf1" // }
|
||||
"\x17\x84\xc9\x7c" // } HMAC-SHA1 fingerprint
|
||||
"\x82\x92\xc2\x75" // }
|
||||
"\xbf\xe3\xed\x41" // }
|
||||
"\x80\x28\x00\x04" // FINGERPRINT attribute header
|
||||
"\xc8\xfb\x0b\x4c" // CRC32 fingerprint
|
||||
,
|
||||
92,
|
||||
USE_MESSAGE_INTEGRITY | USE_FINGERPRINT,
|
||||
"evtj:h6vY",
|
||||
"VOkJxbRl1RmTxUk/WvJxBt",
|
||||
"",
|
||||
"",
|
||||
&create_msgint3
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
static char* print_binary(const pj_uint8_t *data, unsigned data_len)
|
||||
{
|
||||
static char buf[1500];
|
||||
unsigned length = sizeof(buf);
|
||||
char *p = buf;
|
||||
unsigned i;
|
||||
|
||||
for (i=0; i<data_len;) {
|
||||
unsigned j;
|
||||
|
||||
pj_ansi_snprintf(p, 1500-(p-buf),
|
||||
"%04d-%04d ",
|
||||
i, (i+20 < data_len) ? i+20 : data_len);
|
||||
p += 12;
|
||||
|
||||
for (j=0; j<20 && i<data_len && p<(buf+length-10); ++j, ++i) {
|
||||
pj_ansi_sprintf(p, "%02x ", (*data) & 0xFF);
|
||||
p += 3;
|
||||
data++;
|
||||
}
|
||||
|
||||
pj_ansi_sprintf(p, "\n");
|
||||
p++;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int cmp_buf(const pj_uint8_t *s1, const pj_uint8_t *s2, unsigned len)
|
||||
{
|
||||
unsigned i;
|
||||
for (i=0; i<len; ++i) {
|
||||
if (s1[i] != s2[i])
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int fingerprint_test_vector()
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_status_t status;
|
||||
unsigned i;
|
||||
int rc = 0;
|
||||
|
||||
/* To avoid function not referenced warnings */
|
||||
(void)create_msgint2;
|
||||
(void)create_msgint3;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " draft-denis-behave-rfc3489bis-test-vectors-02"));
|
||||
|
||||
pool = pj_pool_create(mem, "fingerprint", 1024, 1024, NULL);
|
||||
|
||||
for (i=0; i<PJ_ARRAY_SIZE(test_vectors); ++i) {
|
||||
struct test_vector *v;
|
||||
pj_stun_msg *ref_msg, *msg;
|
||||
pj_size_t parsed_len;
|
||||
pj_size_t len;
|
||||
unsigned pos;
|
||||
pj_uint8_t buf[1500];
|
||||
char print[1500];
|
||||
pj_str_t key;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " Running test %d/%d", i,
|
||||
PJ_ARRAY_SIZE(test_vectors)));
|
||||
|
||||
v = &test_vectors[i];
|
||||
|
||||
/* Print reference message */
|
||||
PJ_LOG(4,(THIS_FILE, "Reference message PDU:\n%s",
|
||||
print_binary((pj_uint8_t*)v->pdu, v->pdu_len)));
|
||||
|
||||
/* Try to parse the reference message first */
|
||||
status = pj_stun_msg_decode(pool, (pj_uint8_t*)v->pdu, v->pdu_len,
|
||||
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
|
||||
&ref_msg, &parsed_len, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_LOG(1,(THIS_FILE, " Error decoding reference message"));
|
||||
rc = -1010;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
if (parsed_len != v->pdu_len) {
|
||||
PJ_LOG(1,(THIS_FILE, " Parsed len error"));
|
||||
rc = -1020;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Print the reference message */
|
||||
pj_stun_msg_dump(ref_msg, print, sizeof(print), NULL);
|
||||
PJ_LOG(4,(THIS_FILE, "Reference message:\n%s", print));
|
||||
|
||||
/* Create our message */
|
||||
msg = v->create(pool, v);
|
||||
if (msg == NULL) {
|
||||
PJ_LOG(1,(THIS_FILE, " Error creating stun message"));
|
||||
rc = -1030;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Encode message */
|
||||
if (v->options & USE_MESSAGE_INTEGRITY) {
|
||||
pj_str_t s1, s2, r;
|
||||
|
||||
pj_stun_create_key(pool, &key, pj_cstr(&r, v->realm),
|
||||
pj_cstr(&s1, v->username),
|
||||
PJ_STUN_PASSWD_PLAIN,
|
||||
pj_cstr(&s2, v->password));
|
||||
pj_stun_msg_encode(msg, buf, sizeof(buf), 0, &key, &len);
|
||||
|
||||
} else {
|
||||
pj_stun_msg_encode(msg, buf, sizeof(buf), 0, NULL, &len);
|
||||
}
|
||||
|
||||
/* Print our raw message */
|
||||
PJ_LOG(4,(THIS_FILE, "Message PDU:\n%s",
|
||||
print_binary((pj_uint8_t*)buf, (unsigned)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, (unsigned)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 status2;
|
||||
|
||||
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);
|
||||
|
||||
status2 = pj_stun_authenticate_request(buf, (unsigned)len, msg,
|
||||
&cred, pool, NULL, NULL);
|
||||
if (status2 != PJ_SUCCESS) {
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_strerror(status2, 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 status2;
|
||||
status2 = pj_stun_authenticate_response(buf, (unsigned)len,
|
||||
msg, &key);
|
||||
if (status2 != PJ_SUCCESS) {
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_strerror(status2, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(1,(THIS_FILE,
|
||||
" Response authentication failed: %s",
|
||||
errmsg));
|
||||
rc = -1080;
|
||||
goto on_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
on_return:
|
||||
pj_pool_release(pool);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static pj_stun_msg* create_msgint1(pj_pool_t *pool, test_vector *v)
|
||||
{
|
||||
pj_stun_msg *msg;
|
||||
pj_timestamp u64;
|
||||
pj_str_t s1;
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
|
||||
(pj_uint8_t*)v->tsx_id, &msg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_PRIORITY,
|
||||
0x6e0001ff);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
u64.u32.hi = 0x932ff9b1;
|
||||
u64.u32.lo = 0x51263b36;
|
||||
status = pj_stun_msg_add_uint64_attr(pool, msg,
|
||||
PJ_STUN_ATTR_ICE_CONTROLLED, &u64);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_USERNAME,
|
||||
pj_cstr(&s1, v->username));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_msgint_attr(pool, msg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
return msg;
|
||||
|
||||
on_error:
|
||||
app_perror(" error: create_msgint1()", status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pj_stun_msg* create_msgint2(pj_pool_t *pool, test_vector *v)
|
||||
{
|
||||
pj_stun_msg *msg;
|
||||
pj_sockaddr_in mapped_addr;
|
||||
pj_str_t s1;
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
|
||||
(pj_uint8_t*)v->tsx_id, &msg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE,
|
||||
pj_cstr(&s1, "test vector"));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_sockaddr_in_init(&mapped_addr, pj_cstr(&s1, "192.0.2.1"),
|
||||
32853);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_sockaddr_attr(pool, msg,
|
||||
PJ_STUN_ATTR_XOR_MAPPED_ADDR,
|
||||
PJ_TRUE, &mapped_addr,
|
||||
sizeof(pj_sockaddr_in));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_msgint_attr(pool, msg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
return msg;
|
||||
|
||||
on_error:
|
||||
app_perror(" error: create_msgint2()", status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static pj_stun_msg* create_msgint3(pj_pool_t *pool, test_vector *v)
|
||||
{
|
||||
pj_stun_msg *msg;
|
||||
pj_sockaddr mapped_addr;
|
||||
pj_str_t s1;
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_stun_msg_create(pool, v->msg_type, PJ_STUN_MAGIC,
|
||||
(pj_uint8_t*)v->tsx_id, &msg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE,
|
||||
pj_cstr(&s1, "test vector"));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_sockaddr_init(pj_AF_INET6(), &mapped_addr,
|
||||
pj_cstr(&s1, "2001:db8:1234:5678:11:2233:4455:6677"),
|
||||
32853);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_sockaddr_attr(pool, msg,
|
||||
PJ_STUN_ATTR_XOR_MAPPED_ADDR,
|
||||
PJ_TRUE, &mapped_addr,
|
||||
sizeof(pj_sockaddr));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_msgint_attr(pool, msg);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_stun_msg_add_uint_attr(pool, msg, PJ_STUN_ATTR_FINGERPRINT, 0);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
return msg;
|
||||
|
||||
on_error:
|
||||
app_perror(" error: create_msgint3()", status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Compare two messages */
|
||||
static int cmp_msg(const pj_stun_msg *msg1, const pj_stun_msg *msg2)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
if (msg1->hdr.type != msg2->hdr.type)
|
||||
return -10;
|
||||
if (msg1->hdr.length != msg2->hdr.length)
|
||||
return -20;
|
||||
if (msg1->hdr.magic != msg2->hdr.magic)
|
||||
return -30;
|
||||
if (pj_memcmp(msg1->hdr.tsx_id, msg2->hdr.tsx_id, sizeof(msg1->hdr.tsx_id)))
|
||||
return -40;
|
||||
if (msg1->attr_count != msg2->attr_count)
|
||||
return -50;
|
||||
|
||||
for (i=0; i<msg1->attr_count; ++i) {
|
||||
const pj_stun_attr_hdr *a1 = msg1->attr[i];
|
||||
const pj_stun_attr_hdr *a2 = msg2->attr[i];
|
||||
|
||||
if (a1->type != a2->type)
|
||||
return -60;
|
||||
if (a1->length != a2->length)
|
||||
return -70;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decode and authenticate message with unknown non-mandatory attribute */
|
||||
static int handle_unknown_non_mandatory(void)
|
||||
{
|
||||
pj_pool_t *pool = pj_pool_create(mem, NULL, 1000, 1000, NULL);
|
||||
pj_stun_msg *msg0, *msg1, *msg2;
|
||||
pj_uint8_t data[] = { 1, 2, 3, 4, 5, 6};
|
||||
pj_uint8_t packet[500];
|
||||
pj_stun_auth_cred cred;
|
||||
pj_size_t len;
|
||||
pj_status_t rc;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " handling unknown non-mandatory attr"));
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " encoding"));
|
||||
rc = pj_stun_msg_create(pool, PJ_STUN_BINDING_REQUEST, PJ_STUN_MAGIC, NULL, &msg0);
|
||||
rc += pj_stun_msg_add_string_attr(pool, msg0, PJ_STUN_ATTR_USERNAME, &USERNAME);
|
||||
rc += pj_stun_msg_add_binary_attr(pool, msg0, 0x80ff, data, sizeof(data));
|
||||
rc += pj_stun_msg_add_msgint_attr(pool, msg0);
|
||||
rc += pj_stun_msg_encode(msg0, packet, sizeof(packet), 0, &PASSWORD, &len);
|
||||
|
||||
#if 0
|
||||
if (1) {
|
||||
unsigned i;
|
||||
puts("");
|
||||
printf("{ ");
|
||||
for (i=0; i<len; ++i) printf("0x%02x, ", packet[i] & 0xFF);
|
||||
puts(" }");
|
||||
}
|
||||
#endif
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " decoding"));
|
||||
rc += pj_stun_msg_decode(pool, packet, len, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
|
||||
&msg1, NULL, NULL);
|
||||
|
||||
rc += cmp_msg(msg0, msg1);
|
||||
|
||||
pj_bzero(&cred, sizeof(cred));
|
||||
cred.type = PJ_STUN_AUTH_CRED_STATIC;
|
||||
cred.data.static_cred.username = USERNAME;
|
||||
cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
|
||||
cred.data.static_cred.data = PASSWORD;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " authenticating"));
|
||||
rc += pj_stun_authenticate_request(packet, (unsigned)len, msg1, &cred, pool,
|
||||
NULL, NULL);
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " clone"));
|
||||
msg2 = pj_stun_msg_clone(pool, msg1);
|
||||
rc += cmp_msg(msg0, msg2);
|
||||
|
||||
pj_pool_release(pool);
|
||||
|
||||
return rc==0 ? 0 : -4410;
|
||||
}
|
||||
|
||||
|
||||
int stun_test(void)
|
||||
{
|
||||
int pad, rc;
|
||||
|
||||
pad = pj_stun_set_padding_char(32);
|
||||
|
||||
rc = decode_test();
|
||||
if (rc != 0)
|
||||
goto on_return;
|
||||
|
||||
rc = decode_verify();
|
||||
if (rc != 0)
|
||||
goto on_return;
|
||||
|
||||
rc = fingerprint_test_vector();
|
||||
if (rc != 0)
|
||||
goto on_return;
|
||||
|
||||
rc = handle_unknown_non_mandatory();
|
||||
if (rc != 0)
|
||||
goto on_return;
|
||||
|
||||
on_return:
|
||||
pj_stun_set_padding_char(pad);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,886 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "test.h"
|
||||
|
||||
#define THIS_FILE "stun_sock_test.c"
|
||||
|
||||
enum {
|
||||
RESPOND_STUN = 1,
|
||||
WITH_MAPPED = 2,
|
||||
WITH_XOR_MAPPED = 4,
|
||||
ECHO = 8
|
||||
};
|
||||
|
||||
/*
|
||||
* Simple STUN server
|
||||
*/
|
||||
struct stun_srv
|
||||
{
|
||||
pj_activesock_t *asock;
|
||||
unsigned flag;
|
||||
pj_sockaddr addr;
|
||||
unsigned rx_cnt;
|
||||
pj_ioqueue_op_key_t send_key;
|
||||
pj_str_t ip_to_send;
|
||||
pj_uint16_t port_to_send;
|
||||
};
|
||||
|
||||
static pj_bool_t srv_on_data_recvfrom(pj_activesock_t *asock,
|
||||
void *data,
|
||||
pj_size_t size,
|
||||
const pj_sockaddr_t *src_addr,
|
||||
int addr_len,
|
||||
pj_status_t status)
|
||||
{
|
||||
struct stun_srv *srv;
|
||||
pj_ssize_t sent;
|
||||
|
||||
srv = (struct stun_srv*) pj_activesock_get_user_data(asock);
|
||||
|
||||
/* Ignore error */
|
||||
if (status != PJ_SUCCESS)
|
||||
return PJ_TRUE;
|
||||
|
||||
++srv->rx_cnt;
|
||||
|
||||
/* Ignore if we're not responding */
|
||||
if (srv->flag & RESPOND_STUN) {
|
||||
pj_pool_t *pool;
|
||||
pj_stun_msg *req_msg, *res_msg;
|
||||
|
||||
pool = pj_pool_create(mem, "stunsrv", 512, 512, NULL);
|
||||
|
||||
/* Parse request */
|
||||
status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size,
|
||||
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
|
||||
&req_msg, NULL, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror(" pj_stun_msg_decode()", status);
|
||||
pj_pool_release(pool);
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
/* Create response */
|
||||
status = pj_stun_msg_create(pool, PJ_STUN_BINDING_RESPONSE, PJ_STUN_MAGIC,
|
||||
req_msg->hdr.tsx_id, &res_msg);
|
||||
if (status != PJ_SUCCESS) {
|
||||
app_perror(" pj_stun_msg_create()", status);
|
||||
pj_pool_release(pool);
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
/* Add MAPPED-ADDRESS or XOR-MAPPED-ADDRESS (or don't add) */
|
||||
if (srv->flag & WITH_MAPPED) {
|
||||
pj_sockaddr addr;
|
||||
pj_bool_t use_ipv6 = (srv->addr.addr.sa_family == pj_AF_INET6());
|
||||
|
||||
pj_sockaddr_init(GET_AF(use_ipv6), &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 addr;
|
||||
pj_bool_t use_ipv6 = (srv->addr.addr.sa_family == pj_AF_INET6());
|
||||
|
||||
pj_sockaddr_init(GET_AF(use_ipv6), &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,
|
||||
pj_bool_t use_ipv6,
|
||||
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 = (use_ipv6)?pj_str("2002:101:101::"):pj_str("1.1.1.1");
|
||||
srv->port_to_send = 1000;
|
||||
|
||||
status = pj_sockaddr_init(GET_AF(use_ipv6), &srv->addr, 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_bool_t use_ipv6)
|
||||
{
|
||||
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, GET_AF(use_ipv6), &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,
|
||||
pj_bool_t use_ipv6)
|
||||
{
|
||||
struct stun_srv *srv;
|
||||
struct stun_client *client;
|
||||
pj_str_t srv_addr;
|
||||
pj_time_val timeout, t;
|
||||
int i, ret = 0;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " timeout test [%d] - (%s)", destroy_on_err,
|
||||
(use_ipv6)?"IPv6":"IPv4"));
|
||||
|
||||
status = create_client(cfg, &client, destroy_on_err, use_ipv6);
|
||||
if (status != PJ_SUCCESS)
|
||||
return -10;
|
||||
|
||||
status = create_server(client->pool, cfg->ioqueue, 0, use_ipv6, &srv);
|
||||
if (status != PJ_SUCCESS) {
|
||||
destroy_client(client);
|
||||
return -20;
|
||||
}
|
||||
|
||||
srv_addr = (use_ipv6)?pj_str("::1"):pj_str("127.0.0.1");
|
||||
|
||||
status = pj_stun_sock_start(client->sock, &srv_addr,
|
||||
pj_sockaddr_get_port(&srv->addr), 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);
|
||||
for (i=0; i<7; ++i)
|
||||
handle_events(cfg, 100);
|
||||
|
||||
if ((ret == 0) && use_ipv6)
|
||||
ret = timeout_test(cfg, destroy_on_err, 0);
|
||||
|
||||
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,
|
||||
pj_bool_t use_ipv6)
|
||||
{
|
||||
struct stun_srv *srv;
|
||||
struct stun_client *client;
|
||||
pj_str_t srv_addr;
|
||||
pj_time_val timeout, t;
|
||||
int i, ret = 0;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " missing attribute test [%d] - (%s)",
|
||||
destroy_on_err, (use_ipv6)?"IPv6":"IPv4"));
|
||||
|
||||
status = create_client(cfg, &client, destroy_on_err, use_ipv6);
|
||||
if (status != PJ_SUCCESS)
|
||||
return -110;
|
||||
|
||||
status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN, use_ipv6,
|
||||
&srv);
|
||||
if (status != PJ_SUCCESS) {
|
||||
destroy_client(client);
|
||||
return -120;
|
||||
}
|
||||
srv_addr = (use_ipv6)?pj_str("::1"):pj_str("127.0.0.1");
|
||||
|
||||
status = pj_stun_sock_start(client->sock, &srv_addr,
|
||||
pj_sockaddr_get_port(&srv->addr), 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);
|
||||
for (i=0; i<7; ++i)
|
||||
handle_events(cfg, 100);
|
||||
|
||||
if ((ret == 0) && use_ipv6)
|
||||
ret = missing_attr_test(cfg, destroy_on_err, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep-alive test.
|
||||
*/
|
||||
static int keep_alive_test(pj_stun_config *cfg, pj_bool_t use_ipv6)
|
||||
{
|
||||
struct stun_srv *srv;
|
||||
struct stun_client *client;
|
||||
pj_sockaddr mapped_addr;
|
||||
pj_stun_sock_info info;
|
||||
pj_str_t srv_addr;
|
||||
pj_time_val timeout, t;
|
||||
int i, ret = 0;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, " normal operation - (%s)",
|
||||
(use_ipv6)?"IPv6":"IPv4"));
|
||||
|
||||
status = create_client(cfg, &client, PJ_TRUE, use_ipv6);
|
||||
if (status != PJ_SUCCESS)
|
||||
return -310;
|
||||
|
||||
status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN|WITH_XOR_MAPPED,
|
||||
use_ipv6, &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 = (use_ipv6)?pj_str("::1"):pj_str("127.0.0.1");
|
||||
|
||||
status = pj_stun_sock_start(client->sock, &srv_addr,
|
||||
pj_sockaddr_get_port(&srv->addr), 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_init(GET_AF(use_ipv6), &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 = (use_ipv6)?pj_str("2002:202:202::"):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_init(GET_AF(use_ipv6), &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);
|
||||
for (i=0; i<7; ++i)
|
||||
handle_events(cfg, 100);
|
||||
|
||||
if ((ret == 0) && use_ipv6)
|
||||
ret = keep_alive_test(cfg, 0);
|
||||
|
||||
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, USE_IPV6));
|
||||
DO_TEST(timeout_test(&stun_cfg, PJ_TRUE, USE_IPV6));
|
||||
|
||||
DO_TEST(missing_attr_test(&stun_cfg, PJ_FALSE, USE_IPV6));
|
||||
DO_TEST(missing_attr_test(&stun_cfg, PJ_TRUE, USE_IPV6));
|
||||
|
||||
DO_TEST(keep_alive_test(&stun_cfg, USE_IPV6));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "test.h"
|
||||
#include <pjlib.h>
|
||||
#include <pj/compat/socket.h>
|
||||
|
||||
void app_perror_dbg(const char *msg, pj_status_t rc,
|
||||
const char *file, int line)
|
||||
{
|
||||
char errbuf[256];
|
||||
|
||||
PJ_CHECK_STACK();
|
||||
|
||||
pj_strerror(rc, errbuf, sizeof(errbuf));
|
||||
PJ_LOG(1,("test", "%s:%d: %s: [pj_status_t=%d] %s", file, line, msg,
|
||||
rc, errbuf));
|
||||
}
|
||||
|
||||
/* Set socket to nonblocking. */
|
||||
void app_set_sock_nb(pj_sock_t sock)
|
||||
{
|
||||
#if defined(PJ_WIN32) && PJ_WIN32!=0 || \
|
||||
defined(PJ_WIN64) && PJ_WIN64 != 0 || \
|
||||
defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0
|
||||
u_long value = 1;
|
||||
ioctlsocket(sock, FIONBIO, &value);
|
||||
#else
|
||||
pj_uint32_t value = 1;
|
||||
ioctl(sock, FIONBIO, &value);
|
||||
#endif
|
||||
}
|
||||
|
||||
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_lock_t *lock;
|
||||
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_lock_create_recursive_mutex(pool, NULL, &lock);
|
||||
pj_timer_heap_set_lock(timer_heap, lock, PJ_TRUE);
|
||||
|
||||
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 = (unsigned)pj_timer_heap_count(cfg->timer_heap);
|
||||
|
||||
cp = (pj_caching_pool*)cfg->pf;
|
||||
st->pool_used_cnt = (unsigned)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 PJ_TIMER_DEBUG
|
||||
pj_timer_heap_dump(cfg->timer_heap);
|
||||
#endif
|
||||
}
|
||||
|
||||
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_SENDER |
|
||||
PJ_LOG_HAS_MICRO_SEC;
|
||||
|
||||
pj_log_func *orig_log_func;
|
||||
FILE *log_file;
|
||||
|
||||
static void test_log_func(int level, const char *data, int len)
|
||||
{
|
||||
if (log_file) {
|
||||
fwrite(data, len, 1, log_file);
|
||||
}
|
||||
if (level <= 3)
|
||||
orig_log_func(level, data, len);
|
||||
}
|
||||
|
||||
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);
|
||||
PJ_UNUSED_ARG(test_log_func);
|
||||
#elif 1
|
||||
log_file = fopen("pjnath-test.log", "wt");
|
||||
pj_log_set_level(5);
|
||||
orig_log_func = pj_log_get_log_func();
|
||||
pj_log_set_log_func(&test_log_func);
|
||||
#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_TRICKLE_ICE_TEST
|
||||
DO_TEST(trickle_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
|
||||
|
||||
#if INCLUDE_CONCUR_TEST
|
||||
DO_TEST(concur_test());
|
||||
#endif
|
||||
|
||||
on_return:
|
||||
if (log_file)
|
||||
fclose(log_file);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjlib.h>
|
||||
#include <pjlib-util.h>
|
||||
#include <pjnath.h>
|
||||
|
||||
#if defined(PJ_EXCLUDE_BENCHMARK_TESTS) && (PJ_EXCLUDE_BENCHMARK_TESTS==1)
|
||||
# define WITH_BENCHMARK 0
|
||||
#else
|
||||
# define WITH_BENCHMARK 1
|
||||
#endif
|
||||
|
||||
#define INCLUDE_STUN_TEST 1
|
||||
#define INCLUDE_ICE_TEST 1
|
||||
#define INCLUDE_TRICKLE_ICE_TEST 1
|
||||
#define INCLUDE_STUN_SOCK_TEST 1
|
||||
#define INCLUDE_TURN_SOCK_TEST 1
|
||||
#define INCLUDE_CONCUR_TEST 1
|
||||
|
||||
#define GET_AF(use_ipv6) (use_ipv6?pj_AF_INET6():pj_AF_INET())
|
||||
|
||||
#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
|
||||
# define USE_IPV6 1
|
||||
#else
|
||||
# define USE_IPV6 0
|
||||
#endif
|
||||
|
||||
#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK
|
||||
# define USE_TLS 1
|
||||
#else
|
||||
# define USE_TLS 0
|
||||
#endif
|
||||
|
||||
int stun_test(void);
|
||||
int sess_auth_test(void);
|
||||
int stun_sock_test(void);
|
||||
int turn_sock_test(void);
|
||||
int ice_test(void);
|
||||
int trickle_ice_test(void);
|
||||
int concur_test(void);
|
||||
int test_main(void);
|
||||
|
||||
#define app_perror(msg, rc) app_perror_dbg(msg, rc, __FILE__, __LINE__)
|
||||
extern void app_perror_dbg(const char *msg, pj_status_t rc,
|
||||
const char *file, int line);
|
||||
extern void app_set_sock_nb(pj_sock_t sock);
|
||||
extern pj_pool_factory *mem;
|
||||
|
||||
int ice_one_conc_test(pj_stun_config *stun_cfg, int err_quit);
|
||||
|
||||
////////////////////////////////////
|
||||
/*
|
||||
* 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);
|
||||
|
||||
pj_turn_tp_type get_turn_tp_type(pj_uint32_t flag);
|
||||
|
||||
#define ERR_MEMORY_LEAK 1
|
||||
#define ERR_TIMER_LEAK 2
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "test.h"
|
||||
#include "server.h"
|
||||
|
||||
#define SRV_DOMAIN "pjsip.lab.domain"
|
||||
#define KA_INTERVAL 50
|
||||
|
||||
struct test_result
|
||||
{
|
||||
unsigned state_called;
|
||||
unsigned rx_data_cnt;
|
||||
};
|
||||
|
||||
struct test_session
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_stun_config *stun_cfg;
|
||||
pj_turn_sock *turn_sock;
|
||||
pj_dns_resolver *resolver;
|
||||
test_server *test_srv;
|
||||
|
||||
pj_bool_t destroy_called;
|
||||
int destroy_on_state;
|
||||
struct test_result result;
|
||||
};
|
||||
|
||||
struct test_session_cfg
|
||||
{
|
||||
struct {
|
||||
pj_bool_t enable_dns_srv;
|
||||
int destroy_on_state;
|
||||
} client;
|
||||
|
||||
struct {
|
||||
pj_uint32_t flags;
|
||||
pj_bool_t respond_allocate;
|
||||
pj_bool_t respond_refresh;
|
||||
} srv;
|
||||
};
|
||||
|
||||
static void turn_on_rx_data(pj_turn_sock *turn_sock,
|
||||
void *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_sockaddr_t *peer_addr,
|
||||
unsigned addr_len);
|
||||
static void turn_on_state(pj_turn_sock *turn_sock,
|
||||
pj_turn_state_t old_state,
|
||||
pj_turn_state_t new_state);
|
||||
|
||||
static void destroy_session(struct test_session *sess)
|
||||
{
|
||||
if (sess->resolver) {
|
||||
pj_dns_resolver_destroy(sess->resolver, PJ_TRUE);
|
||||
sess->resolver = NULL;
|
||||
}
|
||||
|
||||
if (sess->turn_sock) {
|
||||
if (!sess->destroy_called) {
|
||||
sess->destroy_called = PJ_TRUE;
|
||||
pj_turn_sock_destroy(sess->turn_sock);
|
||||
}
|
||||
sess->turn_sock = NULL;
|
||||
}
|
||||
|
||||
if (sess->test_srv) {
|
||||
destroy_test_server(sess->test_srv);
|
||||
sess->test_srv = NULL;
|
||||
}
|
||||
|
||||
if (sess->pool) {
|
||||
pj_pool_release(sess->pool);
|
||||
}
|
||||
}
|
||||
|
||||
pj_turn_tp_type get_turn_tp_type(pj_uint32_t flag) {
|
||||
if (flag & TURN_TCP) {
|
||||
return PJ_TURN_TP_TCP;
|
||||
} else if (flag & TURN_TLS) {
|
||||
return PJ_TURN_TP_TLS;
|
||||
}
|
||||
return PJ_TURN_TP_UDP;
|
||||
}
|
||||
|
||||
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;
|
||||
pj_bool_t use_ipv6 = cfg->srv.flags & SERVER_IPV6;
|
||||
pj_turn_tp_type tp_type = get_turn_tp_type(cfg->srv.flags);
|
||||
|
||||
/* 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,
|
||||
GET_AF(use_ipv6),
|
||||
tp_type,
|
||||
&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 = use_ipv6?pj_str("::1") : 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 = use_ipv6?pj_str("::1") : pj_str("127.0.0.1");
|
||||
status = pj_turn_sock_alloc(sess->turn_sock, &host, TURN_SERVER_PORT,
|
||||
NULL, &cred, &alloc_param);
|
||||
|
||||
}
|
||||
|
||||
if (status != PJ_SUCCESS) {
|
||||
if (cfg->client.destroy_on_state >= PJ_TURN_STATE_READY) {
|
||||
destroy_session(sess);
|
||||
return -70;
|
||||
}
|
||||
}
|
||||
|
||||
*p_sess = sess;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void turn_on_rx_data(pj_turn_sock *turn_sock,
|
||||
void *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_sockaddr_t *peer_addr,
|
||||
unsigned addr_len)
|
||||
{
|
||||
struct test_session *sess;
|
||||
|
||||
PJ_UNUSED_ARG(pkt);
|
||||
PJ_UNUSED_ARG(pkt_len);
|
||||
PJ_UNUSED_ARG(peer_addr);
|
||||
PJ_UNUSED_ARG(addr_len);
|
||||
|
||||
sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock);
|
||||
if (sess == NULL)
|
||||
return;
|
||||
|
||||
sess->result.rx_data_cnt++;
|
||||
}
|
||||
|
||||
|
||||
static void turn_on_state(pj_turn_sock *turn_sock,
|
||||
pj_turn_state_t old_state,
|
||||
pj_turn_state_t new_state)
|
||||
{
|
||||
struct test_session *sess;
|
||||
unsigned i, mask;
|
||||
|
||||
PJ_UNUSED_ARG(old_state);
|
||||
|
||||
sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock);
|
||||
if (sess == NULL)
|
||||
return;
|
||||
|
||||
/* This state must not be called before */
|
||||
pj_assert((sess->result.state_called & (1<<new_state)) == 0);
|
||||
|
||||
/* new_state must be greater than old_state */
|
||||
pj_assert(new_state > old_state);
|
||||
|
||||
/* must not call any greater state before */
|
||||
mask = 0;
|
||||
for (i=new_state+1; i<31; ++i) mask |= (1 << i);
|
||||
|
||||
pj_assert((sess->result.state_called & mask) == 0);
|
||||
|
||||
sess->result.state_called |= (1 << new_state);
|
||||
|
||||
if (new_state >= sess->destroy_on_state && !sess->destroy_called) {
|
||||
sess->destroy_called = PJ_TRUE;
|
||||
pj_turn_sock_destroy(turn_sock);
|
||||
}
|
||||
|
||||
if (new_state >= PJ_TURN_STATE_DESTROYING) {
|
||||
pj_turn_sock_set_user_data(sess->turn_sock, NULL);
|
||||
sess->turn_sock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void set_server_flag(struct test_session_cfg *test_cfg,
|
||||
pj_bool_t use_ipv6,
|
||||
pj_turn_tp_type tp_type)
|
||||
{
|
||||
pj_uint32_t flag = TURN_UDP;
|
||||
test_cfg->srv.flags &= ~(SERVER_IPV4+SERVER_IPV6+
|
||||
TURN_UDP+TURN_TCP+TURN_TLS);
|
||||
switch (tp_type) {
|
||||
case PJ_TURN_TP_TCP:
|
||||
flag = TURN_TCP;
|
||||
break;
|
||||
case PJ_TURN_TP_TLS:
|
||||
flag = TURN_TLS;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
test_cfg->srv.flags |= ((use_ipv6)?SERVER_IPV6:SERVER_IPV4)+flag;
|
||||
}
|
||||
|
||||
static int state_progression_test(pj_stun_config *stun_cfg,
|
||||
pj_bool_t use_ipv6,
|
||||
pj_turn_tp_type tp_type)
|
||||
{
|
||||
struct test_session_cfg test_cfg =
|
||||
{
|
||||
{ /* Client cfg */
|
||||
PJ_TRUE, /* DNS SRV */
|
||||
0xFFFF /* Destroy on state */
|
||||
},
|
||||
{ /* Server cfg */
|
||||
0xFFFFFFFF, /* flags */
|
||||
PJ_TRUE, /* respond to allocate */
|
||||
PJ_TRUE /* respond to refresh */
|
||||
}
|
||||
};
|
||||
struct test_session *sess;
|
||||
unsigned i;
|
||||
int rc = 0;
|
||||
|
||||
PJ_LOG(3,("", " state progression tests - (%s) (%s)",
|
||||
use_ipv6?"IPv6":"IPv4",
|
||||
(tp_type==PJ_TURN_TP_UDP)?"UDP":
|
||||
(tp_type==PJ_TURN_TP_TCP)?"TCP":"TLS"));
|
||||
|
||||
set_server_flag(&test_cfg, use_ipv6, tp_type);
|
||||
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);
|
||||
if (sess->turn_sock == NULL) {
|
||||
break;
|
||||
}
|
||||
rc = pj_turn_sock_get_info(sess->turn_sock, &info);
|
||||
if (rc!=PJ_SUCCESS)
|
||||
break;
|
||||
|
||||
if (info.state >= PJ_TURN_STATE_READY)
|
||||
break;
|
||||
|
||||
pj_gettimeofday(&now);
|
||||
if (now.sec - tstart.sec > TIMEOUT) {
|
||||
PJ_LOG(3,("", " timed-out"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.state != PJ_TURN_STATE_READY) {
|
||||
PJ_LOG(3,("", " error: state is not READY"));
|
||||
destroy_session(sess);
|
||||
return -130;
|
||||
}
|
||||
|
||||
/* Deallocate */
|
||||
pj_turn_sock_destroy(sess->turn_sock);
|
||||
|
||||
/* Wait for couple of seconds.
|
||||
* We can't poll the session info since the session may have
|
||||
* been destroyed
|
||||
*/
|
||||
poll_events(stun_cfg, 2000, PJ_FALSE);
|
||||
sess->turn_sock = NULL;
|
||||
pj_memcpy(&result, &sess->result, sizeof(result));
|
||||
destroy_session(sess);
|
||||
|
||||
/* Check the result */
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_RESOLVING)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_RESOLVING is not called"));
|
||||
return -140;
|
||||
}
|
||||
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_RESOLVED)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_RESOLVED is not called"));
|
||||
return -150;
|
||||
}
|
||||
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_ALLOCATING)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_ALLOCATING is not called"));
|
||||
return -155;
|
||||
}
|
||||
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_READY)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_READY is not called"));
|
||||
return -160;
|
||||
}
|
||||
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_DEALLOCATING)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_DEALLOCATING is not called"));
|
||||
return -170;
|
||||
}
|
||||
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_DEALLOCATED)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_DEALLOCATED is not called"));
|
||||
return -180;
|
||||
}
|
||||
|
||||
if ((result.state_called & (1<<PJ_TURN_STATE_DESTROYING)) == 0) {
|
||||
PJ_LOG(3,("", " error: PJ_TURN_STATE_DESTROYING is not called"));
|
||||
return -190;
|
||||
}
|
||||
|
||||
poll_events(stun_cfg, 500, PJ_FALSE);
|
||||
rc = check_pjlib_state(stun_cfg, &pjlib_state);
|
||||
if (rc != 0) {
|
||||
PJ_LOG(3,("", " error: memory/timer-heap leak detected"));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_ipv6)
|
||||
rc = state_progression_test(stun_cfg, 0, tp_type);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int destroy_test(pj_stun_config *stun_cfg,
|
||||
pj_bool_t with_dns_srv,
|
||||
pj_bool_t in_callback,
|
||||
pj_bool_t use_ipv6,
|
||||
pj_turn_tp_type tp_type)
|
||||
{
|
||||
struct test_session_cfg test_cfg =
|
||||
{
|
||||
{ /* Client cfg */
|
||||
PJ_TRUE, /* DNS SRV */
|
||||
0xFFFF /* Destroy on state */
|
||||
},
|
||||
{ /* Server cfg */
|
||||
0xFFFFFFFF, /* flags */
|
||||
PJ_TRUE, /* respond to allocate */
|
||||
PJ_TRUE /* respond to refresh */
|
||||
}
|
||||
};
|
||||
struct test_session *sess;
|
||||
int target_state;
|
||||
int rc;
|
||||
|
||||
PJ_LOG(3,("", " destroy test %s %s (%s)",
|
||||
(in_callback? "in callback" : ""),
|
||||
(with_dns_srv? "with DNS srv" : ""),
|
||||
(tp_type==PJ_TURN_TP_UDP)?"UDP":
|
||||
(tp_type==PJ_TURN_TP_TCP)?"TCP":"TLS"));
|
||||
|
||||
test_cfg.client.enable_dns_srv = with_dns_srv;
|
||||
set_server_flag(&test_cfg, use_ipv6, tp_type);
|
||||
|
||||
for (target_state=PJ_TURN_STATE_RESOLVING; target_state<=PJ_TURN_STATE_READY; ++target_state) {
|
||||
enum { TIMEOUT = 60 };
|
||||
pjlib_state pjlib_state;
|
||||
pj_turn_session_info info;
|
||||
pj_time_val tstart;
|
||||
|
||||
capture_pjlib_state(stun_cfg, &pjlib_state);
|
||||
|
||||
PJ_LOG(3,("", " %s", pj_turn_state_name((pj_turn_state_t)target_state)));
|
||||
|
||||
if (in_callback)
|
||||
test_cfg.client.destroy_on_state = target_state;
|
||||
|
||||
rc = create_test_session(stun_cfg, &test_cfg, &sess);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
if (in_callback) {
|
||||
pj_gettimeofday(&tstart);
|
||||
rc = 0;
|
||||
while (sess->turn_sock) {
|
||||
pj_time_val now;
|
||||
|
||||
poll_events(stun_cfg, 100, PJ_FALSE);
|
||||
|
||||
pj_gettimeofday(&now);
|
||||
if (now.sec - tstart.sec > TIMEOUT) {
|
||||
rc = -7;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
pj_gettimeofday(&tstart);
|
||||
rc = 0;
|
||||
while (sess->turn_sock) {
|
||||
pj_time_val now;
|
||||
|
||||
poll_events(stun_cfg, 1, PJ_FALSE);
|
||||
|
||||
pj_turn_sock_get_info(sess->turn_sock, &info);
|
||||
|
||||
if (info.state >= target_state) {
|
||||
pj_turn_sock_destroy(sess->turn_sock);
|
||||
break;
|
||||
}
|
||||
|
||||
pj_gettimeofday(&now);
|
||||
if (now.sec - tstart.sec > TIMEOUT) {
|
||||
rc = -8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (rc != 0) {
|
||||
PJ_LOG(3,("", " error: timeout"));
|
||||
return rc;
|
||||
}
|
||||
|
||||
poll_events(stun_cfg, 1000, PJ_FALSE);
|
||||
destroy_session(sess);
|
||||
|
||||
rc = check_pjlib_state(stun_cfg, &pjlib_state);
|
||||
if (rc != 0) {
|
||||
PJ_LOG(3,("", " error: memory/timer-heap leak detected"));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
int turn_sock_test(void)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_stun_config stun_cfg;
|
||||
int n, 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;
|
||||
}
|
||||
|
||||
for (n = 0; n <= 2; ++n) {
|
||||
pj_turn_tp_type tp_type = PJ_TURN_TP_UDP;
|
||||
|
||||
if ((n == 2) && !USE_TLS)
|
||||
break;
|
||||
|
||||
switch (n) {
|
||||
case 1:
|
||||
tp_type = PJ_TURN_TP_TCP;
|
||||
break;
|
||||
case 2:
|
||||
tp_type = PJ_TURN_TP_TLS;
|
||||
}
|
||||
|
||||
rc = state_progression_test(&stun_cfg, USE_IPV6, tp_type);
|
||||
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, USE_IPV6, tp_type);
|
||||
if (rc != 0)
|
||||
goto on_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
on_return:
|
||||
destroy_stun_config(&stun_cfg);
|
||||
pj_pool_release(pool);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjnath/errno.h>
|
||||
#include <pjnath/stun_msg.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
|
||||
|
||||
/* PJNATH's own error codes/messages
|
||||
* MUST KEEP THIS ARRAY SORTED!!
|
||||
* Message must be limited to 64 chars!
|
||||
*/
|
||||
#if defined(PJ_HAS_ERROR_STRING) && PJ_HAS_ERROR_STRING!=0
|
||||
static const struct
|
||||
{
|
||||
int code;
|
||||
const char *msg;
|
||||
} err_str[] =
|
||||
{
|
||||
/* STUN related error codes */
|
||||
PJ_BUILD_ERR( PJNATH_EINSTUNMSG, "Invalid STUN message"),
|
||||
PJ_BUILD_ERR( PJNATH_EINSTUNMSGLEN, "Invalid STUN message length"),
|
||||
PJ_BUILD_ERR( PJNATH_EINSTUNMSGTYPE, "Invalid or unexpected STUN message type"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNTIMEDOUT, "STUN transaction has timed out"),
|
||||
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNTOOMANYATTR, "Too many STUN attributes"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNINATTRLEN, "Invalid STUN attribute length"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNDUPATTR, "Found duplicate STUN attribute"),
|
||||
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNFINGERPRINT, "STUN FINGERPRINT verification failed"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNMSGINTPOS, "Invalid STUN attribute after MESSAGE-INTEGRITY"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNFINGERPOS, "Invalid STUN attribute after FINGERPRINT"),
|
||||
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNNOMAPPEDADDR, "STUN (XOR-)MAPPED-ADDRESS attribute not found"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNIPV6NOTSUPP, "STUN IPv6 attribute not supported"),
|
||||
PJ_BUILD_ERR( PJNATH_EINVAF, "Invalid STUN address family value"),
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNINSERVER, "Invalid STUN server or server not configured"),
|
||||
|
||||
PJ_BUILD_ERR( PJNATH_ESTUNDESTROYED, "STUN object has been destoyed"),
|
||||
|
||||
/* ICE related errors */
|
||||
PJ_BUILD_ERR( PJNATH_ENOICE, "ICE session not available"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEINPROGRESS, "ICE check is in progress"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEFAILED, "All ICE checklists failed"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEMISMATCH, "Default target doesn't match any ICE candidates"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEINCOMPID, "Invalid ICE component ID"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEINCANDID, "Invalid ICE candidate ID"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEINSRCADDR, "Source address mismatch"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEMISSINGSDP, "Missing ICE SDP attribute"),
|
||||
PJ_BUILD_ERR( PJNATH_EICEINCANDSDP, "Invalid SDP \"candidate\" attribute"),
|
||||
PJ_BUILD_ERR( PJNATH_EICENOHOSTCAND, "No host candidate associated with srflx"),
|
||||
PJ_BUILD_ERR( PJNATH_EICENOMTIMEOUT, "Controlled agent timed out waiting for nomination"),
|
||||
|
||||
/* TURN related errors */
|
||||
PJ_BUILD_ERR( PJNATH_ETURNINTP, "Invalid/unsupported transport"),
|
||||
|
||||
};
|
||||
#endif /* PJ_HAS_ERROR_STRING */
|
||||
|
||||
|
||||
/*
|
||||
* pjnath_strerror()
|
||||
*/
|
||||
static pj_str_t pjnath_strerror(pj_status_t statcode,
|
||||
char *buf, pj_size_t bufsize )
|
||||
{
|
||||
pj_str_t errstr;
|
||||
|
||||
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
|
||||
|
||||
if (statcode >= PJNATH_ERRNO_START &&
|
||||
statcode < PJNATH_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
|
||||
{
|
||||
/* Find the error in the table.
|
||||
* Use binary search!
|
||||
*/
|
||||
int first = 0;
|
||||
int n = PJ_ARRAY_SIZE(err_str);
|
||||
|
||||
while (n > 0) {
|
||||
int half = n/2;
|
||||
int mid = first + half;
|
||||
|
||||
if (err_str[mid].code < statcode) {
|
||||
first = mid+1;
|
||||
n -= (half+1);
|
||||
} else if (err_str[mid].code > statcode) {
|
||||
n = half;
|
||||
} else {
|
||||
first = mid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
|
||||
pj_str_t msg;
|
||||
|
||||
msg.ptr = (char*)err_str[first].msg;
|
||||
msg.slen = pj_ansi_strlen(err_str[first].msg);
|
||||
|
||||
errstr.ptr = buf;
|
||||
pj_strncpy_with_null(&errstr, &msg, bufsize);
|
||||
return errstr;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PJ_HAS_ERROR_STRING */
|
||||
|
||||
|
||||
/* Error not found. */
|
||||
errstr.ptr = buf;
|
||||
errstr.slen = pj_ansi_snprintf(buf, bufsize,
|
||||
"Unknown pjnath error %d",
|
||||
statcode);
|
||||
if (errstr.slen < 1 || errstr.slen >= (int)bufsize)
|
||||
errstr.slen = bufsize-1;
|
||||
|
||||
return errstr;
|
||||
}
|
||||
|
||||
|
||||
static pj_str_t pjnath_strerror2(pj_status_t statcode,
|
||||
char *buf, pj_size_t bufsize )
|
||||
{
|
||||
int stun_code = statcode - PJ_STATUS_FROM_STUN_CODE(0);
|
||||
const pj_str_t cmsg = pj_stun_get_err_reason(stun_code);
|
||||
pj_str_t errstr;
|
||||
|
||||
buf[bufsize-1] = '\0';
|
||||
|
||||
if (cmsg.slen == 0) {
|
||||
/* Not found */
|
||||
errstr.ptr = buf;
|
||||
errstr.slen = pj_ansi_snprintf(buf, bufsize,
|
||||
"Unknown STUN err-code %d",
|
||||
stun_code);
|
||||
} else {
|
||||
errstr.ptr = buf;
|
||||
pj_strncpy(&errstr, &cmsg, bufsize);
|
||||
if (errstr.slen < (int)bufsize)
|
||||
buf[errstr.slen] = '\0';
|
||||
else
|
||||
buf[bufsize-1] = '\0';
|
||||
}
|
||||
|
||||
if (errstr.slen < 1 || errstr.slen >= (int)bufsize)
|
||||
errstr.slen = bufsize-1;
|
||||
|
||||
return errstr;
|
||||
}
|
||||
|
||||
|
||||
PJ_DEF(pj_status_t) pjnath_init(void)
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_register_strerror(PJNATH_ERRNO_START, 299,
|
||||
&pjnath_strerror);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
status = pj_register_strerror(PJ_STATUS_FROM_STUN_CODE(300),
|
||||
699 - 300,
|
||||
&pjnath_strerror2);
|
||||
pj_assert(status == PJ_SUCCESS);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
#if PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL
|
||||
|
||||
PJ_DEF(void) pjnath_perror(const char *sender, const char *title,
|
||||
pj_status_t status)
|
||||
{
|
||||
#if PJNATH_ERROR_LEVEL==1
|
||||
PJ_PERROR(1,(sender, status, title));
|
||||
#elif PJNATH_ERROR_LEVEL==2
|
||||
PJ_PERROR(2,(sender, status, title));
|
||||
#elif PJNATH_ERROR_LEVEL==3
|
||||
PJ_PERROR(3,(sender, status, title));
|
||||
#elif PJNATH_ERROR_LEVEL==4
|
||||
PJ_PERROR(4,(sender, status, title));
|
||||
#elif PJNATH_ERROR_LEVEL==5
|
||||
PJ_PERROR(5,(sender, status, title));
|
||||
#else
|
||||
# error Invalid PJNATH_ERROR_LEVEL value
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* PJNATH_ERROR_LEVEL <= PJ_LOG_MAX_LEVEL */
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,947 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjnath/nat_detect.h>
|
||||
#include <pjnath/errno.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/ioqueue.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/os.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/rand.h>
|
||||
#include <pj/string.h>
|
||||
#include <pj/timer.h>
|
||||
#include <pj/compat/socket.h>
|
||||
|
||||
|
||||
static const char *nat_type_names[] =
|
||||
{
|
||||
"Unknown",
|
||||
"ErrUnknown",
|
||||
"Open",
|
||||
"Blocked",
|
||||
"Symmetric UDP",
|
||||
"Full Cone",
|
||||
"Symmetric",
|
||||
"Restricted",
|
||||
"Port Restricted"
|
||||
};
|
||||
|
||||
|
||||
#define CHANGE_IP_FLAG 4
|
||||
#define CHANGE_PORT_FLAG 2
|
||||
#define CHANGE_IP_PORT_FLAG (CHANGE_IP_FLAG | CHANGE_PORT_FLAG)
|
||||
#define TEST_INTERVAL 50
|
||||
|
||||
enum test_type
|
||||
{
|
||||
ST_TEST_1,
|
||||
ST_TEST_2,
|
||||
ST_TEST_3,
|
||||
ST_TEST_1B,
|
||||
ST_MAX
|
||||
};
|
||||
|
||||
static const char *test_names[] =
|
||||
{
|
||||
"Test I: Binding request",
|
||||
"Test II: Binding request with change address and port request",
|
||||
"Test III: Binding request with change port request",
|
||||
"Test IB: Binding request to alternate address"
|
||||
};
|
||||
|
||||
enum timer_type
|
||||
{
|
||||
TIMER_TEST = 1,
|
||||
TIMER_DESTROY = 2
|
||||
};
|
||||
|
||||
typedef struct nat_detect_session
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_grp_lock_t *grp_lock;
|
||||
|
||||
pj_timer_heap_t *timer_heap;
|
||||
pj_timer_entry timer;
|
||||
unsigned timer_executed;
|
||||
|
||||
void *user_data;
|
||||
pj_stun_nat_detect_cb *cb;
|
||||
pj_sock_t sock;
|
||||
pj_sockaddr local_addr;
|
||||
pj_ioqueue_key_t *key;
|
||||
pj_sockaddr server;
|
||||
pj_sockaddr *cur_server;
|
||||
pj_sockaddr cur_addr;
|
||||
pj_stun_session *stun_sess;
|
||||
|
||||
pj_ioqueue_op_key_t read_op, write_op;
|
||||
pj_uint8_t rx_pkt[PJ_STUN_MAX_PKT_LEN];
|
||||
pj_ssize_t rx_pkt_len;
|
||||
pj_sockaddr src_addr;
|
||||
int src_addr_len;
|
||||
|
||||
struct result
|
||||
{
|
||||
pj_bool_t executed;
|
||||
pj_bool_t complete;
|
||||
pj_status_t status;
|
||||
pj_sockaddr ma;
|
||||
pj_sockaddr ca;
|
||||
pj_stun_tx_data *tdata;
|
||||
} result[ST_MAX];
|
||||
|
||||
} nat_detect_session;
|
||||
|
||||
|
||||
static void on_read_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_ssize_t bytes_read);
|
||||
static void on_request_complete(pj_stun_session *sess,
|
||||
pj_status_t status,
|
||||
void *token,
|
||||
pj_stun_tx_data *tdata,
|
||||
const pj_stun_msg *response,
|
||||
const pj_sockaddr_t *src_addr,
|
||||
unsigned src_addr_len);
|
||||
static pj_status_t on_send_msg(pj_stun_session *sess,
|
||||
void *token,
|
||||
const void *pkt,
|
||||
pj_size_t pkt_size,
|
||||
const pj_sockaddr_t *dst_addr,
|
||||
unsigned addr_len);
|
||||
|
||||
static pj_status_t send_test(nat_detect_session *sess,
|
||||
enum test_type test_id,
|
||||
const pj_sockaddr *alt_addr,
|
||||
pj_uint32_t change_flag);
|
||||
static void on_sess_timer(pj_timer_heap_t *th,
|
||||
pj_timer_entry *te);
|
||||
static void sess_destroy(nat_detect_session *sess);
|
||||
static void sess_on_destroy(void *member);
|
||||
|
||||
/*
|
||||
* Get the NAT name from the specified NAT type.
|
||||
*/
|
||||
PJ_DEF(const char*) pj_stun_get_nat_name(pj_stun_nat_type type)
|
||||
{
|
||||
PJ_ASSERT_RETURN(type >= 0 && type <= PJ_STUN_NAT_TYPE_PORT_RESTRICTED,
|
||||
"*Invalid*");
|
||||
|
||||
return nat_type_names[type];
|
||||
}
|
||||
|
||||
static int test_executed(nat_detect_session *sess)
|
||||
{
|
||||
unsigned i, count;
|
||||
for (i=0, count=0; i<PJ_ARRAY_SIZE(sess->result); ++i) {
|
||||
if (sess->result[i].executed)
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static int test_completed(nat_detect_session *sess)
|
||||
{
|
||||
unsigned i, count;
|
||||
for (i=0, count=0; i<PJ_ARRAY_SIZE(sess->result); ++i) {
|
||||
if (sess->result[i].complete)
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static pj_status_t get_local_interface(const pj_sockaddr *server,
|
||||
pj_sockaddr *local_addr)
|
||||
{
|
||||
pj_sock_t sock;
|
||||
pj_sockaddr tmp, local;
|
||||
int addr_len;
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_sock_socket(server->addr.sa_family, pj_SOCK_DGRAM(),
|
||||
0, &sock);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
addr_len = pj_sockaddr_get_len(server);
|
||||
pj_sockaddr_init(server->addr.sa_family, &local, NULL, 0);
|
||||
status = pj_sock_bind(sock, &local, addr_len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_sock_close(sock);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pj_sock_connect(sock, server, addr_len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_sock_close(sock);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pj_sock_getsockname(sock, &tmp, &addr_len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_sock_close(sock);
|
||||
return status;
|
||||
}
|
||||
|
||||
pj_sockaddr_cp(local_addr, &tmp);
|
||||
|
||||
pj_sock_close(sock);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
PJ_DEF(pj_status_t) pj_stun_detect_nat_type(const pj_sockaddr_in *server,
|
||||
pj_stun_config *stun_cfg,
|
||||
void *user_data,
|
||||
pj_stun_nat_detect_cb *cb)
|
||||
{
|
||||
pj_sockaddr srv;
|
||||
|
||||
if (server)
|
||||
pj_sockaddr_cp(&srv, server);
|
||||
|
||||
return pj_stun_detect_nat_type2(&srv, stun_cfg, user_data, cb);
|
||||
}
|
||||
|
||||
PJ_DEF(pj_status_t) pj_stun_detect_nat_type2(const pj_sockaddr *server,
|
||||
pj_stun_config *stun_cfg,
|
||||
void *user_data,
|
||||
pj_stun_nat_detect_cb *cb)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
nat_detect_session *sess;
|
||||
pj_stun_session_cb sess_cb;
|
||||
pj_ioqueue_callback ioqueue_cb;
|
||||
int af, addr_len;
|
||||
char addr[PJ_INET6_ADDRSTRLEN];
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(server && stun_cfg, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(stun_cfg->pf && stun_cfg->ioqueue && stun_cfg->timer_heap,
|
||||
PJ_EINVAL);
|
||||
|
||||
/*
|
||||
* Init NAT detection session.
|
||||
*/
|
||||
pool = pj_pool_create(stun_cfg->pf, "natck%p", PJNATH_POOL_LEN_NATCK,
|
||||
PJNATH_POOL_INC_NATCK, NULL);
|
||||
if (!pool)
|
||||
return PJ_ENOMEM;
|
||||
|
||||
sess = PJ_POOL_ZALLOC_T(pool, nat_detect_session);
|
||||
sess->pool = pool;
|
||||
sess->user_data = user_data;
|
||||
sess->cb = cb;
|
||||
|
||||
status = pj_grp_lock_create(pool, NULL, &sess->grp_lock);
|
||||
if (status != PJ_SUCCESS) {
|
||||
/* Group lock not created yet, just destroy pool and return */
|
||||
pj_pool_release(pool);
|
||||
return status;
|
||||
}
|
||||
|
||||
pj_grp_lock_add_ref(sess->grp_lock);
|
||||
pj_grp_lock_add_handler(sess->grp_lock, pool, sess, &sess_on_destroy);
|
||||
|
||||
pj_sockaddr_cp(&sess->server, server);
|
||||
|
||||
/*
|
||||
* Init timer to self-destroy.
|
||||
*/
|
||||
sess->timer_heap = stun_cfg->timer_heap;
|
||||
sess->timer.cb = &on_sess_timer;
|
||||
sess->timer.user_data = sess;
|
||||
|
||||
|
||||
/*
|
||||
* Initialize socket.
|
||||
*/
|
||||
af = server->addr.sa_family;
|
||||
status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sess->sock);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/*
|
||||
* Bind to any.
|
||||
*/
|
||||
addr_len = pj_sockaddr_get_len(server);
|
||||
pj_sockaddr_init(server->addr.sa_family, &sess->local_addr, NULL, 0);
|
||||
status = pj_sock_bind(sess->sock, &sess->local_addr, addr_len);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/*
|
||||
* Get local/bound address.
|
||||
*/
|
||||
status = pj_sock_getsockname(sess->sock, &sess->local_addr, &addr_len);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/*
|
||||
* Find out which interface is used to send to the server.
|
||||
*/
|
||||
status = get_local_interface(server, &sess->local_addr);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
PJ_LOG(5,(sess->pool->obj_name, "Local address is %s:%d",
|
||||
pj_sockaddr_print(&sess->local_addr, addr, sizeof(addr), 2),
|
||||
pj_sockaddr_get_port(&sess->local_addr)));
|
||||
|
||||
PJ_LOG(5,(sess->pool->obj_name, "Server set to %s:%d",
|
||||
pj_sockaddr_print(server, addr, sizeof(addr), 2),
|
||||
pj_sockaddr_get_port(server)));
|
||||
|
||||
/*
|
||||
* Register socket to ioqueue to receive asynchronous input
|
||||
* notification.
|
||||
*/
|
||||
pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
|
||||
ioqueue_cb.on_read_complete = &on_read_complete;
|
||||
|
||||
status = pj_ioqueue_register_sock2(sess->pool, stun_cfg->ioqueue,
|
||||
sess->sock, sess->grp_lock, sess,
|
||||
&ioqueue_cb, &sess->key);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/*
|
||||
* Create STUN session.
|
||||
*/
|
||||
pj_bzero(&sess_cb, sizeof(sess_cb));
|
||||
sess_cb.on_request_complete = &on_request_complete;
|
||||
sess_cb.on_send_msg = &on_send_msg;
|
||||
status = pj_stun_session_create(stun_cfg, pool->obj_name, &sess_cb,
|
||||
PJ_FALSE, sess->grp_lock, &sess->stun_sess);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
pj_stun_session_set_user_data(sess->stun_sess, sess);
|
||||
|
||||
/*
|
||||
* Kick-off ioqueue reading.
|
||||
*/
|
||||
pj_ioqueue_op_key_init(&sess->read_op, sizeof(sess->read_op));
|
||||
pj_ioqueue_op_key_init(&sess->write_op, sizeof(sess->write_op));
|
||||
on_read_complete(sess->key, &sess->read_op, 0);
|
||||
|
||||
/*
|
||||
* Start TEST_1
|
||||
*/
|
||||
sess->timer.id = TIMER_TEST;
|
||||
on_sess_timer(stun_cfg->timer_heap, &sess->timer);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
|
||||
on_error:
|
||||
sess_destroy(sess);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static void sess_destroy(nat_detect_session *sess)
|
||||
{
|
||||
if (sess->stun_sess) {
|
||||
pj_stun_session_destroy(sess->stun_sess);
|
||||
sess->stun_sess = NULL;
|
||||
}
|
||||
|
||||
if (sess->key) {
|
||||
pj_ioqueue_unregister(sess->key);
|
||||
sess->key = NULL;
|
||||
sess->sock = PJ_INVALID_SOCKET;
|
||||
} else if (sess->sock && sess->sock != PJ_INVALID_SOCKET) {
|
||||
pj_sock_close(sess->sock);
|
||||
sess->sock = PJ_INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (sess->grp_lock) {
|
||||
pj_grp_lock_dec_ref(sess->grp_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void sess_on_destroy(void *member)
|
||||
{
|
||||
nat_detect_session *sess = (nat_detect_session*)member;
|
||||
if (sess->pool) {
|
||||
pj_pool_release(sess->pool);
|
||||
}
|
||||
}
|
||||
|
||||
static void end_session(nat_detect_session *sess,
|
||||
pj_status_t status,
|
||||
pj_stun_nat_type nat_type)
|
||||
{
|
||||
pj_stun_nat_detect_result result;
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_time_val delay;
|
||||
|
||||
if (sess->timer.id != 0) {
|
||||
pj_timer_heap_cancel(sess->timer_heap, &sess->timer);
|
||||
sess->timer.id = 0;
|
||||
}
|
||||
|
||||
pj_bzero(&result, sizeof(result));
|
||||
errmsg[0] = '\0';
|
||||
result.status_text = errmsg;
|
||||
|
||||
result.status = status;
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
result.nat_type = nat_type;
|
||||
result.nat_type_name = nat_type_names[result.nat_type];
|
||||
|
||||
if (sess->cb)
|
||||
(*sess->cb)(sess->user_data, &result);
|
||||
|
||||
delay.sec = 0;
|
||||
delay.msec = 0;
|
||||
|
||||
sess->timer.id = TIMER_DESTROY;
|
||||
pj_timer_heap_schedule(sess->timer_heap, &sess->timer, &delay);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback upon receiving packet from network.
|
||||
*/
|
||||
static void on_read_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_ssize_t bytes_read)
|
||||
{
|
||||
nat_detect_session *sess;
|
||||
pj_status_t status;
|
||||
|
||||
sess = (nat_detect_session *) pj_ioqueue_get_user_data(key);
|
||||
pj_assert(sess != NULL);
|
||||
|
||||
pj_grp_lock_acquire(sess->grp_lock);
|
||||
|
||||
/* Ignore packet when STUN session has been destroyed */
|
||||
if (!sess->stun_sess)
|
||||
goto on_return;
|
||||
|
||||
if (bytes_read < 0) {
|
||||
if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
|
||||
-bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) &&
|
||||
-bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))
|
||||
{
|
||||
/* Permanent error */
|
||||
end_session(sess, (pj_status_t)-bytes_read,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
} else if (bytes_read > 0) {
|
||||
pj_stun_session_on_rx_pkt(sess->stun_sess, sess->rx_pkt, bytes_read,
|
||||
PJ_STUN_IS_DATAGRAM|PJ_STUN_CHECK_PACKET,
|
||||
NULL, NULL,
|
||||
&sess->src_addr, sess->src_addr_len);
|
||||
}
|
||||
|
||||
|
||||
sess->rx_pkt_len = sizeof(sess->rx_pkt);
|
||||
sess->src_addr_len = sizeof(sess->src_addr);
|
||||
status = pj_ioqueue_recvfrom(key, op_key, sess->rx_pkt, &sess->rx_pkt_len,
|
||||
PJ_IOQUEUE_ALWAYS_ASYNC,
|
||||
&sess->src_addr, &sess->src_addr_len);
|
||||
|
||||
if (status != PJ_EPENDING) {
|
||||
pj_assert(status != PJ_SUCCESS);
|
||||
end_session(sess, status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
}
|
||||
|
||||
on_return:
|
||||
pj_grp_lock_release(sess->grp_lock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback to send outgoing packet from STUN session.
|
||||
*/
|
||||
static pj_status_t on_send_msg(pj_stun_session *stun_sess,
|
||||
void *token,
|
||||
const void *pkt,
|
||||
pj_size_t pkt_size,
|
||||
const pj_sockaddr_t *dst_addr,
|
||||
unsigned addr_len)
|
||||
{
|
||||
nat_detect_session *sess;
|
||||
pj_ssize_t pkt_len;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_UNUSED_ARG(token);
|
||||
|
||||
sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess);
|
||||
|
||||
pkt_len = pkt_size;
|
||||
status = pj_ioqueue_sendto(sess->key, &sess->write_op, pkt, &pkt_len, 0,
|
||||
dst_addr, addr_len);
|
||||
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback upon request completion.
|
||||
*/
|
||||
static void on_request_complete(pj_stun_session *stun_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)
|
||||
{
|
||||
nat_detect_session *sess;
|
||||
pj_stun_sockaddr_attr *mattr = NULL;
|
||||
pj_stun_changed_addr_attr *ca = NULL;
|
||||
pj_uint32_t *tsx_id;
|
||||
int cmp;
|
||||
unsigned test_id;
|
||||
|
||||
PJ_UNUSED_ARG(token);
|
||||
PJ_UNUSED_ARG(tdata);
|
||||
PJ_UNUSED_ARG(src_addr);
|
||||
PJ_UNUSED_ARG(src_addr_len);
|
||||
|
||||
sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess);
|
||||
|
||||
pj_grp_lock_acquire(sess->grp_lock);
|
||||
|
||||
/* Find errors in the response */
|
||||
if (status == PJ_SUCCESS) {
|
||||
|
||||
/* Check error message */
|
||||
if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) {
|
||||
pj_stun_errcode_attr *eattr;
|
||||
int err_code;
|
||||
|
||||
eattr = (pj_stun_errcode_attr*)
|
||||
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0);
|
||||
|
||||
if (eattr != NULL)
|
||||
err_code = eattr->err_code;
|
||||
else
|
||||
err_code = PJ_STUN_SC_SERVER_ERROR;
|
||||
|
||||
status = PJ_STATUS_FROM_STUN_CODE(err_code);
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
/* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */
|
||||
mattr = (pj_stun_sockaddr_attr*)
|
||||
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0);
|
||||
if (mattr == NULL) {
|
||||
mattr = (pj_stun_sockaddr_attr*)
|
||||
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0);
|
||||
}
|
||||
|
||||
if (mattr == NULL) {
|
||||
status = PJNATH_ESTUNNOMAPPEDADDR;
|
||||
}
|
||||
|
||||
/* Get CHANGED-ADDRESS attribute */
|
||||
ca = (pj_stun_changed_addr_attr*)
|
||||
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0);
|
||||
|
||||
if (ca == NULL) {
|
||||
status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the result */
|
||||
tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id;
|
||||
test_id = tsx_id[2];
|
||||
|
||||
if (test_id >= ST_MAX) {
|
||||
PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response",
|
||||
test_id));
|
||||
end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR),
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
PJ_PERROR(5,(sess->pool->obj_name, status, "Completed %s",
|
||||
test_names[test_id]));
|
||||
|
||||
sess->result[test_id].complete = PJ_TRUE;
|
||||
sess->result[test_id].status = status;
|
||||
if (status == PJ_SUCCESS) {
|
||||
pj_sockaddr_cp(&sess->result[test_id].ma, &mattr->sockaddr);
|
||||
pj_sockaddr_cp(&sess->result[test_id].ca, &ca->sockaddr);
|
||||
}
|
||||
|
||||
/* Send Test 1B only when Test 2 completes. Must not send Test 1B
|
||||
* before Test 2 completes to avoid creating mapping on the NAT.
|
||||
*/
|
||||
if (!sess->result[ST_TEST_1B].executed &&
|
||||
sess->result[ST_TEST_2].complete &&
|
||||
sess->result[ST_TEST_2].status != PJ_SUCCESS &&
|
||||
sess->result[ST_TEST_1].complete &&
|
||||
sess->result[ST_TEST_1].status == PJ_SUCCESS)
|
||||
{
|
||||
cmp = pj_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma);
|
||||
if (cmp != 0)
|
||||
send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0);
|
||||
}
|
||||
|
||||
if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess))
|
||||
goto on_return;
|
||||
|
||||
/* Handle the test result according to RFC 3489 page 22:
|
||||
|
||||
|
||||
+--------+
|
||||
| Test |
|
||||
| 1 |
|
||||
+--------+
|
||||
|
|
||||
|
|
||||
V
|
||||
/\ /\
|
||||
N / \ Y / \ Y +--------+
|
||||
UDP <-------/Resp\--------->/ IP \------------->| Test |
|
||||
Blocked \ ? / \Same/ | 2 |
|
||||
\ / \? / +--------+
|
||||
\/ \/ |
|
||||
| N |
|
||||
| V
|
||||
V /\
|
||||
+--------+ Sym. N / \
|
||||
| Test | UDP <---/Resp\
|
||||
| 2 | Firewall \ ? /
|
||||
+--------+ \ /
|
||||
| \/
|
||||
V |Y
|
||||
/\ /\ |
|
||||
Symmetric N / \ +--------+ N / \ V
|
||||
NAT <--- / IP \<-----| Test |<--- /Resp\ Open
|
||||
\Same/ | 1B | \ ? / Internet
|
||||
\? / +--------+ \ /
|
||||
\/ \/
|
||||
| |Y
|
||||
| |
|
||||
| V
|
||||
| Full
|
||||
| Cone
|
||||
V /\
|
||||
+--------+ / \ Y
|
||||
| Test |------>/Resp\---->Restricted
|
||||
| 3 | \ ? /
|
||||
+--------+ \ /
|
||||
\/
|
||||
|N
|
||||
| Port
|
||||
+------>Restricted
|
||||
|
||||
Figure 2: Flow for type discovery process
|
||||
*/
|
||||
|
||||
switch (sess->result[ST_TEST_1].status) {
|
||||
case PJNATH_ESTUNTIMEDOUT:
|
||||
/*
|
||||
* Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED);
|
||||
break;
|
||||
case PJ_SUCCESS:
|
||||
/*
|
||||
* Test 1 is successful. Further tests are needed to detect
|
||||
* NAT type. Compare the MAPPED-ADDRESS with the local address.
|
||||
*/
|
||||
cmp = pj_sockaddr_cmp(&sess->local_addr, &sess->result[ST_TEST_1].ma);
|
||||
if (cmp==0) {
|
||||
/*
|
||||
* MAPPED-ADDRESS and local address is equal. Need one more
|
||||
* test to determine NAT type.
|
||||
*/
|
||||
switch (sess->result[ST_TEST_2].status) {
|
||||
case PJ_SUCCESS:
|
||||
/*
|
||||
* Test 2 is also successful. We're in the open.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN);
|
||||
break;
|
||||
case PJNATH_ESTUNTIMEDOUT:
|
||||
/*
|
||||
* Test 2 has timed out. We're behind somekind of UDP
|
||||
* firewall.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* We've got other error with Test 2.
|
||||
*/
|
||||
end_session(sess, sess->result[ST_TEST_2].status,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* MAPPED-ADDRESS is different than local address.
|
||||
* We're behind NAT.
|
||||
*/
|
||||
switch (sess->result[ST_TEST_2].status) {
|
||||
case PJ_SUCCESS:
|
||||
/*
|
||||
* Test 2 is successful. We're behind a full-cone NAT.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE);
|
||||
break;
|
||||
case PJNATH_ESTUNTIMEDOUT:
|
||||
/*
|
||||
* Test 2 has timed-out Check result of test 1B..
|
||||
*/
|
||||
switch (sess->result[ST_TEST_1B].status) {
|
||||
case PJ_SUCCESS:
|
||||
/*
|
||||
* Compare the MAPPED-ADDRESS of test 1B with the
|
||||
* MAPPED-ADDRESS returned in test 1..
|
||||
*/
|
||||
cmp = pj_sockaddr_cmp(&sess->result[ST_TEST_1].ma,
|
||||
&sess->result[ST_TEST_1B].ma);
|
||||
if (cmp != 0) {
|
||||
/*
|
||||
* MAPPED-ADDRESS is different, we're behind a
|
||||
* symmetric NAT.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS,
|
||||
PJ_STUN_NAT_TYPE_SYMMETRIC);
|
||||
} else {
|
||||
/*
|
||||
* MAPPED-ADDRESS is equal. We're behind a restricted
|
||||
* or port-restricted NAT, depending on the result of
|
||||
* test 3.
|
||||
*/
|
||||
switch (sess->result[ST_TEST_3].status) {
|
||||
case PJ_SUCCESS:
|
||||
/*
|
||||
* Test 3 is successful, we're behind a restricted
|
||||
* NAT.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS,
|
||||
PJ_STUN_NAT_TYPE_RESTRICTED);
|
||||
break;
|
||||
case PJNATH_ESTUNTIMEDOUT:
|
||||
/*
|
||||
* Test 3 failed, we're behind a port restricted
|
||||
* NAT.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS,
|
||||
PJ_STUN_NAT_TYPE_PORT_RESTRICTED);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Got other error with test 3.
|
||||
*/
|
||||
end_session(sess, sess->result[ST_TEST_3].status,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PJNATH_ESTUNTIMEDOUT:
|
||||
/*
|
||||
* Strangely test 1B has failed. Maybe connectivity was
|
||||
* lost? Or perhaps port 3489 (the usual port number in
|
||||
* CHANGED-ADDRESS) is blocked?
|
||||
*/
|
||||
switch (sess->result[ST_TEST_3].status) {
|
||||
case PJ_SUCCESS:
|
||||
/* Although test 1B failed, test 3 was successful.
|
||||
* It could be that port 3489 is blocked, while the
|
||||
* NAT itself looks to be a Restricted one.
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS,
|
||||
PJ_STUN_NAT_TYPE_RESTRICTED);
|
||||
break;
|
||||
default:
|
||||
/* Can't distinguish between Symmetric and Port
|
||||
* Restricted, so set the type to Unknown
|
||||
*/
|
||||
end_session(sess, PJ_SUCCESS,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Got other error with test 1B.
|
||||
*/
|
||||
end_session(sess, sess->result[ST_TEST_1B].status,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* We've got other error with Test 2.
|
||||
*/
|
||||
end_session(sess, sess->result[ST_TEST_2].status,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* We've got other error with Test 1.
|
||||
*/
|
||||
end_session(sess, sess->result[ST_TEST_1].status,
|
||||
PJ_STUN_NAT_TYPE_ERR_UNKNOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
on_return:
|
||||
pj_grp_lock_release(sess->grp_lock);
|
||||
}
|
||||
|
||||
|
||||
/* Perform test */
|
||||
static pj_status_t send_test(nat_detect_session *sess,
|
||||
enum test_type test_id,
|
||||
const pj_sockaddr *alt_addr,
|
||||
pj_uint32_t change_flag)
|
||||
{
|
||||
pj_uint32_t magic, tsx_id[3];
|
||||
char addr[PJ_INET6_ADDRSTRLEN];
|
||||
pj_status_t status;
|
||||
|
||||
sess->result[test_id].executed = PJ_TRUE;
|
||||
|
||||
/* Randomize tsx id */
|
||||
do {
|
||||
magic = pj_rand();
|
||||
} while (magic == PJ_STUN_MAGIC);
|
||||
|
||||
tsx_id[0] = pj_rand();
|
||||
tsx_id[1] = pj_rand();
|
||||
tsx_id[2] = test_id;
|
||||
|
||||
/* Create BIND request */
|
||||
status = pj_stun_session_create_req(sess->stun_sess,
|
||||
PJ_STUN_BINDING_REQUEST, magic,
|
||||
(pj_uint8_t*)tsx_id,
|
||||
&sess->result[test_id].tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Add CHANGE-REQUEST attribute */
|
||||
status = pj_stun_msg_add_uint_attr(sess->pool,
|
||||
sess->result[test_id].tdata->msg,
|
||||
PJ_STUN_ATTR_CHANGE_REQUEST,
|
||||
change_flag);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Configure alternate address, synthesize it if necessary */
|
||||
if (alt_addr) {
|
||||
status = pj_sockaddr_synthesize(sess->server.addr.sa_family,
|
||||
&sess->cur_addr, alt_addr);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
sess->cur_server = &sess->cur_addr;
|
||||
} else {
|
||||
sess->cur_server = &sess->server;
|
||||
}
|
||||
|
||||
PJ_LOG(5,(sess->pool->obj_name,
|
||||
"Performing %s to %s:%d",
|
||||
test_names[test_id],
|
||||
pj_sockaddr_print(sess->cur_server, addr, sizeof(addr), 2),
|
||||
pj_sockaddr_get_port(sess->cur_server)));
|
||||
|
||||
/* Send the request */
|
||||
status = pj_stun_session_send_msg(sess->stun_sess, NULL, PJ_TRUE,
|
||||
PJ_TRUE, sess->cur_server,
|
||||
pj_sockaddr_get_len(sess->cur_server),
|
||||
sess->result[test_id].tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
|
||||
on_error:
|
||||
sess->result[test_id].complete = PJ_TRUE;
|
||||
sess->result[test_id].status = status;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/* Timer callback */
|
||||
static void on_sess_timer(pj_timer_heap_t *th,
|
||||
pj_timer_entry *te)
|
||||
{
|
||||
nat_detect_session *sess;
|
||||
|
||||
sess = (nat_detect_session*) te->user_data;
|
||||
|
||||
if (te->id == TIMER_DESTROY) {
|
||||
pj_grp_lock_acquire(sess->grp_lock);
|
||||
pj_ioqueue_unregister(sess->key);
|
||||
sess->key = NULL;
|
||||
sess->sock = PJ_INVALID_SOCKET;
|
||||
te->id = 0;
|
||||
pj_grp_lock_release(sess->grp_lock);
|
||||
|
||||
sess_destroy(sess);
|
||||
|
||||
} else if (te->id == TIMER_TEST) {
|
||||
|
||||
pj_bool_t next_timer;
|
||||
|
||||
pj_grp_lock_acquire(sess->grp_lock);
|
||||
|
||||
next_timer = PJ_FALSE;
|
||||
|
||||
if (sess->timer_executed == 0) {
|
||||
send_test(sess, ST_TEST_1, NULL, 0);
|
||||
next_timer = PJ_TRUE;
|
||||
} else if (sess->timer_executed == 1) {
|
||||
send_test(sess, ST_TEST_2, NULL, CHANGE_IP_PORT_FLAG);
|
||||
next_timer = PJ_TRUE;
|
||||
} else if (sess->timer_executed == 2) {
|
||||
send_test(sess, ST_TEST_3, NULL, CHANGE_PORT_FLAG);
|
||||
} else {
|
||||
pj_assert(!"Shouldn't have timer at this state");
|
||||
}
|
||||
|
||||
++sess->timer_executed;
|
||||
|
||||
if (next_timer) {
|
||||
pj_time_val delay = {0, TEST_INTERVAL};
|
||||
pj_timer_heap_schedule(th, te, &delay);
|
||||
} else {
|
||||
te->id = 0;
|
||||
}
|
||||
|
||||
pj_grp_lock_release(sess->grp_lock);
|
||||
|
||||
} else {
|
||||
pj_assert(!"Invalid timer ID");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjnath/stun_auth.h>
|
||||
#include <pjnath/errno.h>
|
||||
#include <pjlib-util/hmac_sha1.h>
|
||||
#include <pjlib-util/md5.h>
|
||||
#include <pjlib-util/sha1.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
#define THIS_FILE "stun_auth.c"
|
||||
|
||||
/* Duplicate credential */
|
||||
PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool,
|
||||
pj_stun_auth_cred *dst,
|
||||
const pj_stun_auth_cred *src)
|
||||
{
|
||||
dst->type = src->type;
|
||||
|
||||
switch (src->type) {
|
||||
case PJ_STUN_AUTH_CRED_STATIC:
|
||||
pj_strdup(pool, &dst->data.static_cred.realm,
|
||||
&src->data.static_cred.realm);
|
||||
pj_strdup(pool, &dst->data.static_cred.username,
|
||||
&src->data.static_cred.username);
|
||||
dst->data.static_cred.data_type = src->data.static_cred.data_type;
|
||||
pj_strdup(pool, &dst->data.static_cred.data,
|
||||
&src->data.static_cred.data);
|
||||
pj_strdup(pool, &dst->data.static_cred.nonce,
|
||||
&src->data.static_cred.nonce);
|
||||
break;
|
||||
case PJ_STUN_AUTH_CRED_DYNAMIC:
|
||||
pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred,
|
||||
sizeof(src->data.dyn_cred));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Duplicate request credential.
|
||||
*/
|
||||
PJ_DEF(void) pj_stun_req_cred_info_dup( pj_pool_t *pool,
|
||||
pj_stun_req_cred_info *dst,
|
||||
const pj_stun_req_cred_info *src)
|
||||
{
|
||||
pj_strdup(pool, &dst->realm, &src->realm);
|
||||
pj_strdup(pool, &dst->username, &src->username);
|
||||
pj_strdup(pool, &dst->nonce, &src->nonce);
|
||||
pj_strdup(pool, &dst->auth_key, &src->auth_key);
|
||||
}
|
||||
|
||||
|
||||
/* Calculate HMAC-SHA1 key for long term credential, by getting
|
||||
* MD5 digest of username, realm, and password.
|
||||
*/
|
||||
static void calc_md5_key(pj_uint8_t digest[16],
|
||||
const pj_str_t *realm,
|
||||
const pj_str_t *username,
|
||||
const pj_str_t *passwd)
|
||||
{
|
||||
/* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
|
||||
* the MD5 hash of the result of concatenating the following five
|
||||
* fields: (1) The username, with any quotes and trailing nulls
|
||||
* removed, (2) A single colon, (3) The realm, with any quotes and
|
||||
* trailing nulls removed, (4) A single colon, and (5) The
|
||||
* password, with any trailing nulls removed.
|
||||
*/
|
||||
pj_md5_context ctx;
|
||||
pj_str_t s;
|
||||
|
||||
pj_md5_init(&ctx);
|
||||
|
||||
#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \
|
||||
s.ptr++, s.slen--; \
|
||||
if (s.slen && s.ptr[s.slen-1]=='"') \
|
||||
s.slen--;
|
||||
|
||||
/* Add username */
|
||||
s = *username;
|
||||
REMOVE_QUOTE(s);
|
||||
pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, (unsigned)s.slen);
|
||||
|
||||
/* Add single colon */
|
||||
pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
|
||||
|
||||
/* Add realm */
|
||||
s = *realm;
|
||||
REMOVE_QUOTE(s);
|
||||
pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, (unsigned)s.slen);
|
||||
|
||||
#undef REMOVE_QUOTE
|
||||
|
||||
/* Another colon */
|
||||
pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
|
||||
|
||||
/* Add password */
|
||||
pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, (unsigned)passwd->slen);
|
||||
|
||||
/* Done */
|
||||
pj_md5_final(&ctx, digest);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create authentication key to be used for encoding the message with
|
||||
* MESSAGE-INTEGRITY.
|
||||
*/
|
||||
PJ_DEF(void) pj_stun_create_key(pj_pool_t *pool,
|
||||
pj_str_t *key,
|
||||
const pj_str_t *realm,
|
||||
const pj_str_t *username,
|
||||
pj_stun_passwd_type data_type,
|
||||
const pj_str_t *data)
|
||||
{
|
||||
PJ_ASSERT_ON_FAIL(pool && key && username && data, return);
|
||||
|
||||
if (realm && realm->slen) {
|
||||
if (data_type == PJ_STUN_PASSWD_PLAIN) {
|
||||
key->ptr = (char*) pj_pool_alloc(pool, 16);
|
||||
calc_md5_key((pj_uint8_t*)key->ptr, realm, username, data);
|
||||
key->slen = 16;
|
||||
} else {
|
||||
pj_strdup(pool, key, data);
|
||||
}
|
||||
} else {
|
||||
pj_assert(data_type == PJ_STUN_PASSWD_PLAIN);
|
||||
pj_strdup(pool, key, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*unused PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
|
||||
{
|
||||
return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
|
||||
}*/
|
||||
|
||||
|
||||
PJ_INLINE(void) PUT_VAL16(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
|
||||
{
|
||||
buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
|
||||
buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
|
||||
}
|
||||
|
||||
|
||||
/* Send 401 response */
|
||||
static pj_status_t create_challenge(pj_pool_t *pool,
|
||||
const pj_stun_msg *msg,
|
||||
int err_code,
|
||||
const char *errstr,
|
||||
const pj_str_t *realm,
|
||||
const pj_str_t *nonce,
|
||||
pj_stun_msg **p_response)
|
||||
{
|
||||
pj_stun_msg *response;
|
||||
pj_str_t tmp_nonce;
|
||||
pj_str_t err_msg;
|
||||
pj_status_t rc;
|
||||
|
||||
rc = pj_stun_msg_create_response(pool, msg, err_code,
|
||||
(errstr?pj_cstr(&err_msg, errstr):NULL),
|
||||
&response);
|
||||
if (rc != PJ_SUCCESS)
|
||||
return rc;
|
||||
|
||||
/* SHOULD NOT add REALM, NONCE, USERNAME, and M-I on 400 response */
|
||||
if (err_code!=400 && realm && realm->slen) {
|
||||
rc = pj_stun_msg_add_string_attr(pool, response,
|
||||
PJ_STUN_ATTR_REALM,
|
||||
realm);
|
||||
if (rc != PJ_SUCCESS)
|
||||
return rc;
|
||||
|
||||
/* long term must include nonce */
|
||||
if (!nonce || nonce->slen == 0) {
|
||||
tmp_nonce = pj_str("pjstun");
|
||||
nonce = &tmp_nonce;
|
||||
}
|
||||
}
|
||||
|
||||
if (err_code!=400 && nonce && nonce->slen) {
|
||||
rc = pj_stun_msg_add_string_attr(pool, response,
|
||||
PJ_STUN_ATTR_NONCE,
|
||||
nonce);
|
||||
if (rc != PJ_SUCCESS)
|
||||
return rc;
|
||||
}
|
||||
|
||||
*p_response = response;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/* Verify credential in the request */
|
||||
PJ_DEF(pj_status_t) pj_stun_authenticate_request(const pj_uint8_t *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_stun_msg *msg,
|
||||
pj_stun_auth_cred *cred,
|
||||
pj_pool_t *pool,
|
||||
pj_stun_req_cred_info *p_info,
|
||||
pj_stun_msg **p_response)
|
||||
{
|
||||
pj_stun_req_cred_info tmp_info;
|
||||
const pj_stun_msgint_attr *amsgi;
|
||||
unsigned i, amsgi_pos;
|
||||
pj_bool_t has_attr_beyond_mi;
|
||||
const pj_stun_username_attr *auser;
|
||||
const pj_stun_realm_attr *arealm;
|
||||
const pj_stun_realm_attr *anonce;
|
||||
pj_hmac_sha1_context ctx;
|
||||
pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
|
||||
pj_stun_status err_code;
|
||||
const char *err_text = NULL;
|
||||
pj_status_t status;
|
||||
|
||||
/* msg and credential MUST be specified */
|
||||
PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL);
|
||||
|
||||
/* If p_response is specified, pool MUST be specified. */
|
||||
PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
|
||||
|
||||
if (p_response)
|
||||
*p_response = NULL;
|
||||
|
||||
if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
|
||||
p_response = NULL;
|
||||
|
||||
if (p_info == NULL)
|
||||
p_info = &tmp_info;
|
||||
|
||||
pj_bzero(p_info, sizeof(pj_stun_req_cred_info));
|
||||
|
||||
/* Get realm and nonce from credential */
|
||||
p_info->realm.slen = p_info->nonce.slen = 0;
|
||||
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
|
||||
p_info->realm = cred->data.static_cred.realm;
|
||||
p_info->nonce = cred->data.static_cred.nonce;
|
||||
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
|
||||
status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data,
|
||||
pool, &p_info->realm,
|
||||
&p_info->nonce);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
} else {
|
||||
pj_assert(!"Invalid credential type");
|
||||
return PJ_EBUG;
|
||||
}
|
||||
|
||||
/* Look for MESSAGE-INTEGRITY while counting the position */
|
||||
amsgi_pos = 0;
|
||||
has_attr_beyond_mi = PJ_FALSE;
|
||||
amsgi = NULL;
|
||||
for (i=0; i<msg->attr_count; ++i) {
|
||||
if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
|
||||
amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
|
||||
} else if (amsgi) {
|
||||
has_attr_beyond_mi = PJ_TRUE;
|
||||
break;
|
||||
} else {
|
||||
amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (amsgi == NULL) {
|
||||
/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
|
||||
for short term, and 401 for long term.
|
||||
The rule has been changed from rfc3489bis-06
|
||||
*/
|
||||
err_code = p_info->realm.slen ? PJ_STUN_SC_UNAUTHORIZED :
|
||||
PJ_STUN_SC_BAD_REQUEST;
|
||||
goto on_auth_failed;
|
||||
}
|
||||
|
||||
/* Next check that USERNAME is present */
|
||||
auser = (const pj_stun_username_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
|
||||
if (auser == NULL) {
|
||||
/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should return 400
|
||||
for both short and long term, since M-I is present.
|
||||
The rule has been changed from rfc3489bis-06
|
||||
*/
|
||||
err_code = PJ_STUN_SC_BAD_REQUEST;
|
||||
err_text = "Missing USERNAME";
|
||||
goto on_auth_failed;
|
||||
}
|
||||
|
||||
/* Get REALM, if any */
|
||||
arealm = (const pj_stun_realm_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
|
||||
|
||||
/* Reject with 400 if we have long term credential and the request
|
||||
* is missing REALM attribute.
|
||||
*/
|
||||
if (p_info->realm.slen && arealm==NULL) {
|
||||
err_code = PJ_STUN_SC_BAD_REQUEST;
|
||||
err_text = "Missing REALM";
|
||||
goto on_auth_failed;
|
||||
}
|
||||
|
||||
/* Check if username match */
|
||||
if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
|
||||
pj_bool_t username_ok;
|
||||
username_ok = !pj_strcmp(&auser->value,
|
||||
&cred->data.static_cred.username);
|
||||
if (username_ok) {
|
||||
pj_strdup(pool, &p_info->username,
|
||||
&cred->data.static_cred.username);
|
||||
pj_stun_create_key(pool, &p_info->auth_key, &p_info->realm,
|
||||
&auser->value, cred->data.static_cred.data_type,
|
||||
&cred->data.static_cred.data);
|
||||
} else {
|
||||
/* Username mismatch */
|
||||
/* According to rfc3489bis-10 Sec 10.1.2/10.2.2, we should
|
||||
* return 401
|
||||
*/
|
||||
err_code = PJ_STUN_SC_UNAUTHORIZED;
|
||||
goto on_auth_failed;
|
||||
}
|
||||
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
|
||||
pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
|
||||
pj_str_t password;
|
||||
pj_status_t rc;
|
||||
|
||||
rc = cred->data.dyn_cred.get_password(msg,
|
||||
cred->data.dyn_cred.user_data,
|
||||
(arealm?&arealm->value:NULL),
|
||||
&auser->value, pool,
|
||||
&data_type, &password);
|
||||
if (rc == PJ_SUCCESS) {
|
||||
pj_strdup(pool, &p_info->username, &auser->value);
|
||||
pj_stun_create_key(pool, &p_info->auth_key,
|
||||
(arealm?&arealm->value:NULL), &auser->value,
|
||||
data_type, &password);
|
||||
} else {
|
||||
err_code = PJ_STUN_SC_UNAUTHORIZED;
|
||||
goto on_auth_failed;
|
||||
}
|
||||
} else {
|
||||
pj_assert(!"Invalid credential type");
|
||||
return PJ_EBUG;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Get NONCE attribute */
|
||||
anonce = (pj_stun_nonce_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
|
||||
|
||||
/* Check for long term/short term requirements. */
|
||||
if (p_info->realm.slen != 0 && arealm == NULL) {
|
||||
/* Long term credential is required and REALM is not present */
|
||||
err_code = PJ_STUN_SC_BAD_REQUEST;
|
||||
err_text = "Missing REALM";
|
||||
goto on_auth_failed;
|
||||
|
||||
} else if (p_info->realm.slen != 0 && arealm != NULL) {
|
||||
/* We want long term, and REALM is present */
|
||||
|
||||
/* NONCE must be present. */
|
||||
if (anonce == NULL && p_info->nonce.slen) {
|
||||
err_code = PJ_STUN_SC_BAD_REQUEST;
|
||||
err_text = "Missing NONCE";
|
||||
goto on_auth_failed;
|
||||
}
|
||||
|
||||
/* Verify REALM matches */
|
||||
if (pj_stricmp(&arealm->value, &p_info->realm)) {
|
||||
/* REALM doesn't match */
|
||||
err_code = PJ_STUN_SC_UNAUTHORIZED;
|
||||
err_text = "Invalid REALM";
|
||||
goto on_auth_failed;
|
||||
}
|
||||
|
||||
/* Valid case, will validate the message integrity later */
|
||||
|
||||
} else if (p_info->realm.slen == 0 && arealm != NULL) {
|
||||
/* We want to use short term credential, but client uses long
|
||||
* term credential. The draft doesn't mention anything about
|
||||
* switching between long term and short term.
|
||||
*/
|
||||
|
||||
/* For now just accept the credential, anyway it will probably
|
||||
* cause wrong message integrity value later.
|
||||
*/
|
||||
} else if (p_info->realm.slen==0 && arealm == NULL) {
|
||||
/* Short term authentication is wanted, and one is supplied */
|
||||
|
||||
/* Application MAY request NONCE to be supplied */
|
||||
if (p_info->nonce.slen != 0) {
|
||||
err_code = PJ_STUN_SC_UNAUTHORIZED;
|
||||
err_text = "NONCE required";
|
||||
goto on_auth_failed;
|
||||
}
|
||||
}
|
||||
|
||||
/* If NONCE is present, validate it */
|
||||
if (anonce) {
|
||||
pj_bool_t ok;
|
||||
|
||||
if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC &&
|
||||
cred->data.dyn_cred.verify_nonce != NULL)
|
||||
{
|
||||
ok=cred->data.dyn_cred.verify_nonce(msg,
|
||||
cred->data.dyn_cred.user_data,
|
||||
(arealm?&arealm->value:NULL),
|
||||
&auser->value,
|
||||
&anonce->value);
|
||||
} else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
|
||||
ok = PJ_TRUE;
|
||||
} else {
|
||||
if (p_info->nonce.slen) {
|
||||
ok = !pj_strcmp(&anonce->value, &p_info->nonce);
|
||||
} else {
|
||||
ok = PJ_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
err_code = PJ_STUN_SC_STALE_NONCE;
|
||||
goto on_auth_failed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now calculate HMAC of the message. */
|
||||
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)p_info->auth_key.ptr,
|
||||
(unsigned)p_info->auth_key.slen);
|
||||
|
||||
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
|
||||
/* Pre rfc3489bis-06 style of calculation */
|
||||
pj_hmac_sha1_update(&ctx, pkt, 20);
|
||||
#else
|
||||
/* First calculate HMAC for the header.
|
||||
* The calculation is different depending on whether FINGERPRINT attribute
|
||||
* is present in the message.
|
||||
*/
|
||||
if (has_attr_beyond_mi) {
|
||||
pj_uint8_t hdr_copy[20];
|
||||
pj_memcpy(hdr_copy, pkt, 20);
|
||||
PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos + 24));
|
||||
pj_hmac_sha1_update(&ctx, hdr_copy, 20);
|
||||
} else {
|
||||
pj_hmac_sha1_update(&ctx, pkt, 20);
|
||||
}
|
||||
#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
|
||||
|
||||
/* Now update with the message body */
|
||||
pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
|
||||
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
|
||||
// This is no longer necessary as per rfc3489bis-08
|
||||
if ((amsgi_pos+20) & 0x3F) {
|
||||
pj_uint8_t zeroes[64];
|
||||
pj_bzero(zeroes, sizeof(zeroes));
|
||||
pj_hmac_sha1_update(&ctx, zeroes, 64-((amsgi_pos+20) & 0x3F));
|
||||
}
|
||||
#endif
|
||||
pj_hmac_sha1_final(&ctx, digest);
|
||||
|
||||
|
||||
/* Compare HMACs */
|
||||
if (pj_memcmp(amsgi->hmac, digest, 20)) {
|
||||
/* HMAC value mismatch */
|
||||
/* According to rfc3489bis-10 Sec 10.1.2 we should return 401 */
|
||||
err_code = PJ_STUN_SC_UNAUTHORIZED;
|
||||
err_text = "MESSAGE-INTEGRITY mismatch";
|
||||
goto on_auth_failed;
|
||||
}
|
||||
|
||||
/* Everything looks okay! */
|
||||
return PJ_SUCCESS;
|
||||
|
||||
on_auth_failed:
|
||||
if (p_response) {
|
||||
create_challenge(pool, msg, err_code, err_text,
|
||||
&p_info->realm, &p_info->nonce, p_response);
|
||||
}
|
||||
return PJ_STATUS_FROM_STUN_CODE(err_code);
|
||||
}
|
||||
|
||||
|
||||
/* Determine if STUN message can be authenticated */
|
||||
PJ_DEF(pj_bool_t) pj_stun_auth_valid_for_msg(const pj_stun_msg *msg)
|
||||
{
|
||||
unsigned msg_type = msg->hdr.type;
|
||||
const pj_stun_errcode_attr *err_attr;
|
||||
|
||||
/* STUN requests and success response can be authenticated */
|
||||
if (!PJ_STUN_IS_ERROR_RESPONSE(msg_type) &&
|
||||
!PJ_STUN_IS_INDICATION(msg_type))
|
||||
{
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
/* STUN Indication cannot be authenticated */
|
||||
if (PJ_STUN_IS_INDICATION(msg_type))
|
||||
return PJ_FALSE;
|
||||
|
||||
/* Authentication for STUN error responses depend on the error
|
||||
* code.
|
||||
*/
|
||||
err_attr = (const pj_stun_errcode_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
|
||||
if (err_attr == NULL) {
|
||||
PJ_LOG(4,(THIS_FILE, "STUN error code attribute not present in "
|
||||
"error response"));
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
switch (err_attr->err_code) {
|
||||
case PJ_STUN_SC_BAD_REQUEST: /* 400 (Bad Request) */
|
||||
case PJ_STUN_SC_UNAUTHORIZED: /* 401 (Unauthorized) */
|
||||
case PJ_STUN_SC_STALE_NONCE: /* 438 (Stale Nonce) */
|
||||
|
||||
/* Due to the way this response is generated here, we can't really
|
||||
* authenticate 420 (Unknown Attribute) response */
|
||||
case PJ_STUN_SC_UNKNOWN_ATTRIBUTE:
|
||||
return PJ_FALSE;
|
||||
default:
|
||||
return PJ_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Authenticate MESSAGE-INTEGRITY in the response */
|
||||
PJ_DEF(pj_status_t) pj_stun_authenticate_response(const pj_uint8_t *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_stun_msg *msg,
|
||||
const pj_str_t *key)
|
||||
{
|
||||
const pj_stun_msgint_attr *amsgi;
|
||||
unsigned i, amsgi_pos;
|
||||
pj_bool_t has_attr_beyond_mi;
|
||||
pj_hmac_sha1_context ctx;
|
||||
pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
|
||||
|
||||
PJ_ASSERT_RETURN(pkt && pkt_len && msg && key, PJ_EINVAL);
|
||||
|
||||
/* First check that MESSAGE-INTEGRITY is present */
|
||||
amsgi = (const pj_stun_msgint_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
|
||||
if (amsgi == NULL) {
|
||||
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
|
||||
/* Check that message length is valid */
|
||||
if (msg->hdr.length < 24) {
|
||||
return PJNATH_EINSTUNMSGLEN;
|
||||
}
|
||||
|
||||
/* Look for MESSAGE-INTEGRITY while counting the position */
|
||||
amsgi_pos = 0;
|
||||
has_attr_beyond_mi = PJ_FALSE;
|
||||
amsgi = NULL;
|
||||
for (i=0; i<msg->attr_count; ++i) {
|
||||
if (msg->attr[i]->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
|
||||
amsgi = (const pj_stun_msgint_attr*) msg->attr[i];
|
||||
} else if (amsgi) {
|
||||
has_attr_beyond_mi = PJ_TRUE;
|
||||
break;
|
||||
} else {
|
||||
amsgi_pos += ((msg->attr[i]->length+3) & ~0x03) + 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (amsgi == NULL) {
|
||||
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST);
|
||||
}
|
||||
|
||||
/* Now calculate HMAC of the message. */
|
||||
pj_hmac_sha1_init(&ctx, (pj_uint8_t*)key->ptr, (unsigned)key->slen);
|
||||
|
||||
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
|
||||
/* Pre rfc3489bis-06 style of calculation */
|
||||
pj_hmac_sha1_update(&ctx, pkt, 20);
|
||||
#else
|
||||
/* First calculate HMAC for the header.
|
||||
* The calculation is different depending on whether FINGERPRINT attribute
|
||||
* is present in the message.
|
||||
*/
|
||||
if (has_attr_beyond_mi) {
|
||||
pj_uint8_t hdr_copy[20];
|
||||
pj_memcpy(hdr_copy, pkt, 20);
|
||||
PUT_VAL16(hdr_copy, 2, (pj_uint16_t)(amsgi_pos+24));
|
||||
pj_hmac_sha1_update(&ctx, hdr_copy, 20);
|
||||
} else {
|
||||
pj_hmac_sha1_update(&ctx, pkt, 20);
|
||||
}
|
||||
#endif /* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
|
||||
|
||||
/* Now update with the message body */
|
||||
pj_hmac_sha1_update(&ctx, pkt+20, amsgi_pos);
|
||||
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
|
||||
// This is no longer necessary as per rfc3489bis-08
|
||||
if ((amsgi_pos+20) & 0x3F) {
|
||||
pj_uint8_t zeroes[64];
|
||||
pj_bzero(zeroes, sizeof(zeroes));
|
||||
pj_hmac_sha1_update(&ctx, zeroes, 64-((amsgi_pos+20) & 0x3F));
|
||||
}
|
||||
#endif
|
||||
pj_hmac_sha1_final(&ctx, digest);
|
||||
|
||||
/* Compare HMACs */
|
||||
if (pj_memcmp(amsgi->hmac, digest, 20)) {
|
||||
/* HMAC value mismatch */
|
||||
return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
/* Everything looks okay! */
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,300 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjnath/stun_msg.h>
|
||||
#include <pjnath/errno.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/os.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
#if PJ_LOG_MAX_LEVEL > 0
|
||||
|
||||
|
||||
#define APPLY() if (len < 1 || len >= (end-p)) \
|
||||
goto on_return; \
|
||||
p += len
|
||||
|
||||
static int print_binary(char *buffer, unsigned length,
|
||||
const pj_uint8_t *data, unsigned data_len)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
if (length < data_len * 2 + 8)
|
||||
return -1;
|
||||
|
||||
pj_ansi_sprintf(buffer, ", data=");
|
||||
buffer += 7;
|
||||
|
||||
for (i=0; i<data_len; ++i) {
|
||||
pj_ansi_sprintf(buffer, "%02x", (*data) & 0xFF);
|
||||
buffer += 2;
|
||||
data++;
|
||||
}
|
||||
|
||||
pj_ansi_sprintf(buffer, "\n");
|
||||
buffer++;
|
||||
|
||||
return data_len * 2 + 8;
|
||||
}
|
||||
|
||||
static int print_attr(char *buffer, unsigned length,
|
||||
const pj_stun_attr_hdr *ahdr)
|
||||
{
|
||||
char *p = buffer, *end = buffer + length;
|
||||
const char *attr_name = pj_stun_get_attr_name(ahdr->type);
|
||||
char attr_buf[32];
|
||||
int len;
|
||||
|
||||
if (*attr_name == '?') {
|
||||
pj_ansi_snprintf(attr_buf, sizeof(attr_buf), "Attr 0x%x",
|
||||
ahdr->type);
|
||||
attr_name = attr_buf;
|
||||
}
|
||||
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
" %s: length=%d",
|
||||
attr_name,
|
||||
(int)ahdr->length);
|
||||
APPLY();
|
||||
|
||||
|
||||
switch (ahdr->type) {
|
||||
case PJ_STUN_ATTR_MAPPED_ADDR:
|
||||
case PJ_STUN_ATTR_RESPONSE_ADDR:
|
||||
case PJ_STUN_ATTR_SOURCE_ADDR:
|
||||
case PJ_STUN_ATTR_CHANGED_ADDR:
|
||||
case PJ_STUN_ATTR_REFLECTED_FROM:
|
||||
case PJ_STUN_ATTR_XOR_PEER_ADDR:
|
||||
case PJ_STUN_ATTR_XOR_RELAYED_ADDR:
|
||||
case PJ_STUN_ATTR_XOR_MAPPED_ADDR:
|
||||
case PJ_STUN_ATTR_XOR_REFLECTED_FROM:
|
||||
case PJ_STUN_ATTR_ALTERNATE_SERVER:
|
||||
{
|
||||
char addr[PJ_INET6_ADDRSTRLEN];
|
||||
const pj_stun_sockaddr_attr *attr;
|
||||
pj_uint16_t af;
|
||||
|
||||
attr = (const pj_stun_sockaddr_attr*)ahdr;
|
||||
af = attr->sockaddr.addr.sa_family;
|
||||
|
||||
if ((af == pj_AF_INET()) || (af == pj_AF_INET6())) {
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", %s addr=%s\n",
|
||||
(af == pj_AF_INET())?"IPv4":"IPv6",
|
||||
pj_sockaddr_print(&attr->sockaddr,
|
||||
addr, sizeof(addr),3));
|
||||
} else {
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", INVALID ADDRESS FAMILY!\n");
|
||||
}
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_CHANNEL_NUMBER:
|
||||
{
|
||||
const pj_stun_uint_attr *attr;
|
||||
|
||||
attr = (const pj_stun_uint_attr*)ahdr;
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", chnum=%u (0x%x)\n",
|
||||
(int)PJ_STUN_GET_CH_NB(attr->value),
|
||||
(int)PJ_STUN_GET_CH_NB(attr->value));
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_CHANGE_REQUEST:
|
||||
case PJ_STUN_ATTR_LIFETIME:
|
||||
case PJ_STUN_ATTR_BANDWIDTH:
|
||||
case PJ_STUN_ATTR_REQ_ADDR_TYPE:
|
||||
case PJ_STUN_ATTR_EVEN_PORT:
|
||||
case PJ_STUN_ATTR_REQ_TRANSPORT:
|
||||
case PJ_STUN_ATTR_TIMER_VAL:
|
||||
case PJ_STUN_ATTR_PRIORITY:
|
||||
case PJ_STUN_ATTR_FINGERPRINT:
|
||||
case PJ_STUN_ATTR_REFRESH_INTERVAL:
|
||||
case PJ_STUN_ATTR_ICMP:
|
||||
{
|
||||
const pj_stun_uint_attr *attr;
|
||||
|
||||
attr = (const pj_stun_uint_attr*)ahdr;
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", value=%u (0x%x)\n",
|
||||
(pj_uint32_t)attr->value,
|
||||
(pj_uint32_t)attr->value);
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_USERNAME:
|
||||
case PJ_STUN_ATTR_PASSWORD:
|
||||
case PJ_STUN_ATTR_REALM:
|
||||
case PJ_STUN_ATTR_NONCE:
|
||||
case PJ_STUN_ATTR_SOFTWARE:
|
||||
{
|
||||
const pj_stun_string_attr *attr;
|
||||
|
||||
attr = (pj_stun_string_attr*)ahdr;
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", value=\"%.*s\"\n",
|
||||
(int)attr->value.slen,
|
||||
attr->value.ptr);
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_ERROR_CODE:
|
||||
{
|
||||
const pj_stun_errcode_attr *attr;
|
||||
|
||||
attr = (const pj_stun_errcode_attr*) ahdr;
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", err_code=%d, reason=\"%.*s\"\n",
|
||||
attr->err_code,
|
||||
(int)attr->reason.slen,
|
||||
attr->reason.ptr);
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES:
|
||||
{
|
||||
const pj_stun_unknown_attr *attr;
|
||||
unsigned j;
|
||||
|
||||
attr = (const pj_stun_unknown_attr*) ahdr;
|
||||
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
", unknown list:");
|
||||
APPLY();
|
||||
|
||||
for (j=0; j<attr->attr_count; ++j) {
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
" %d",
|
||||
(int)attr->attrs[j]);
|
||||
APPLY();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_MESSAGE_INTEGRITY:
|
||||
{
|
||||
const pj_stun_msgint_attr *attr;
|
||||
|
||||
attr = (const pj_stun_msgint_attr*) ahdr;
|
||||
len = print_binary(p, (unsigned)(end-p), attr->hmac, 20);
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
|
||||
case PJ_STUN_ATTR_DATA:
|
||||
{
|
||||
const pj_stun_binary_attr *attr;
|
||||
|
||||
attr = (const pj_stun_binary_attr*) ahdr;
|
||||
len = print_binary(p, (unsigned)(end-p), attr->data, attr->length);
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
case PJ_STUN_ATTR_ICE_CONTROLLED:
|
||||
case PJ_STUN_ATTR_ICE_CONTROLLING:
|
||||
case PJ_STUN_ATTR_RESERVATION_TOKEN:
|
||||
{
|
||||
const pj_stun_uint64_attr *attr;
|
||||
pj_uint8_t data[8];
|
||||
int i;
|
||||
|
||||
attr = (const pj_stun_uint64_attr*) ahdr;
|
||||
|
||||
for (i=0; i<8; ++i)
|
||||
data[i] = ((const pj_uint8_t*)&attr->value)[7-i];
|
||||
|
||||
len = print_binary(p, (unsigned)(end-p), data, 8);
|
||||
APPLY();
|
||||
}
|
||||
break;
|
||||
case PJ_STUN_ATTR_USE_CANDIDATE:
|
||||
case PJ_STUN_ATTR_DONT_FRAGMENT:
|
||||
default:
|
||||
len = pj_ansi_snprintf(p, end-p, "\n");
|
||||
APPLY();
|
||||
break;
|
||||
}
|
||||
|
||||
return (int)(p-buffer);
|
||||
|
||||
on_return:
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Dump STUN message to a printable string output.
|
||||
*/
|
||||
PJ_DEF(char*) pj_stun_msg_dump(const pj_stun_msg *msg,
|
||||
char *buffer,
|
||||
unsigned length,
|
||||
unsigned *printed_len)
|
||||
{
|
||||
char *p, *end;
|
||||
int len;
|
||||
unsigned i;
|
||||
pj_uint32_t tsx_id[3];
|
||||
|
||||
PJ_ASSERT_RETURN(msg && buffer && length, NULL);
|
||||
|
||||
PJ_CHECK_STACK();
|
||||
|
||||
p = buffer;
|
||||
end = buffer + length;
|
||||
|
||||
len = pj_ansi_snprintf(p, end-p, "STUN %s %s\n",
|
||||
pj_stun_get_method_name(msg->hdr.type),
|
||||
pj_stun_get_class_name(msg->hdr.type));
|
||||
APPLY();
|
||||
|
||||
pj_memcpy(tsx_id, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id));
|
||||
len = pj_ansi_snprintf(p, end-p,
|
||||
" Hdr: length=%d, magic=%08x, tsx_id=%08x%08x%08x\n"
|
||||
" Attributes:\n",
|
||||
msg->hdr.length,
|
||||
msg->hdr.magic,
|
||||
tsx_id[0],
|
||||
tsx_id[1],
|
||||
tsx_id[2]);
|
||||
APPLY();
|
||||
|
||||
for (i=0; i<msg->attr_count; ++i) {
|
||||
len = print_attr(p, (unsigned)(end-p), msg->attr[i]);
|
||||
APPLY();
|
||||
}
|
||||
|
||||
on_return:
|
||||
*p = '\0';
|
||||
if (printed_len)
|
||||
*printed_len = (unsigned)(p-buffer);
|
||||
return buffer;
|
||||
|
||||
#undef APPLY
|
||||
}
|
||||
|
||||
|
||||
#endif /* PJ_LOG_MAX_LEVEL > 0 */
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,488 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjnath/stun_transaction.h>
|
||||
#include <pjnath/errno.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
#include <pj/timer.h>
|
||||
|
||||
|
||||
#define THIS_FILE "stun_transaction.c"
|
||||
#define TIMER_INACTIVE 0
|
||||
#define TIMER_ACTIVE 1
|
||||
|
||||
|
||||
struct pj_stun_client_tsx
|
||||
{
|
||||
char obj_name[PJ_MAX_OBJ_NAME];
|
||||
pj_stun_tsx_cb cb;
|
||||
void *user_data;
|
||||
pj_grp_lock_t *grp_lock;
|
||||
|
||||
pj_bool_t complete;
|
||||
|
||||
pj_bool_t require_retransmit;
|
||||
unsigned rto_msec;
|
||||
pj_timer_entry retransmit_timer;
|
||||
unsigned transmit_count;
|
||||
pj_time_val retransmit_time;
|
||||
pj_timer_heap_t *timer_heap;
|
||||
|
||||
pj_timer_entry destroy_timer;
|
||||
|
||||
void *last_pkt;
|
||||
unsigned last_pkt_size;
|
||||
};
|
||||
|
||||
|
||||
#if 1
|
||||
# define TRACE_(expr) PJ_LOG(5,expr)
|
||||
#else
|
||||
# define TRACE_(expr)
|
||||
#endif
|
||||
|
||||
|
||||
static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
|
||||
pj_timer_entry *timer);
|
||||
static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
|
||||
pj_timer_entry *timer);
|
||||
|
||||
/*
|
||||
* Create a STUN client transaction.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_create(pj_stun_config *cfg,
|
||||
pj_pool_t *pool,
|
||||
pj_grp_lock_t *grp_lock,
|
||||
const pj_stun_tsx_cb *cb,
|
||||
pj_stun_client_tsx **p_tsx)
|
||||
{
|
||||
pj_stun_client_tsx *tsx;
|
||||
|
||||
PJ_ASSERT_RETURN(cfg && cb && p_tsx, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL);
|
||||
|
||||
tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx);
|
||||
tsx->rto_msec = cfg->rto_msec;
|
||||
tsx->timer_heap = cfg->timer_heap;
|
||||
tsx->grp_lock = grp_lock;
|
||||
pj_memcpy(&tsx->cb, cb, sizeof(*cb));
|
||||
|
||||
tsx->retransmit_timer.cb = &retransmit_timer_callback;
|
||||
tsx->retransmit_timer.user_data = tsx;
|
||||
|
||||
tsx->destroy_timer.cb = &destroy_timer_callback;
|
||||
tsx->destroy_timer.user_data = tsx;
|
||||
|
||||
pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), "utsx%p", tsx);
|
||||
|
||||
*p_tsx = tsx;
|
||||
|
||||
PJ_LOG(5,(tsx->obj_name, "STUN client transaction created"));
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_schedule_destroy(
|
||||
pj_stun_client_tsx *tsx,
|
||||
const pj_time_val *delay)
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(tsx && delay, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(tsx->cb.on_destroy, PJ_EINVAL);
|
||||
|
||||
pj_grp_lock_acquire(tsx->grp_lock);
|
||||
|
||||
/* Cancel previously registered timer */
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer,
|
||||
TIMER_INACTIVE);
|
||||
|
||||
/* Stop retransmission, just in case */
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
|
||||
TIMER_INACTIVE);
|
||||
|
||||
status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap,
|
||||
&tsx->destroy_timer, delay,
|
||||
TIMER_ACTIVE, tsx->grp_lock);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_grp_lock_release(tsx->grp_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
tsx->cb.on_complete = NULL;
|
||||
|
||||
pj_grp_lock_release(tsx->grp_lock);
|
||||
|
||||
TRACE_((tsx->obj_name, "STUN transaction %p schedule destroy", tsx));
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx)
|
||||
{
|
||||
/*
|
||||
* Currently tsx has no objects to destroy so we don't need to do anything
|
||||
* here.
|
||||
*/
|
||||
/* pj_stun_client_tsx_stop(tsx); */
|
||||
PJ_UNUSED_ARG(tsx);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy transaction immediately.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_stop(pj_stun_client_tsx *tsx)
|
||||
{
|
||||
PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
|
||||
|
||||
/* Don't call grp_lock_acquire() because we might be called on
|
||||
* group lock's destructor.
|
||||
*/
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
|
||||
TIMER_INACTIVE);
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->destroy_timer,
|
||||
TIMER_INACTIVE);
|
||||
|
||||
PJ_LOG(5,(tsx->obj_name, "STUN client transaction %p stopped, ref_cnt=%d",
|
||||
tsx, pj_grp_lock_get_ref(tsx->grp_lock)));
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check if transaction has completed.
|
||||
*/
|
||||
PJ_DEF(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx)
|
||||
{
|
||||
PJ_ASSERT_RETURN(tsx, PJ_FALSE);
|
||||
return tsx->complete;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set user data.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx,
|
||||
void *data)
|
||||
{
|
||||
PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
|
||||
tsx->user_data = data;
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get the user data
|
||||
*/
|
||||
PJ_DEF(void*) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx)
|
||||
{
|
||||
PJ_ASSERT_RETURN(tsx, NULL);
|
||||
return tsx->user_data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Transmit message.
|
||||
*/
|
||||
static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx,
|
||||
pj_bool_t mod_count)
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(tsx->retransmit_timer.id == TIMER_INACTIVE ||
|
||||
!tsx->require_retransmit || !mod_count, PJ_EBUSY);
|
||||
|
||||
if (tsx->require_retransmit && mod_count) {
|
||||
/* Calculate retransmit/timeout delay */
|
||||
if (tsx->transmit_count == 0) {
|
||||
tsx->retransmit_time.sec = 0;
|
||||
tsx->retransmit_time.msec = tsx->rto_msec;
|
||||
|
||||
} else if (tsx->transmit_count < PJ_STUN_MAX_TRANSMIT_COUNT-1) {
|
||||
unsigned msec;
|
||||
|
||||
msec = PJ_TIME_VAL_MSEC(tsx->retransmit_time);
|
||||
msec <<= 1;
|
||||
tsx->retransmit_time.sec = msec / 1000;
|
||||
tsx->retransmit_time.msec = msec % 1000;
|
||||
|
||||
} else {
|
||||
tsx->retransmit_time.sec = PJ_STUN_TIMEOUT_VALUE / 1000;
|
||||
tsx->retransmit_time.msec = PJ_STUN_TIMEOUT_VALUE % 1000;
|
||||
}
|
||||
|
||||
/* Schedule timer first because when send_msg() failed we can
|
||||
* cancel it (as opposed to when schedule_timer() failed we cannot
|
||||
* cancel transmission).
|
||||
*/;
|
||||
status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap,
|
||||
&tsx->retransmit_timer,
|
||||
&tsx->retransmit_time,
|
||||
TIMER_ACTIVE,
|
||||
tsx->grp_lock);
|
||||
if (status != PJ_SUCCESS) {
|
||||
tsx->retransmit_timer.id = TIMER_INACTIVE;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (mod_count)
|
||||
tsx->transmit_count++;
|
||||
|
||||
PJ_LOG(5,(tsx->obj_name, "STUN sending message (transmit count=%d)",
|
||||
tsx->transmit_count));
|
||||
pj_log_push_indent();
|
||||
|
||||
/* Send message */
|
||||
status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size);
|
||||
if (status == PJ_EPENDING || status == PJ_EBUSY)
|
||||
status = PJ_SUCCESS;
|
||||
|
||||
if (status == PJNATH_ESTUNDESTROYED) {
|
||||
/* We've been destroyed, don't access the object. */
|
||||
} else if (status != PJ_SUCCESS) {
|
||||
if (mod_count || status == PJ_EINVALIDOP) {
|
||||
pj_timer_heap_cancel_if_active( tsx->timer_heap,
|
||||
&tsx->retransmit_timer,
|
||||
TIMER_INACTIVE);
|
||||
}
|
||||
PJ_PERROR(4, (tsx->obj_name, status, "STUN error sending message"));
|
||||
}
|
||||
|
||||
pj_log_pop_indent();
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send outgoing message and start STUN transaction.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx,
|
||||
pj_bool_t retransmit,
|
||||
void *pkt,
|
||||
unsigned pkt_len)
|
||||
{
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY);
|
||||
|
||||
pj_grp_lock_acquire(tsx->grp_lock);
|
||||
|
||||
/* Encode message */
|
||||
tsx->last_pkt = pkt;
|
||||
tsx->last_pkt_size = pkt_len;
|
||||
|
||||
/* Update STUN retransmit flag */
|
||||
tsx->require_retransmit = retransmit;
|
||||
|
||||
/* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE.
|
||||
* Since we don't have timeout timer, simulate this by using
|
||||
* retransmit timer.
|
||||
*/
|
||||
if (!retransmit) {
|
||||
unsigned timeout;
|
||||
|
||||
pj_assert(tsx->retransmit_timer.id == 0);
|
||||
tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT;
|
||||
|
||||
timeout = tsx->rto_msec * 16;
|
||||
tsx->retransmit_time.sec = timeout / 1000;
|
||||
tsx->retransmit_time.msec = timeout % 1000;
|
||||
|
||||
/* Schedule timer first because when send_msg() failed we can
|
||||
* cancel it (as opposed to when schedule_timer() failed we cannot
|
||||
* cancel transmission).
|
||||
*/;
|
||||
status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap,
|
||||
&tsx->retransmit_timer,
|
||||
&tsx->retransmit_time,
|
||||
TIMER_ACTIVE,
|
||||
tsx->grp_lock);
|
||||
if (status != PJ_SUCCESS) {
|
||||
tsx->retransmit_timer.id = TIMER_INACTIVE;
|
||||
pj_grp_lock_release(tsx->grp_lock);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the message */
|
||||
status = tsx_transmit_msg(tsx, PJ_TRUE);
|
||||
if (status != PJ_SUCCESS) {
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap,
|
||||
&tsx->retransmit_timer,
|
||||
TIMER_INACTIVE);
|
||||
pj_grp_lock_release(tsx->grp_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
pj_grp_lock_release(tsx->grp_lock);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/* Retransmit timer callback */
|
||||
static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
|
||||
pj_timer_entry *timer)
|
||||
{
|
||||
pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_UNUSED_ARG(timer_heap);
|
||||
pj_grp_lock_acquire(tsx->grp_lock);
|
||||
|
||||
if (tsx->transmit_count >= PJ_STUN_MAX_TRANSMIT_COUNT) {
|
||||
/* tsx may be destroyed when calling the callback below */
|
||||
pj_grp_lock_t *grp_lock = tsx->grp_lock;
|
||||
|
||||
/* Retransmission count exceeded. Transaction has failed */
|
||||
tsx->retransmit_timer.id = 0;
|
||||
PJ_LOG(4,(tsx->obj_name, "STUN timeout waiting for response"));
|
||||
pj_log_push_indent();
|
||||
if (!tsx->complete) {
|
||||
tsx->complete = PJ_TRUE;
|
||||
if (tsx->cb.on_complete) {
|
||||
tsx->cb.on_complete(tsx, PJNATH_ESTUNTIMEDOUT, NULL, NULL, 0);
|
||||
}
|
||||
}
|
||||
pj_grp_lock_release(grp_lock);
|
||||
/* We might have been destroyed, don't try to access the object */
|
||||
pj_log_pop_indent();
|
||||
return;
|
||||
}
|
||||
|
||||
tsx->retransmit_timer.id = 0;
|
||||
status = tsx_transmit_msg(tsx, PJ_TRUE);
|
||||
if (status != PJ_SUCCESS) {
|
||||
tsx->retransmit_timer.id = 0;
|
||||
if (!tsx->complete) {
|
||||
tsx->complete = PJ_TRUE;
|
||||
if (tsx->cb.on_complete) {
|
||||
tsx->cb.on_complete(tsx, status, NULL, NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pj_grp_lock_release(tsx->grp_lock);
|
||||
/* We might have been destroyed, don't try to access the object */
|
||||
}
|
||||
|
||||
/*
|
||||
* Request to retransmit the request.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_retransmit(pj_stun_client_tsx *tsx,
|
||||
pj_bool_t mod_count)
|
||||
{
|
||||
if (tsx->destroy_timer.id != 0) {
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if (mod_count) {
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
|
||||
TIMER_INACTIVE);
|
||||
}
|
||||
|
||||
return tsx_transmit_msg(tsx, mod_count);
|
||||
}
|
||||
|
||||
/* Timer callback to destroy transaction */
|
||||
static void destroy_timer_callback(pj_timer_heap_t *timer_heap,
|
||||
pj_timer_entry *timer)
|
||||
{
|
||||
pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
|
||||
|
||||
PJ_UNUSED_ARG(timer_heap);
|
||||
|
||||
tsx->destroy_timer.id = PJ_FALSE;
|
||||
|
||||
tsx->cb.on_destroy(tsx);
|
||||
/* Don't access transaction after this */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Notify the STUN transaction about the arrival of STUN response.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
|
||||
const pj_stun_msg *msg,
|
||||
const pj_sockaddr_t *src_addr,
|
||||
unsigned src_addr_len)
|
||||
{
|
||||
pj_stun_errcode_attr *err_attr;
|
||||
pj_status_t status;
|
||||
|
||||
/* Must be STUN response message */
|
||||
if (!PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) &&
|
||||
!PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
|
||||
{
|
||||
PJ_LOG(4,(tsx->obj_name,
|
||||
"STUN rx_msg() error: not response message"));
|
||||
return PJNATH_EINSTUNMSGTYPE;
|
||||
}
|
||||
|
||||
|
||||
/* We have a response with matching transaction ID.
|
||||
* We can cancel retransmit timer now.
|
||||
*/
|
||||
pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer,
|
||||
TIMER_INACTIVE);
|
||||
|
||||
/* Find STUN error code attribute */
|
||||
err_attr = (pj_stun_errcode_attr*)
|
||||
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
|
||||
|
||||
if (err_attr && err_attr->err_code <= 200) {
|
||||
/* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2:
|
||||
* Any response between 100 and 299 MUST result in the cessation
|
||||
* of request retransmissions, but otherwise is discarded.
|
||||
*/
|
||||
PJ_LOG(4,(tsx->obj_name,
|
||||
"STUN rx_msg() error: received provisional %d code (%.*s)",
|
||||
err_attr->err_code,
|
||||
(int)err_attr->reason.slen,
|
||||
err_attr->reason.ptr));
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
if (err_attr == NULL) {
|
||||
status = PJ_SUCCESS;
|
||||
} else {
|
||||
status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code);
|
||||
}
|
||||
|
||||
/* Call callback */
|
||||
if (!tsx->complete) {
|
||||
tsx->complete = PJ_TRUE;
|
||||
if (tsx->cb.on_complete) {
|
||||
tsx->cb.on_complete(tsx, status, msg, src_addr, src_addr_len);
|
||||
}
|
||||
/* We might have been destroyed, don't try to access the object */
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,631 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <pjnath.h>
|
||||
#include <pjlib-util.h>
|
||||
#include <pjlib.h>
|
||||
|
||||
|
||||
#define THIS_FILE "client_main.c"
|
||||
#define LOCAL_PORT 1998
|
||||
#define BANDWIDTH 64 /* -1 to disable */
|
||||
#define LIFETIME 600 /* -1 to disable */
|
||||
#define REQ_TRANSPORT -1 /* 0: udp, 1: tcp, -1: disable */
|
||||
#define REQ_PORT_PROPS -1 /* -1 to disable */
|
||||
#define REQ_IP 0 /* IP address string */
|
||||
|
||||
//#define OPTIONS PJ_STUN_NO_AUTHENTICATE
|
||||
#define OPTIONS 0
|
||||
|
||||
|
||||
struct peer
|
||||
{
|
||||
pj_stun_sock *stun_sock;
|
||||
pj_sockaddr mapped_addr;
|
||||
};
|
||||
|
||||
|
||||
static struct global
|
||||
{
|
||||
pj_caching_pool cp;
|
||||
pj_pool_t *pool;
|
||||
pj_stun_config stun_config;
|
||||
pj_thread_t *thread;
|
||||
pj_bool_t quit;
|
||||
|
||||
pj_dns_resolver *resolver;
|
||||
|
||||
pj_turn_sock *relay;
|
||||
pj_sockaddr relay_addr;
|
||||
|
||||
struct peer peer[2];
|
||||
} g;
|
||||
|
||||
static struct options
|
||||
{
|
||||
pj_bool_t use_tcp;
|
||||
char *srv_addr;
|
||||
char *srv_port;
|
||||
char *realm;
|
||||
char *user_name;
|
||||
char *password;
|
||||
pj_bool_t use_fingerprint;
|
||||
char *stun_server;
|
||||
char *nameserver;
|
||||
} o;
|
||||
|
||||
|
||||
static int worker_thread(void *unused);
|
||||
static void turn_on_rx_data(pj_turn_sock *relay,
|
||||
void *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_sockaddr_t *peer_addr,
|
||||
unsigned addr_len);
|
||||
static void turn_on_state(pj_turn_sock *relay, pj_turn_state_t old_state,
|
||||
pj_turn_state_t new_state);
|
||||
static pj_bool_t stun_sock_on_status(pj_stun_sock *stun_sock,
|
||||
pj_stun_sock_op op,
|
||||
pj_status_t status);
|
||||
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);
|
||||
|
||||
|
||||
static void my_perror(const char *title, pj_status_t status)
|
||||
{
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "%s: %s", title, errmsg));
|
||||
}
|
||||
|
||||
#define CHECK(expr) status=expr; \
|
||||
if (status!=PJ_SUCCESS) { \
|
||||
my_perror(#expr, status); \
|
||||
return status; \
|
||||
}
|
||||
|
||||
static int init()
|
||||
{
|
||||
int i;
|
||||
pj_status_t status;
|
||||
|
||||
CHECK( pj_init() );
|
||||
CHECK( pjlib_util_init() );
|
||||
CHECK( pjnath_init() );
|
||||
|
||||
/* Check that server is specified */
|
||||
if (!o.srv_addr) {
|
||||
printf("Error: server must be specified\n");
|
||||
return PJ_EINVAL;
|
||||
}
|
||||
|
||||
pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0);
|
||||
|
||||
g.pool = pj_pool_create(&g.cp.factory, "main", 1000, 1000, NULL);
|
||||
|
||||
/* Init global STUN config */
|
||||
pj_stun_config_init(&g.stun_config, &g.cp.factory, 0, NULL, NULL);
|
||||
|
||||
/* Create global timer heap */
|
||||
CHECK( pj_timer_heap_create(g.pool, 1000, &g.stun_config.timer_heap) );
|
||||
|
||||
/* Create global ioqueue */
|
||||
CHECK( pj_ioqueue_create(g.pool, 16, &g.stun_config.ioqueue) );
|
||||
|
||||
/*
|
||||
* Create peers
|
||||
*/
|
||||
for (i=0; i<(int)PJ_ARRAY_SIZE(g.peer); ++i) {
|
||||
pj_stun_sock_cb stun_sock_cb;
|
||||
char name[] = "peer0";
|
||||
pj_uint16_t port;
|
||||
pj_stun_sock_cfg ss_cfg;
|
||||
pj_str_t server;
|
||||
|
||||
pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb));
|
||||
stun_sock_cb.on_rx_data = &stun_sock_on_rx_data;
|
||||
stun_sock_cb.on_status = &stun_sock_on_status;
|
||||
|
||||
g.peer[i].mapped_addr.addr.sa_family = pj_AF_INET();
|
||||
|
||||
pj_stun_sock_cfg_default(&ss_cfg);
|
||||
#if 1
|
||||
/* make reading the log easier */
|
||||
ss_cfg.ka_interval = 300;
|
||||
#endif
|
||||
|
||||
name[strlen(name)-1] = '0'+i;
|
||||
status = pj_stun_sock_create(&g.stun_config, name, pj_AF_INET(),
|
||||
&stun_sock_cb, &ss_cfg,
|
||||
&g.peer[i], &g.peer[i].stun_sock);
|
||||
if (status != PJ_SUCCESS) {
|
||||
my_perror("pj_stun_sock_create()", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (o.stun_server) {
|
||||
server = pj_str(o.stun_server);
|
||||
port = PJ_STUN_PORT;
|
||||
} else {
|
||||
server = pj_str(o.srv_addr);
|
||||
port = (pj_uint16_t)(o.srv_port?atoi(o.srv_port):PJ_STUN_PORT);
|
||||
}
|
||||
status = pj_stun_sock_start(g.peer[i].stun_sock, &server,
|
||||
port, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
my_perror("pj_stun_sock_start()", status);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start the worker thread */
|
||||
CHECK( pj_thread_create(g.pool, "stun", &worker_thread, NULL, 0, 0, &g.thread) );
|
||||
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static int client_shutdown()
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
if (g.thread) {
|
||||
g.quit = 1;
|
||||
pj_thread_join(g.thread);
|
||||
pj_thread_destroy(g.thread);
|
||||
g.thread = NULL;
|
||||
}
|
||||
if (g.relay) {
|
||||
pj_turn_sock_destroy(g.relay);
|
||||
g.relay = NULL;
|
||||
}
|
||||
for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
|
||||
if (g.peer[i].stun_sock) {
|
||||
pj_stun_sock_destroy(g.peer[i].stun_sock);
|
||||
g.peer[i].stun_sock = NULL;
|
||||
}
|
||||
}
|
||||
if (g.stun_config.timer_heap) {
|
||||
pj_timer_heap_destroy(g.stun_config.timer_heap);
|
||||
g.stun_config.timer_heap = NULL;
|
||||
}
|
||||
if (g.stun_config.ioqueue) {
|
||||
pj_ioqueue_destroy(g.stun_config.ioqueue);
|
||||
g.stun_config.ioqueue = NULL;
|
||||
}
|
||||
if (g.pool) {
|
||||
pj_pool_release(g.pool);
|
||||
g.pool = NULL;
|
||||
}
|
||||
pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
|
||||
pj_caching_pool_destroy(&g.cp);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static int worker_thread(void *unused)
|
||||
{
|
||||
PJ_UNUSED_ARG(unused);
|
||||
|
||||
while (!g.quit) {
|
||||
const pj_time_val delay = {0, 10};
|
||||
|
||||
/* Poll ioqueue for the TURN client */
|
||||
pj_ioqueue_poll(g.stun_config.ioqueue, &delay);
|
||||
|
||||
/* Poll the timer heap */
|
||||
pj_timer_heap_poll(g.stun_config.timer_heap, NULL);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pj_status_t create_relay(void)
|
||||
{
|
||||
pj_turn_sock_cb rel_cb;
|
||||
pj_stun_auth_cred cred;
|
||||
pj_str_t srv;
|
||||
pj_status_t status;
|
||||
|
||||
if (g.relay) {
|
||||
PJ_LOG(1,(THIS_FILE, "Relay already created"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Create DNS resolver if configured */
|
||||
if (o.nameserver) {
|
||||
pj_str_t ns = pj_str(o.nameserver);
|
||||
|
||||
status = pj_dns_resolver_create(&g.cp.factory, "resolver", 0,
|
||||
g.stun_config.timer_heap,
|
||||
g.stun_config.ioqueue, &g.resolver);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_LOG(1,(THIS_FILE, "Error creating resolver (err=%d)", status));
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pj_dns_resolver_set_ns(g.resolver, 1, &ns, NULL);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_LOG(1,(THIS_FILE, "Error configuring nameserver (err=%d)", status));
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
pj_bzero(&rel_cb, sizeof(rel_cb));
|
||||
rel_cb.on_rx_data = &turn_on_rx_data;
|
||||
rel_cb.on_state = &turn_on_state;
|
||||
CHECK( pj_turn_sock_create(&g.stun_config, pj_AF_INET(),
|
||||
(o.use_tcp? PJ_TURN_TP_TCP : PJ_TURN_TP_UDP),
|
||||
&rel_cb, 0,
|
||||
NULL, &g.relay) );
|
||||
|
||||
if (o.user_name) {
|
||||
pj_bzero(&cred, sizeof(cred));
|
||||
cred.type = PJ_STUN_AUTH_CRED_STATIC;
|
||||
cred.data.static_cred.realm = pj_str(o.realm);
|
||||
cred.data.static_cred.username = pj_str(o.user_name);
|
||||
cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
|
||||
cred.data.static_cred.data = pj_str(o.password);
|
||||
//cred.data.static_cred.nonce = pj_str(o.nonce);
|
||||
} else {
|
||||
PJ_LOG(2,(THIS_FILE, "Warning: no credential is set"));
|
||||
}
|
||||
|
||||
srv = pj_str(o.srv_addr);
|
||||
CHECK(pj_turn_sock_alloc(g.relay, /* the relay */
|
||||
&srv, /* srv addr */
|
||||
(o.srv_port?atoi(o.srv_port):PJ_STUN_PORT),/* def port */
|
||||
g.resolver, /* resolver */
|
||||
(o.user_name?&cred:NULL), /* credential */
|
||||
NULL) /* alloc param */
|
||||
);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static void destroy_relay(void)
|
||||
{
|
||||
if (g.relay) {
|
||||
pj_turn_sock_destroy(g.relay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void turn_on_rx_data(pj_turn_sock *relay,
|
||||
void *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_sockaddr_t *peer_addr,
|
||||
unsigned addr_len)
|
||||
{
|
||||
char addrinfo[80];
|
||||
|
||||
pj_sockaddr_print(peer_addr, addrinfo, sizeof(addrinfo), 3);
|
||||
|
||||
PJ_LOG(3,(THIS_FILE, "Client received %d bytes data from %s: %.*s",
|
||||
pkt_len, addrinfo, pkt_len, pkt));
|
||||
}
|
||||
|
||||
|
||||
static void turn_on_state(pj_turn_sock *relay, pj_turn_state_t old_state,
|
||||
pj_turn_state_t new_state)
|
||||
{
|
||||
PJ_LOG(3,(THIS_FILE, "State %s --> %s", pj_turn_state_name(old_state),
|
||||
pj_turn_state_name(new_state)));
|
||||
|
||||
if (new_state == PJ_TURN_STATE_READY) {
|
||||
pj_turn_session_info info;
|
||||
pj_turn_sock_get_info(relay, &info);
|
||||
pj_memcpy(&g.relay_addr, &info.relay_addr, sizeof(pj_sockaddr));
|
||||
} else if (new_state > PJ_TURN_STATE_READY && g.relay) {
|
||||
PJ_LOG(3,(THIS_FILE, "Relay shutting down.."));
|
||||
g.relay = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static pj_bool_t stun_sock_on_status(pj_stun_sock *stun_sock,
|
||||
pj_stun_sock_op op,
|
||||
pj_status_t status)
|
||||
{
|
||||
struct peer *peer = (struct peer*) pj_stun_sock_get_user_data(stun_sock);
|
||||
|
||||
if (status == PJ_SUCCESS) {
|
||||
PJ_LOG(4,(THIS_FILE, "peer%d: %s success", peer-g.peer,
|
||||
pj_stun_sock_op_name(op)));
|
||||
} else {
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(1,(THIS_FILE, "peer%d: %s error: %s", peer-g.peer,
|
||||
pj_stun_sock_op_name(op), errmsg));
|
||||
return PJ_FALSE;
|
||||
}
|
||||
|
||||
if (op==PJ_STUN_SOCK_BINDING_OP || op==PJ_STUN_SOCK_KEEP_ALIVE_OP) {
|
||||
pj_stun_sock_info info;
|
||||
int cmp;
|
||||
|
||||
pj_stun_sock_get_info(stun_sock, &info);
|
||||
cmp = pj_sockaddr_cmp(&info.mapped_addr, &peer->mapped_addr);
|
||||
|
||||
if (cmp) {
|
||||
char straddr[PJ_INET6_ADDRSTRLEN+10];
|
||||
|
||||
pj_sockaddr_cp(&peer->mapped_addr, &info.mapped_addr);
|
||||
pj_sockaddr_print(&peer->mapped_addr, straddr, sizeof(straddr), 3);
|
||||
PJ_LOG(3,(THIS_FILE, "peer%d: STUN mapped address is %s",
|
||||
peer-g.peer, straddr));
|
||||
}
|
||||
}
|
||||
|
||||
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 peer *peer = (struct peer*) pj_stun_sock_get_user_data(stun_sock);
|
||||
char straddr[PJ_INET6_ADDRSTRLEN+10];
|
||||
|
||||
((char*)pkt)[pkt_len] = '\0';
|
||||
|
||||
pj_sockaddr_print(src_addr, straddr, sizeof(straddr), 3);
|
||||
PJ_LOG(3,(THIS_FILE, "peer%d: received %d bytes data from %s: %s",
|
||||
peer-g.peer, pkt_len, straddr, (char*)pkt));
|
||||
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void menu(void)
|
||||
{
|
||||
pj_turn_session_info info;
|
||||
char client_state[20], relay_addr[80], peer0_addr[80], peer1_addr[80];
|
||||
|
||||
if (g.relay) {
|
||||
pj_turn_sock_get_info(g.relay, &info);
|
||||
strcpy(client_state, pj_turn_state_name(info.state));
|
||||
if (info.state >= PJ_TURN_STATE_READY)
|
||||
pj_sockaddr_print(&info.relay_addr, relay_addr, sizeof(relay_addr), 3);
|
||||
else
|
||||
strcpy(relay_addr, "0.0.0.0:0");
|
||||
} else {
|
||||
strcpy(client_state, "NULL");
|
||||
strcpy(relay_addr, "0.0.0.0:0");
|
||||
}
|
||||
|
||||
pj_sockaddr_print(&g.peer[0].mapped_addr, peer0_addr, sizeof(peer0_addr), 3);
|
||||
pj_sockaddr_print(&g.peer[1].mapped_addr, peer1_addr, sizeof(peer1_addr), 3);
|
||||
|
||||
|
||||
puts("\n");
|
||||
puts("+=====================================================================+");
|
||||
puts("| CLIENT | PEER-0 |");
|
||||
puts("| | |");
|
||||
printf("| State : %-12s | Address: %-21s |\n",
|
||||
client_state, peer0_addr);
|
||||
printf("| Relay addr: %-21s | |\n",
|
||||
relay_addr);
|
||||
puts("| | 0 Send data to relay address |");
|
||||
puts("| a Allocate relay | |");
|
||||
puts("| p,pp Set permission for peer 0/1 +--------------------------------+");
|
||||
puts("| s,ss Send data to peer 0/1 | PEER-1 |");
|
||||
puts("| b,bb BindChannel to peer 0/1 | |");
|
||||
printf("| x Delete allocation | Address: %-21s |\n",
|
||||
peer1_addr);
|
||||
puts("+------------------------------------+ |");
|
||||
puts("| q Quit d Dump | 1 Send data to relay adderss |");
|
||||
puts("+------------------------------------+--------------------------------+");
|
||||
printf(">>> ");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
static void console_main(void)
|
||||
{
|
||||
while (!g.quit) {
|
||||
char input[32];
|
||||
struct peer *peer;
|
||||
pj_status_t status;
|
||||
|
||||
menu();
|
||||
|
||||
if (fgets(input, sizeof(input), stdin) == NULL)
|
||||
break;
|
||||
|
||||
switch (input[0]) {
|
||||
case 'a':
|
||||
create_relay();
|
||||
break;
|
||||
case 'd':
|
||||
pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
|
||||
break;
|
||||
case 's':
|
||||
if (g.relay == NULL) {
|
||||
puts("Error: no relay");
|
||||
continue;
|
||||
}
|
||||
if (input[1]!='s')
|
||||
peer = &g.peer[0];
|
||||
else
|
||||
peer = &g.peer[1];
|
||||
|
||||
strcpy(input, "Hello from client");
|
||||
status = pj_turn_sock_sendto(g.relay, (const pj_uint8_t*)input,
|
||||
strlen(input)+1,
|
||||
&peer->mapped_addr,
|
||||
pj_sockaddr_get_len(&peer->mapped_addr));
|
||||
if (status != PJ_SUCCESS && status != PJ_EPENDING)
|
||||
my_perror("turn_udp_sendto() failed", status);
|
||||
break;
|
||||
case 'b':
|
||||
if (g.relay == NULL) {
|
||||
puts("Error: no relay");
|
||||
continue;
|
||||
}
|
||||
if (input[1]!='b')
|
||||
peer = &g.peer[0];
|
||||
else
|
||||
peer = &g.peer[1];
|
||||
|
||||
status = pj_turn_sock_bind_channel(g.relay, &peer->mapped_addr,
|
||||
pj_sockaddr_get_len(&peer->mapped_addr));
|
||||
if (status != PJ_SUCCESS)
|
||||
my_perror("turn_udp_bind_channel() failed", status);
|
||||
break;
|
||||
case 'p':
|
||||
if (g.relay == NULL) {
|
||||
puts("Error: no relay");
|
||||
continue;
|
||||
}
|
||||
if (input[1]!='p')
|
||||
peer = &g.peer[0];
|
||||
else
|
||||
peer = &g.peer[1];
|
||||
|
||||
status = pj_turn_sock_set_perm(g.relay, 1, &peer->mapped_addr, 1);
|
||||
if (status != PJ_SUCCESS)
|
||||
my_perror("pj_turn_sock_set_perm() failed", status);
|
||||
break;
|
||||
case 'x':
|
||||
if (g.relay == NULL) {
|
||||
puts("Error: no relay");
|
||||
continue;
|
||||
}
|
||||
destroy_relay();
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
if (g.relay == NULL) {
|
||||
puts("No relay");
|
||||
break;
|
||||
}
|
||||
peer = &g.peer[input[0]-'0'];
|
||||
sprintf(input, "Hello from peer%d", input[0]-'0');
|
||||
pj_stun_sock_sendto(peer->stun_sock, NULL, input, strlen(input)+1, 0,
|
||||
&g.relay_addr, pj_sockaddr_get_len(&g.relay_addr));
|
||||
break;
|
||||
case 'q':
|
||||
g.quit = PJ_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
puts("Usage: pjturn_client TURN-SERVER [OPTIONS]");
|
||||
puts("");
|
||||
puts("where TURN-SERVER is \"host[:port]\"");
|
||||
puts("");
|
||||
puts("and OPTIONS:");
|
||||
puts(" --tcp, -T Use TCP to connect to TURN server");
|
||||
puts(" --realm, -r REALM Set realm of the credential to REALM");
|
||||
puts(" --username, -u UID Set username of the credential to UID");
|
||||
puts(" --password, -p PASSWD Set password of the credential to PASSWD");
|
||||
puts(" --fingerprint, -F Use fingerprint for outgoing requests");
|
||||
puts(" --stun-srv, -S NAME Use this STUN srv instead of TURN for Binding discovery");
|
||||
puts(" --nameserver, -N IP Activate DNS SRV, use this DNS server");
|
||||
puts(" --help, -h");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct pj_getopt_option long_options[] = {
|
||||
{ "realm", 1, 0, 'r'},
|
||||
{ "username", 1, 0, 'u'},
|
||||
{ "password", 1, 0, 'p'},
|
||||
{ "fingerprint",0, 0, 'F'},
|
||||
{ "tcp", 0, 0, 'T'},
|
||||
{ "help", 0, 0, 'h'},
|
||||
{ "stun-srv", 1, 0, 'S'},
|
||||
{ "nameserver", 1, 0, 'N'}
|
||||
};
|
||||
int c, opt_id;
|
||||
char *pos;
|
||||
pj_status_t status;
|
||||
|
||||
while((c=pj_getopt_long(argc,argv, "r:u:p:S:N:hFT", long_options, &opt_id))!=-1) {
|
||||
switch (c) {
|
||||
case 'r':
|
||||
o.realm = pj_optarg;
|
||||
break;
|
||||
case 'u':
|
||||
o.user_name = pj_optarg;
|
||||
break;
|
||||
case 'p':
|
||||
o.password = pj_optarg;
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
return 0;
|
||||
case 'F':
|
||||
o.use_fingerprint = PJ_TRUE;
|
||||
break;
|
||||
case 'T':
|
||||
o.use_tcp = PJ_TRUE;
|
||||
break;
|
||||
case 'S':
|
||||
o.stun_server = pj_optarg;
|
||||
break;
|
||||
case 'N':
|
||||
o.nameserver = pj_optarg;
|
||||
break;
|
||||
default:
|
||||
printf("Argument \"%s\" is not valid. Use -h to see help",
|
||||
argv[pj_optind]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pj_optind == argc) {
|
||||
puts("Error: TARGET is needed");
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((pos=pj_ansi_strchr(argv[pj_optind], ':')) != NULL) {
|
||||
o.srv_addr = argv[pj_optind];
|
||||
*pos = '\0';
|
||||
o.srv_port = pos+1;
|
||||
} else {
|
||||
o.srv_addr = argv[pj_optind];
|
||||
}
|
||||
|
||||
if ((status=init()) != 0)
|
||||
goto on_return;
|
||||
|
||||
//if ((status=create_relay()) != 0)
|
||||
// goto on_return;
|
||||
|
||||
console_main();
|
||||
|
||||
on_return:
|
||||
client_shutdown();
|
||||
return status ? 1 : 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,145 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "auth.h"
|
||||
#include <pjlib.h>
|
||||
|
||||
|
||||
#define MAX_REALM 80
|
||||
#define MAX_USERNAME 32
|
||||
#define MAX_PASSWORD 32
|
||||
#define MAX_NONCE 32
|
||||
|
||||
static char g_realm[MAX_REALM];
|
||||
|
||||
static struct cred_t
|
||||
{
|
||||
char username[MAX_USERNAME];
|
||||
char passwd[MAX_PASSWORD];
|
||||
} g_cred[] =
|
||||
{
|
||||
{ "100", "100" },
|
||||
{ "700", "700" },
|
||||
{ "701", "701" }
|
||||
};
|
||||
|
||||
#define THIS_FILE "auth.c"
|
||||
#define THE_NONCE "pjnath"
|
||||
#define LOG(expr) PJ_LOG(3,expr)
|
||||
|
||||
|
||||
/*
|
||||
* Initialize TURN authentication subsystem.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_auth_init(const char *realm)
|
||||
{
|
||||
PJ_ASSERT_RETURN(pj_ansi_strlen(realm) < MAX_REALM, PJ_ENAMETOOLONG);
|
||||
pj_ansi_strcpy(g_realm, realm);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shutdown TURN authentication subsystem.
|
||||
*/
|
||||
PJ_DEF(void) pj_turn_auth_dinit(void)
|
||||
{
|
||||
/* Nothing to do */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function is called by pj_stun_verify_credential() when
|
||||
* server needs to challenge the request with 401 response.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_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);
|
||||
|
||||
*realm = pj_str(g_realm);
|
||||
*nonce = pj_str(THE_NONCE);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called to get the password for the specified username.
|
||||
* This function is also used to check whether the username is valid.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_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)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
PJ_UNUSED_ARG(msg);
|
||||
PJ_UNUSED_ARG(user_data);
|
||||
PJ_UNUSED_ARG(pool);
|
||||
|
||||
if (pj_stricmp2(realm, g_realm)) {
|
||||
LOG((THIS_FILE, "auth error: invalid realm '%.*s'",
|
||||
(int)realm->slen, realm->ptr));
|
||||
return PJ_EINVAL;
|
||||
}
|
||||
|
||||
for (i=0; i<PJ_ARRAY_SIZE(g_cred); ++i) {
|
||||
if (pj_stricmp2(username, g_cred[i].username) == 0) {
|
||||
*data_type = PJ_STUN_PASSWD_PLAIN;
|
||||
*data = pj_str(g_cred[i].passwd);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
LOG((THIS_FILE, "auth error: user '%.*s' not found",
|
||||
(int)username->slen, username->ptr));
|
||||
return PJ_ENOTFOUND;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function will be called to verify that the NONCE given
|
||||
* in the message can be accepted. If this callback returns
|
||||
* PJ_FALSE, 438 (Stale Nonce) response will be created.
|
||||
*/
|
||||
PJ_DEF(pj_bool_t) pj_turn_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_stricmp2(nonce, THE_NONCE)) {
|
||||
LOG((THIS_FILE, "auth error: invalid nonce '%.*s'",
|
||||
(int)nonce->slen, nonce->ptr));
|
||||
return PJ_FALSE;
|
||||
}
|
||||
|
||||
return PJ_TRUE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __PJ_TURN_SRV_AUTH_H__
|
||||
#define __PJ_TURN_SRV_AUTH_H__
|
||||
|
||||
#include <pjnath.h>
|
||||
|
||||
/**
|
||||
* Initialize TURN authentication subsystem.
|
||||
*
|
||||
* @return PJ_SUCCESS on success.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_auth_init(const char *realm);
|
||||
|
||||
/**
|
||||
* Shutdown TURN authentication subsystem.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_auth_dinit(void);
|
||||
|
||||
/**
|
||||
* This function is called by pj_stun_verify_credential() when
|
||||
* server needs to challenge the request with 401 response.
|
||||
*
|
||||
* @param user_data Should be ignored.
|
||||
* @param pool Pool to allocate memory.
|
||||
* @param realm On return, the function should fill in with
|
||||
* realm if application wants to use long term
|
||||
* credential. Otherwise application should set
|
||||
* empty string for the realm.
|
||||
* @param nonce On return, if application wants to use long
|
||||
* term credential, it MUST fill in the nonce
|
||||
* with some value. Otherwise if short term
|
||||
* credential is wanted, it MAY set this value.
|
||||
* If short term credential is wanted and the
|
||||
* application doesn't want to include NONCE,
|
||||
* then it must set this to empty string.
|
||||
*
|
||||
* @return The callback should return PJ_SUCCESS, or
|
||||
* otherwise response message will not be
|
||||
* created.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_get_auth(void *user_data,
|
||||
pj_pool_t *pool,
|
||||
pj_str_t *realm,
|
||||
pj_str_t *nonce);
|
||||
|
||||
/**
|
||||
* This function is called to get the password for the specified username.
|
||||
* This function is also used to check whether the username is valid.
|
||||
*
|
||||
* @param msg The STUN message where the password will be
|
||||
* applied to.
|
||||
* @param user_data Should be ignored.
|
||||
* @param realm The realm as specified in the message.
|
||||
* @param username The username as specified in the message.
|
||||
* @param pool Pool to allocate memory when necessary.
|
||||
* @param data_type On return, application should fill up this
|
||||
* argument with the type of data (which should
|
||||
* be zero if data is a plaintext password).
|
||||
* @param data On return, application should fill up this
|
||||
* argument with the password according to
|
||||
* data_type.
|
||||
*
|
||||
* @return The callback should return PJ_SUCCESS if
|
||||
* username has been successfully verified
|
||||
* and password was obtained. If non-PJ_SUCCESS
|
||||
* is returned, it is assumed that the
|
||||
* username is not valid.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_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);
|
||||
|
||||
/**
|
||||
* This function will be called to verify that the NONCE given
|
||||
* in the message can be accepted. If this callback returns
|
||||
* PJ_FALSE, 438 (Stale Nonce) response will be created.
|
||||
*
|
||||
* @param msg The STUN message where the nonce was received.
|
||||
* @param user_data Should be ignored.
|
||||
* @param realm The realm as specified in the message.
|
||||
* @param username The username as specified in the message.
|
||||
* @param nonce The nonce to be verified.
|
||||
*
|
||||
* @return The callback MUST return non-zero if the
|
||||
* NONCE can be accepted.
|
||||
*/
|
||||
PJ_DECL(pj_bool_t) pj_turn_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);
|
||||
|
||||
#endif /* __PJ_TURN_SRV_AUTH_H__ */
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "turn.h"
|
||||
#include <pj/compat/socket.h>
|
||||
|
||||
#if PJ_HAS_TCP
|
||||
|
||||
struct accept_op
|
||||
{
|
||||
pj_ioqueue_op_key_t op_key;
|
||||
pj_sock_t sock;
|
||||
pj_sockaddr src_addr;
|
||||
int src_addr_len;
|
||||
};
|
||||
|
||||
struct tcp_listener
|
||||
{
|
||||
pj_turn_listener base;
|
||||
pj_ioqueue_key_t *key;
|
||||
unsigned accept_cnt;
|
||||
struct accept_op *accept_op; /* Array of accept_op's */
|
||||
};
|
||||
|
||||
|
||||
static void lis_on_accept_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_sock_t sock,
|
||||
pj_status_t status);
|
||||
static pj_status_t lis_destroy(pj_turn_listener *listener);
|
||||
static void transport_create(pj_sock_t sock, pj_turn_listener *lis,
|
||||
pj_sockaddr_t *src_addr, int src_addr_len);
|
||||
|
||||
static void show_err(const char *sender, const char *title,
|
||||
pj_status_t status)
|
||||
{
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(4,(sender, "%s: %s", title, errmsg));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create a new listener on the specified port.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_listener_create_tcp(pj_turn_srv *srv,
|
||||
int af,
|
||||
const pj_str_t *bound_addr,
|
||||
unsigned port,
|
||||
unsigned concurrency_cnt,
|
||||
unsigned flags,
|
||||
pj_turn_listener **p_listener)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
struct tcp_listener *tcp_lis;
|
||||
pj_ioqueue_callback ioqueue_cb;
|
||||
unsigned i;
|
||||
pj_status_t status;
|
||||
|
||||
/* Create structure */
|
||||
pool = pj_pool_create(srv->core.pf, "tcpl%p", 1000, 1000, NULL);
|
||||
tcp_lis = PJ_POOL_ZALLOC_T(pool, struct tcp_listener);
|
||||
tcp_lis->base.pool = pool;
|
||||
tcp_lis->base.obj_name = pool->obj_name;
|
||||
tcp_lis->base.server = srv;
|
||||
tcp_lis->base.tp_type = PJ_TURN_TP_TCP;
|
||||
tcp_lis->base.sock = PJ_INVALID_SOCKET;
|
||||
//tcp_lis->base.sendto = &tcp_sendto;
|
||||
tcp_lis->base.destroy = &lis_destroy;
|
||||
tcp_lis->accept_cnt = concurrency_cnt;
|
||||
tcp_lis->base.flags = flags;
|
||||
|
||||
/* Create socket */
|
||||
status = pj_sock_socket(af, pj_SOCK_STREAM(), 0, &tcp_lis->base.sock);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Init bind address */
|
||||
status = pj_sockaddr_init(af, &tcp_lis->base.addr, bound_addr,
|
||||
(pj_uint16_t)port);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Create info */
|
||||
pj_ansi_strcpy(tcp_lis->base.info, "TCP:");
|
||||
pj_sockaddr_print(&tcp_lis->base.addr, tcp_lis->base.info+4,
|
||||
sizeof(tcp_lis->base.info)-4, 3);
|
||||
|
||||
/* Bind socket */
|
||||
status = pj_sock_bind(tcp_lis->base.sock, &tcp_lis->base.addr,
|
||||
pj_sockaddr_get_len(&tcp_lis->base.addr));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Listen() */
|
||||
status = pj_sock_listen(tcp_lis->base.sock, 5);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Register to ioqueue */
|
||||
pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
|
||||
ioqueue_cb.on_accept_complete = &lis_on_accept_complete;
|
||||
status = pj_ioqueue_register_sock(pool, srv->core.ioqueue, tcp_lis->base.sock,
|
||||
tcp_lis, &ioqueue_cb, &tcp_lis->key);
|
||||
|
||||
/* Create op keys */
|
||||
tcp_lis->accept_op = (struct accept_op*)pj_pool_calloc(pool, concurrency_cnt,
|
||||
sizeof(struct accept_op));
|
||||
|
||||
/* Create each accept_op and kick off read operation */
|
||||
for (i=0; i<concurrency_cnt; ++i) {
|
||||
lis_on_accept_complete(tcp_lis->key, &tcp_lis->accept_op[i].op_key,
|
||||
PJ_INVALID_SOCKET, PJ_EPENDING);
|
||||
}
|
||||
|
||||
/* Done */
|
||||
PJ_LOG(4,(tcp_lis->base.obj_name, "Listener %s created",
|
||||
tcp_lis->base.info));
|
||||
|
||||
*p_listener = &tcp_lis->base;
|
||||
return PJ_SUCCESS;
|
||||
|
||||
|
||||
on_error:
|
||||
lis_destroy(&tcp_lis->base);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy listener.
|
||||
*/
|
||||
static pj_status_t lis_destroy(pj_turn_listener *listener)
|
||||
{
|
||||
struct tcp_listener *tcp_lis = (struct tcp_listener *)listener;
|
||||
unsigned i;
|
||||
|
||||
if (tcp_lis->key) {
|
||||
pj_ioqueue_unregister(tcp_lis->key);
|
||||
tcp_lis->key = NULL;
|
||||
tcp_lis->base.sock = PJ_INVALID_SOCKET;
|
||||
} else if (tcp_lis->base.sock != PJ_INVALID_SOCKET) {
|
||||
pj_sock_close(tcp_lis->base.sock);
|
||||
tcp_lis->base.sock = PJ_INVALID_SOCKET;
|
||||
}
|
||||
|
||||
for (i=0; i<tcp_lis->accept_cnt; ++i) {
|
||||
/* Nothing to do */
|
||||
}
|
||||
|
||||
if (tcp_lis->base.pool) {
|
||||
pj_pool_t *pool = tcp_lis->base.pool;
|
||||
|
||||
PJ_LOG(4,(tcp_lis->base.obj_name, "Listener %s destroyed",
|
||||
tcp_lis->base.info));
|
||||
|
||||
tcp_lis->base.pool = NULL;
|
||||
pj_pool_release(pool);
|
||||
}
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback on new TCP connection.
|
||||
*/
|
||||
static void lis_on_accept_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_sock_t sock,
|
||||
pj_status_t status)
|
||||
{
|
||||
struct tcp_listener *tcp_lis;
|
||||
struct accept_op *accept_op = (struct accept_op*) op_key;
|
||||
|
||||
tcp_lis = (struct tcp_listener*) pj_ioqueue_get_user_data(key);
|
||||
|
||||
PJ_UNUSED_ARG(sock);
|
||||
|
||||
do {
|
||||
/* Report new connection. */
|
||||
if (status == PJ_SUCCESS) {
|
||||
char addr[PJ_INET6_ADDRSTRLEN+8];
|
||||
PJ_LOG(5,(tcp_lis->base.obj_name, "Incoming TCP from %s",
|
||||
pj_sockaddr_print(&accept_op->src_addr, addr,
|
||||
sizeof(addr), 3)));
|
||||
transport_create(accept_op->sock, &tcp_lis->base,
|
||||
&accept_op->src_addr, accept_op->src_addr_len);
|
||||
} else if (status != PJ_EPENDING) {
|
||||
show_err(tcp_lis->base.obj_name, "accept()", status);
|
||||
}
|
||||
|
||||
/* Prepare next accept() */
|
||||
accept_op->src_addr_len = sizeof(accept_op->src_addr);
|
||||
status = pj_ioqueue_accept(key, op_key, &accept_op->sock,
|
||||
NULL,
|
||||
&accept_op->src_addr,
|
||||
&accept_op->src_addr_len);
|
||||
|
||||
} while (status != PJ_EPENDING && status != PJ_ECANCELLED &&
|
||||
status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL));
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************/
|
||||
/*
|
||||
* Transport
|
||||
*/
|
||||
enum
|
||||
{
|
||||
TIMER_NONE,
|
||||
TIMER_DESTROY
|
||||
};
|
||||
|
||||
/* The delay in seconds to be applied before TCP transport is destroyed when
|
||||
* no allocation is referencing it. This also means the initial time to wait
|
||||
* after the initial TCP connection establishment to receive a valid STUN
|
||||
* message in the transport.
|
||||
*/
|
||||
#define SHUTDOWN_DELAY 10
|
||||
|
||||
struct recv_op
|
||||
{
|
||||
pj_ioqueue_op_key_t op_key;
|
||||
pj_turn_pkt pkt;
|
||||
};
|
||||
|
||||
struct tcp_transport
|
||||
{
|
||||
pj_turn_transport base;
|
||||
pj_pool_t *pool;
|
||||
pj_timer_entry timer;
|
||||
|
||||
pj_turn_allocation *alloc;
|
||||
int ref_cnt;
|
||||
|
||||
pj_sock_t sock;
|
||||
pj_ioqueue_key_t *key;
|
||||
struct recv_op recv_op;
|
||||
pj_ioqueue_op_key_t send_op;
|
||||
};
|
||||
|
||||
|
||||
static void tcp_on_read_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_ssize_t bytes_read);
|
||||
|
||||
static pj_status_t tcp_sendto(pj_turn_transport *tp,
|
||||
const void *packet,
|
||||
pj_size_t size,
|
||||
unsigned flag,
|
||||
const pj_sockaddr_t *addr,
|
||||
int addr_len);
|
||||
static void tcp_destroy(struct tcp_transport *tcp);
|
||||
static void tcp_add_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc);
|
||||
static void tcp_dec_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc);
|
||||
static void timer_callback(pj_timer_heap_t *timer_heap,
|
||||
pj_timer_entry *entry);
|
||||
|
||||
static void transport_create(pj_sock_t sock, pj_turn_listener *lis,
|
||||
pj_sockaddr_t *src_addr, int src_addr_len)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
struct tcp_transport *tcp;
|
||||
pj_ioqueue_callback cb;
|
||||
pj_status_t status;
|
||||
|
||||
pool = pj_pool_create(lis->server->core.pf, "tcp%p", 1000, 1000, NULL);
|
||||
|
||||
tcp = PJ_POOL_ZALLOC_T(pool, struct tcp_transport);
|
||||
tcp->base.obj_name = pool->obj_name;
|
||||
tcp->base.listener = lis;
|
||||
tcp->base.info = lis->info;
|
||||
tcp->base.sendto = &tcp_sendto;
|
||||
tcp->base.add_ref = &tcp_add_ref;
|
||||
tcp->base.dec_ref = &tcp_dec_ref;
|
||||
tcp->pool = pool;
|
||||
tcp->sock = sock;
|
||||
|
||||
pj_timer_entry_init(&tcp->timer, TIMER_NONE, tcp, &timer_callback);
|
||||
|
||||
/* Register to ioqueue */
|
||||
pj_bzero(&cb, sizeof(cb));
|
||||
cb.on_read_complete = &tcp_on_read_complete;
|
||||
status = pj_ioqueue_register_sock(pool, lis->server->core.ioqueue, sock,
|
||||
tcp, &cb, &tcp->key);
|
||||
if (status != PJ_SUCCESS) {
|
||||
tcp_destroy(tcp);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Init pkt */
|
||||
tcp->recv_op.pkt.pool = pj_pool_create(lis->server->core.pf, "tcpkt%p",
|
||||
1000, 1000, NULL);
|
||||
tcp->recv_op.pkt.transport = &tcp->base;
|
||||
tcp->recv_op.pkt.src.tp_type = PJ_TURN_TP_TCP;
|
||||
tcp->recv_op.pkt.src_addr_len = src_addr_len;
|
||||
pj_memcpy(&tcp->recv_op.pkt.src.clt_addr, src_addr, src_addr_len);
|
||||
|
||||
tcp_on_read_complete(tcp->key, &tcp->recv_op.op_key, -PJ_EPENDING);
|
||||
/* Should not access transport from now, it may have been destroyed */
|
||||
}
|
||||
|
||||
|
||||
static void tcp_destroy(struct tcp_transport *tcp)
|
||||
{
|
||||
if (tcp->key) {
|
||||
pj_ioqueue_unregister(tcp->key);
|
||||
tcp->key = NULL;
|
||||
tcp->sock = 0;
|
||||
} else if (tcp->sock) {
|
||||
pj_sock_close(tcp->sock);
|
||||
tcp->sock = 0;
|
||||
}
|
||||
|
||||
if (tcp->pool) {
|
||||
pj_pool_release(tcp->pool);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void timer_callback(pj_timer_heap_t *timer_heap,
|
||||
pj_timer_entry *entry)
|
||||
{
|
||||
struct tcp_transport *tcp = (struct tcp_transport*) entry->user_data;
|
||||
|
||||
PJ_UNUSED_ARG(timer_heap);
|
||||
|
||||
tcp_destroy(tcp);
|
||||
}
|
||||
|
||||
|
||||
static void tcp_on_read_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_ssize_t bytes_read)
|
||||
{
|
||||
struct tcp_transport *tcp;
|
||||
struct recv_op *recv_op = (struct recv_op*) op_key;
|
||||
pj_status_t status;
|
||||
|
||||
tcp = (struct tcp_transport*) pj_ioqueue_get_user_data(key);
|
||||
|
||||
do {
|
||||
/* Report to server or allocation, if we have allocation */
|
||||
if (bytes_read > 0) {
|
||||
|
||||
recv_op->pkt.len = bytes_read;
|
||||
pj_gettimeofday(&recv_op->pkt.rx_time);
|
||||
|
||||
tcp_add_ref(&tcp->base, NULL);
|
||||
|
||||
if (tcp->alloc) {
|
||||
pj_turn_allocation_on_rx_client_pkt(tcp->alloc, &recv_op->pkt);
|
||||
} else {
|
||||
pj_turn_srv_on_rx_pkt(tcp->base.listener->server, &recv_op->pkt);
|
||||
}
|
||||
|
||||
pj_assert(tcp->ref_cnt > 0);
|
||||
tcp_dec_ref(&tcp->base, NULL);
|
||||
|
||||
} else if (bytes_read != -PJ_EPENDING) {
|
||||
/* TCP connection closed/error. Notify client and then destroy
|
||||
* ourselves.
|
||||
* Note: the -PJ_EPENDING is the value passed during init.
|
||||
*/
|
||||
++tcp->ref_cnt;
|
||||
|
||||
if (tcp->alloc) {
|
||||
if (bytes_read != 0) {
|
||||
show_err(tcp->base.obj_name, "TCP socket error",
|
||||
-bytes_read);
|
||||
} else {
|
||||
PJ_LOG(5,(tcp->base.obj_name, "TCP socket closed"));
|
||||
}
|
||||
pj_turn_allocation_on_transport_closed(tcp->alloc, &tcp->base);
|
||||
tcp->alloc = NULL;
|
||||
}
|
||||
|
||||
pj_assert(tcp->ref_cnt > 0);
|
||||
if (--tcp->ref_cnt == 0) {
|
||||
tcp_destroy(tcp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset pool */
|
||||
pj_pool_reset(recv_op->pkt.pool);
|
||||
|
||||
/* If packet is full discard it */
|
||||
if (recv_op->pkt.len == sizeof(recv_op->pkt.pkt)) {
|
||||
PJ_LOG(4,(tcp->base.obj_name, "Buffer discarded"));
|
||||
recv_op->pkt.len = 0;
|
||||
}
|
||||
|
||||
/* Read next packet */
|
||||
bytes_read = sizeof(recv_op->pkt.pkt) - recv_op->pkt.len;
|
||||
status = pj_ioqueue_recv(tcp->key, op_key,
|
||||
recv_op->pkt.pkt + recv_op->pkt.len,
|
||||
&bytes_read, 0);
|
||||
|
||||
if (status != PJ_EPENDING && status != PJ_SUCCESS)
|
||||
bytes_read = -status;
|
||||
|
||||
} while (status != PJ_EPENDING && status != PJ_ECANCELLED &&
|
||||
status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL));
|
||||
|
||||
}
|
||||
|
||||
|
||||
static pj_status_t tcp_sendto(pj_turn_transport *tp,
|
||||
const void *packet,
|
||||
pj_size_t size,
|
||||
unsigned flag,
|
||||
const pj_sockaddr_t *addr,
|
||||
int addr_len)
|
||||
{
|
||||
struct tcp_transport *tcp = (struct tcp_transport*) tp;
|
||||
pj_ssize_t length = size;
|
||||
|
||||
PJ_UNUSED_ARG(addr);
|
||||
PJ_UNUSED_ARG(addr_len);
|
||||
|
||||
return pj_ioqueue_send(tcp->key, &tcp->send_op, packet, &length, flag);
|
||||
}
|
||||
|
||||
|
||||
static void tcp_add_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
struct tcp_transport *tcp = (struct tcp_transport*) tp;
|
||||
|
||||
++tcp->ref_cnt;
|
||||
|
||||
if (tcp->alloc == NULL && alloc) {
|
||||
tcp->alloc = alloc;
|
||||
}
|
||||
|
||||
/* Cancel shutdown timer if it's running */
|
||||
if (tcp->timer.id != TIMER_NONE) {
|
||||
pj_timer_heap_cancel(tcp->base.listener->server->core.timer_heap,
|
||||
&tcp->timer);
|
||||
tcp->timer.id = TIMER_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void tcp_dec_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
struct tcp_transport *tcp = (struct tcp_transport*) tp;
|
||||
|
||||
--tcp->ref_cnt;
|
||||
|
||||
if (alloc && alloc == tcp->alloc) {
|
||||
tcp->alloc = NULL;
|
||||
}
|
||||
|
||||
if (tcp->ref_cnt == 0 && tcp->timer.id == TIMER_NONE) {
|
||||
pj_time_val delay = { SHUTDOWN_DELAY, 0 };
|
||||
tcp->timer.id = TIMER_DESTROY;
|
||||
pj_timer_heap_schedule(tcp->base.listener->server->core.timer_heap,
|
||||
&tcp->timer, &delay);
|
||||
}
|
||||
}
|
||||
|
||||
#else /* PJ_HAS_TCP */
|
||||
|
||||
/* To avoid empty translation unit warning */
|
||||
int listener_tcp_dummy = 0;
|
||||
|
||||
#endif /* PJ_HAS_TCP */
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "turn.h"
|
||||
#include <pj/compat/socket.h>
|
||||
|
||||
struct read_op
|
||||
{
|
||||
pj_ioqueue_op_key_t op_key;
|
||||
pj_turn_pkt pkt;
|
||||
};
|
||||
|
||||
struct udp_listener
|
||||
{
|
||||
pj_turn_listener base;
|
||||
|
||||
pj_ioqueue_key_t *key;
|
||||
unsigned read_cnt;
|
||||
struct read_op **read_op; /* Array of read_op's */
|
||||
|
||||
pj_turn_transport tp; /* Transport instance */
|
||||
};
|
||||
|
||||
|
||||
static pj_status_t udp_destroy(pj_turn_listener *udp);
|
||||
static void on_read_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_ssize_t bytes_read);
|
||||
|
||||
static pj_status_t udp_sendto(pj_turn_transport *tp,
|
||||
const void *packet,
|
||||
pj_size_t size,
|
||||
unsigned flag,
|
||||
const pj_sockaddr_t *addr,
|
||||
int addr_len);
|
||||
static void udp_add_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc);
|
||||
static void udp_dec_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
|
||||
/*
|
||||
* Create a new listener on the specified port.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_listener_create_udp( pj_turn_srv *srv,
|
||||
int af,
|
||||
const pj_str_t *bound_addr,
|
||||
unsigned port,
|
||||
unsigned concurrency_cnt,
|
||||
unsigned flags,
|
||||
pj_turn_listener **p_listener)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
struct udp_listener *udp;
|
||||
pj_ioqueue_callback ioqueue_cb;
|
||||
unsigned i;
|
||||
pj_status_t status;
|
||||
|
||||
/* Create structure */
|
||||
pool = pj_pool_create(srv->core.pf, "udp%p", 1000, 1000, NULL);
|
||||
udp = PJ_POOL_ZALLOC_T(pool, struct udp_listener);
|
||||
udp->base.pool = pool;
|
||||
udp->base.obj_name = pool->obj_name;
|
||||
udp->base.server = srv;
|
||||
udp->base.tp_type = PJ_TURN_TP_UDP;
|
||||
udp->base.sock = PJ_INVALID_SOCKET;
|
||||
udp->base.destroy = &udp_destroy;
|
||||
udp->read_cnt = concurrency_cnt;
|
||||
udp->base.flags = flags;
|
||||
|
||||
udp->tp.obj_name = udp->base.obj_name;
|
||||
udp->tp.info = udp->base.info;
|
||||
udp->tp.listener = &udp->base;
|
||||
udp->tp.sendto = &udp_sendto;
|
||||
udp->tp.add_ref = &udp_add_ref;
|
||||
udp->tp.dec_ref = &udp_dec_ref;
|
||||
|
||||
/* Create socket */
|
||||
status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &udp->base.sock);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Init bind address */
|
||||
status = pj_sockaddr_init(af, &udp->base.addr, bound_addr,
|
||||
(pj_uint16_t)port);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Create info */
|
||||
pj_ansi_strcpy(udp->base.info, "UDP:");
|
||||
pj_sockaddr_print(&udp->base.addr, udp->base.info+4,
|
||||
sizeof(udp->base.info)-4, 3);
|
||||
|
||||
/* Bind socket */
|
||||
status = pj_sock_bind(udp->base.sock, &udp->base.addr,
|
||||
pj_sockaddr_get_len(&udp->base.addr));
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Register to ioqueue */
|
||||
pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
|
||||
ioqueue_cb.on_read_complete = on_read_complete;
|
||||
status = pj_ioqueue_register_sock(pool, srv->core.ioqueue, udp->base.sock,
|
||||
udp, &ioqueue_cb, &udp->key);
|
||||
|
||||
/* Create op keys */
|
||||
udp->read_op = (struct read_op**)pj_pool_calloc(pool, concurrency_cnt,
|
||||
sizeof(struct read_op*));
|
||||
|
||||
/* Create each read_op and kick off read operation */
|
||||
for (i=0; i<concurrency_cnt; ++i) {
|
||||
pj_pool_t *rpool = pj_pool_create(srv->core.pf, "rop%p",
|
||||
1000, 1000, NULL);
|
||||
|
||||
udp->read_op[i] = PJ_POOL_ZALLOC_T(pool, struct read_op);
|
||||
udp->read_op[i]->pkt.pool = rpool;
|
||||
|
||||
on_read_complete(udp->key, &udp->read_op[i]->op_key, 0);
|
||||
}
|
||||
|
||||
/* Done */
|
||||
PJ_LOG(4,(udp->base.obj_name, "Listener %s created", udp->base.info));
|
||||
|
||||
*p_listener = &udp->base;
|
||||
return PJ_SUCCESS;
|
||||
|
||||
|
||||
on_error:
|
||||
udp_destroy(&udp->base);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy listener.
|
||||
*/
|
||||
static pj_status_t udp_destroy(pj_turn_listener *listener)
|
||||
{
|
||||
struct udp_listener *udp = (struct udp_listener *)listener;
|
||||
unsigned i;
|
||||
|
||||
if (udp->key) {
|
||||
pj_ioqueue_unregister(udp->key);
|
||||
udp->key = NULL;
|
||||
udp->base.sock = PJ_INVALID_SOCKET;
|
||||
} else if (udp->base.sock != PJ_INVALID_SOCKET) {
|
||||
pj_sock_close(udp->base.sock);
|
||||
udp->base.sock = PJ_INVALID_SOCKET;
|
||||
}
|
||||
|
||||
for (i=0; i<udp->read_cnt; ++i) {
|
||||
if (udp->read_op[i]->pkt.pool) {
|
||||
pj_pool_t *rpool = udp->read_op[i]->pkt.pool;
|
||||
udp->read_op[i]->pkt.pool = NULL;
|
||||
pj_pool_release(rpool);
|
||||
}
|
||||
}
|
||||
|
||||
if (udp->base.pool) {
|
||||
pj_pool_t *pool = udp->base.pool;
|
||||
|
||||
PJ_LOG(4,(udp->base.obj_name, "Listener %s destroyed",
|
||||
udp->base.info));
|
||||
|
||||
udp->base.pool = NULL;
|
||||
pj_pool_release(pool);
|
||||
}
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback to send packet.
|
||||
*/
|
||||
static pj_status_t udp_sendto(pj_turn_transport *tp,
|
||||
const void *packet,
|
||||
pj_size_t size,
|
||||
unsigned flag,
|
||||
const pj_sockaddr_t *addr,
|
||||
int addr_len)
|
||||
{
|
||||
pj_ssize_t len = size;
|
||||
return pj_sock_sendto(tp->listener->sock, packet, &len, flag, addr, addr_len);
|
||||
}
|
||||
|
||||
|
||||
static void udp_add_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
/* Do nothing */
|
||||
PJ_UNUSED_ARG(tp);
|
||||
PJ_UNUSED_ARG(alloc);
|
||||
}
|
||||
|
||||
static void udp_dec_ref(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
/* Do nothing */
|
||||
PJ_UNUSED_ARG(tp);
|
||||
PJ_UNUSED_ARG(alloc);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Callback on received packet.
|
||||
*/
|
||||
static void on_read_complete(pj_ioqueue_key_t *key,
|
||||
pj_ioqueue_op_key_t *op_key,
|
||||
pj_ssize_t bytes_read)
|
||||
{
|
||||
struct udp_listener *udp;
|
||||
struct read_op *read_op = (struct read_op*) op_key;
|
||||
pj_status_t status;
|
||||
|
||||
udp = (struct udp_listener*) pj_ioqueue_get_user_data(key);
|
||||
|
||||
do {
|
||||
pj_pool_t *rpool;
|
||||
|
||||
/* Report to server */
|
||||
if (bytes_read > 0) {
|
||||
read_op->pkt.len = bytes_read;
|
||||
pj_gettimeofday(&read_op->pkt.rx_time);
|
||||
|
||||
pj_turn_srv_on_rx_pkt(udp->base.server, &read_op->pkt);
|
||||
}
|
||||
|
||||
/* Reset pool */
|
||||
rpool = read_op->pkt.pool;
|
||||
pj_pool_reset(rpool);
|
||||
read_op->pkt.pool = rpool;
|
||||
read_op->pkt.transport = &udp->tp;
|
||||
read_op->pkt.src.tp_type = udp->base.tp_type;
|
||||
|
||||
/* Read next packet */
|
||||
bytes_read = sizeof(read_op->pkt.pkt);
|
||||
read_op->pkt.src_addr_len = sizeof(read_op->pkt.src.clt_addr);
|
||||
pj_bzero(&read_op->pkt.src.clt_addr, sizeof(read_op->pkt.src.clt_addr));
|
||||
|
||||
status = pj_ioqueue_recvfrom(udp->key, op_key,
|
||||
read_op->pkt.pkt, &bytes_read, 0,
|
||||
&read_op->pkt.src.clt_addr,
|
||||
&read_op->pkt.src_addr_len);
|
||||
|
||||
if (status != PJ_EPENDING && status != PJ_SUCCESS)
|
||||
bytes_read = -status;
|
||||
|
||||
} while (status != PJ_EPENDING && status != PJ_ECANCELLED &&
|
||||
status != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "turn.h"
|
||||
#include "auth.h"
|
||||
|
||||
#define REALM "pjsip.org"
|
||||
//#define TURN_PORT PJ_STUN_TURN_PORT
|
||||
#define TURN_PORT 34780
|
||||
#define LOG_LEVEL 4
|
||||
|
||||
|
||||
static pj_caching_pool g_cp;
|
||||
|
||||
int err(const char *title, pj_status_t status)
|
||||
{
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
|
||||
printf("%s: %s\n", title, errmsg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void dump_status(pj_turn_srv *srv)
|
||||
{
|
||||
char addr[80];
|
||||
pj_hash_iterator_t itbuf, *it;
|
||||
pj_time_val now;
|
||||
unsigned i;
|
||||
|
||||
for (i=0; i<srv->core.lis_cnt; ++i) {
|
||||
pj_turn_listener *lis = srv->core.listener[i];
|
||||
printf("Server address : %s\n", lis->info);
|
||||
}
|
||||
|
||||
printf("Worker threads : %d\n", srv->core.thread_cnt);
|
||||
printf("Total mem usage: %u.%03uMB\n", (unsigned)(g_cp.used_size / 1000000),
|
||||
(unsigned)((g_cp.used_size % 1000000)/1000));
|
||||
printf("UDP port range : %u %u %u (next/min/max)\n", srv->ports.next_udp,
|
||||
srv->ports.min_udp, srv->ports.max_udp);
|
||||
printf("TCP port range : %u %u %u (next/min/max)\n", srv->ports.next_tcp,
|
||||
srv->ports.min_tcp, srv->ports.max_tcp);
|
||||
printf("Clients # : %u\n", pj_hash_count(srv->tables.alloc));
|
||||
|
||||
puts("");
|
||||
|
||||
if (pj_hash_count(srv->tables.alloc)==0) {
|
||||
return;
|
||||
}
|
||||
|
||||
puts("# Client addr. Alloc addr. Username Lftm Expy #prm #chl");
|
||||
puts("------------------------------------------------------------------------------");
|
||||
|
||||
pj_gettimeofday(&now);
|
||||
|
||||
it = pj_hash_first(srv->tables.alloc, &itbuf);
|
||||
i=1;
|
||||
while (it) {
|
||||
pj_turn_allocation *alloc = (pj_turn_allocation*)
|
||||
pj_hash_this(srv->tables.alloc, it);
|
||||
printf("%-3d %-22s %-22s %-8.*s %-4d %-4ld %-4d %-4d\n",
|
||||
i,
|
||||
alloc->info,
|
||||
pj_sockaddr_print(&alloc->relay.hkey.addr, addr, sizeof(addr), 3),
|
||||
(int)alloc->cred.data.static_cred.username.slen,
|
||||
alloc->cred.data.static_cred.username.ptr,
|
||||
alloc->relay.lifetime,
|
||||
alloc->relay.expiry.sec - now.sec,
|
||||
pj_hash_count(alloc->peer_table),
|
||||
pj_hash_count(alloc->ch_table));
|
||||
|
||||
it = pj_hash_next(srv->tables.alloc, it);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
static void menu(void)
|
||||
{
|
||||
puts("");
|
||||
puts("Menu:");
|
||||
puts(" d Dump status");
|
||||
puts(" q Quit");
|
||||
printf(">> ");
|
||||
}
|
||||
|
||||
static void console_main(pj_turn_srv *srv)
|
||||
{
|
||||
pj_bool_t quit = PJ_FALSE;
|
||||
|
||||
while (!quit) {
|
||||
char line[10];
|
||||
|
||||
menu();
|
||||
|
||||
if (fgets(line, sizeof(line), stdin) == NULL)
|
||||
break;
|
||||
|
||||
switch (line[0]) {
|
||||
case 'd':
|
||||
dump_status(srv);
|
||||
break;
|
||||
case 'q':
|
||||
quit = PJ_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pj_turn_srv *srv;
|
||||
pj_turn_listener *listener;
|
||||
pj_status_t status;
|
||||
|
||||
status = pj_init();
|
||||
if (status != PJ_SUCCESS)
|
||||
return err("pj_init() error", status);
|
||||
|
||||
pjlib_util_init();
|
||||
pjnath_init();
|
||||
|
||||
pj_caching_pool_init(&g_cp, NULL, 0);
|
||||
|
||||
pj_turn_auth_init(REALM);
|
||||
|
||||
status = pj_turn_srv_create(&g_cp.factory, &srv);
|
||||
if (status != PJ_SUCCESS)
|
||||
return err("Error creating server", status);
|
||||
|
||||
status = pj_turn_listener_create_udp(srv, pj_AF_INET(), NULL,
|
||||
TURN_PORT, 1, 0, &listener);
|
||||
if (status != PJ_SUCCESS)
|
||||
return err("Error creating UDP listener", status);
|
||||
|
||||
#if PJ_HAS_TCP
|
||||
status = pj_turn_listener_create_tcp(srv, pj_AF_INET(), NULL,
|
||||
TURN_PORT, 1, 0, &listener);
|
||||
if (status != PJ_SUCCESS)
|
||||
return err("Error creating listener", status);
|
||||
#endif
|
||||
|
||||
status = pj_turn_srv_add_listener(srv, listener);
|
||||
if (status != PJ_SUCCESS)
|
||||
return err("Error adding listener", status);
|
||||
|
||||
puts("Server is running");
|
||||
|
||||
pj_log_set_level(LOG_LEVEL);
|
||||
|
||||
console_main(srv);
|
||||
|
||||
pj_turn_srv_destroy(srv);
|
||||
pj_caching_pool_destroy(&g_cp);
|
||||
pj_shutdown();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,698 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include "turn.h"
|
||||
#include "auth.h"
|
||||
|
||||
#define MAX_CLIENTS 32
|
||||
#define MAX_PEERS_PER_CLIENT 8
|
||||
//#define MAX_HANDLES (MAX_CLIENTS*MAX_PEERS_PER_CLIENT+MAX_LISTENERS)
|
||||
#define MAX_HANDLES PJ_IOQUEUE_MAX_HANDLES
|
||||
#define MAX_TIMER (MAX_HANDLES * 2)
|
||||
#define MIN_PORT 49152
|
||||
#define MAX_PORT 65535
|
||||
#define MAX_LISTENERS 16
|
||||
#define MAX_THREADS 2
|
||||
#define MAX_NET_EVENTS 1000
|
||||
|
||||
/* Prototypes */
|
||||
static int server_thread_proc(void *arg);
|
||||
static pj_status_t on_tx_stun_msg( pj_stun_session *sess,
|
||||
void *token,
|
||||
const void *pkt,
|
||||
pj_size_t pkt_size,
|
||||
const pj_sockaddr_t *dst_addr,
|
||||
unsigned addr_len);
|
||||
static pj_status_t on_rx_stun_request(pj_stun_session *sess,
|
||||
const pj_uint8_t *pkt,
|
||||
unsigned pkt_len,
|
||||
const pj_stun_rx_data *rdata,
|
||||
void *user_data,
|
||||
const pj_sockaddr_t *src_addr,
|
||||
unsigned src_addr_len);
|
||||
|
||||
struct saved_cred
|
||||
{
|
||||
pj_str_t realm;
|
||||
pj_str_t username;
|
||||
pj_str_t nonce;
|
||||
int data_type;
|
||||
pj_str_t data;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Get transport type name, normally for logging purpose only.
|
||||
*/
|
||||
PJ_DEF(const char*) pj_turn_tp_type_name(int tp_type)
|
||||
{
|
||||
/* Must be 3 characters long! */
|
||||
if (tp_type == PJ_TURN_TP_UDP) {
|
||||
return "UDP";
|
||||
} else if (tp_type == PJ_TURN_TP_TCP) {
|
||||
return "TCP";
|
||||
} else {
|
||||
pj_assert(!"Unsupported transport");
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create server.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_srv_create(pj_pool_factory *pf,
|
||||
pj_turn_srv **p_srv)
|
||||
{
|
||||
pj_pool_t *pool;
|
||||
pj_stun_session_cb sess_cb;
|
||||
pj_turn_srv *srv;
|
||||
unsigned i;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(pf && p_srv, PJ_EINVAL);
|
||||
|
||||
/* Create server and init core settings */
|
||||
pool = pj_pool_create(pf, "srv%p", 1000, 1000, NULL);
|
||||
srv = PJ_POOL_ZALLOC_T(pool, pj_turn_srv);
|
||||
srv->obj_name = pool->obj_name;
|
||||
srv->core.pf = pf;
|
||||
srv->core.pool = pool;
|
||||
srv->core.tls_key = srv->core.tls_data = -1;
|
||||
|
||||
/* Create ioqueue */
|
||||
status = pj_ioqueue_create(pool, MAX_HANDLES, &srv->core.ioqueue);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Server mutex */
|
||||
status = pj_lock_create_recursive_mutex(pool, srv->obj_name,
|
||||
&srv->core.lock);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Allocate TLS */
|
||||
status = pj_thread_local_alloc(&srv->core.tls_key);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
status = pj_thread_local_alloc(&srv->core.tls_data);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Create timer heap */
|
||||
status = pj_timer_heap_create(pool, MAX_TIMER, &srv->core.timer_heap);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
|
||||
/* Configure lock for the timer heap */
|
||||
pj_timer_heap_set_lock(srv->core.timer_heap, srv->core.lock, PJ_FALSE);
|
||||
|
||||
/* Array of listeners */
|
||||
srv->core.listener = (pj_turn_listener**)
|
||||
pj_pool_calloc(pool, MAX_LISTENERS,
|
||||
sizeof(srv->core.listener[0]));
|
||||
|
||||
/* Create hash tables */
|
||||
srv->tables.alloc = pj_hash_create(pool, MAX_CLIENTS);
|
||||
srv->tables.res = pj_hash_create(pool, MAX_CLIENTS);
|
||||
|
||||
/* Init ports settings */
|
||||
srv->ports.min_udp = srv->ports.next_udp = MIN_PORT;
|
||||
srv->ports.max_udp = MAX_PORT;
|
||||
srv->ports.min_tcp = srv->ports.next_tcp = MIN_PORT;
|
||||
srv->ports.max_tcp = MAX_PORT;
|
||||
|
||||
/* Init STUN config */
|
||||
pj_stun_config_init(&srv->core.stun_cfg, pf, 0, srv->core.ioqueue,
|
||||
srv->core.timer_heap);
|
||||
|
||||
/* Init STUN credential */
|
||||
srv->core.cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
|
||||
srv->core.cred.data.dyn_cred.user_data = srv;
|
||||
srv->core.cred.data.dyn_cred.get_auth = &pj_turn_get_auth;
|
||||
srv->core.cred.data.dyn_cred.get_password = &pj_turn_get_password;
|
||||
srv->core.cred.data.dyn_cred.verify_nonce = &pj_turn_verify_nonce;
|
||||
|
||||
/* Create STUN session to handle new allocation */
|
||||
pj_bzero(&sess_cb, sizeof(sess_cb));
|
||||
sess_cb.on_rx_request = &on_rx_stun_request;
|
||||
sess_cb.on_send_msg = &on_tx_stun_msg;
|
||||
|
||||
status = pj_stun_session_create(&srv->core.stun_cfg, srv->obj_name,
|
||||
&sess_cb, PJ_FALSE, NULL,
|
||||
&srv->core.stun_sess);
|
||||
if (status != PJ_SUCCESS) {
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
pj_stun_session_set_user_data(srv->core.stun_sess, srv);
|
||||
pj_stun_session_set_credential(srv->core.stun_sess, PJ_STUN_AUTH_LONG_TERM,
|
||||
&srv->core.cred);
|
||||
|
||||
|
||||
/* Array of worker threads */
|
||||
srv->core.thread_cnt = MAX_THREADS;
|
||||
srv->core.thread = (pj_thread_t**)
|
||||
pj_pool_calloc(pool, srv->core.thread_cnt,
|
||||
sizeof(pj_thread_t*));
|
||||
|
||||
/* Start the worker threads */
|
||||
for (i=0; i<srv->core.thread_cnt; ++i) {
|
||||
status = pj_thread_create(pool, srv->obj_name, &server_thread_proc,
|
||||
srv, 0, 0, &srv->core.thread[i]);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
/* We're done. Application should add listeners now */
|
||||
PJ_LOG(4,(srv->obj_name, "TURN server v%s is running",
|
||||
pj_get_version()));
|
||||
|
||||
*p_srv = srv;
|
||||
return PJ_SUCCESS;
|
||||
|
||||
on_error:
|
||||
pj_turn_srv_destroy(srv);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle timer and network events
|
||||
*/
|
||||
static void srv_handle_events(pj_turn_srv *srv, const pj_time_val *max_timeout)
|
||||
{
|
||||
/* timeout is 'out' var. This just to make compiler happy. */
|
||||
pj_time_val timeout = { 0, 0};
|
||||
unsigned net_event_count = 0;
|
||||
int c;
|
||||
|
||||
/* Poll the timer. The timer heap has its own mutex for better
|
||||
* granularity, so we don't need to lock the server.
|
||||
*/
|
||||
timeout.sec = timeout.msec = 0;
|
||||
c = pj_timer_heap_poll( srv->core.timer_heap, &timeout );
|
||||
|
||||
/* timer_heap_poll should never ever returns negative value, or otherwise
|
||||
* ioqueue_poll() will block forever!
|
||||
*/
|
||||
pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
|
||||
if (timeout.msec >= 1000) timeout.msec = 999;
|
||||
|
||||
/* If caller specifies maximum time to wait, then compare the value with
|
||||
* the timeout to wait from timer, and use the minimum value.
|
||||
*/
|
||||
if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) {
|
||||
timeout = *max_timeout;
|
||||
}
|
||||
|
||||
/* Poll ioqueue.
|
||||
* Repeat polling the ioqueue while we have immediate events, because
|
||||
* timer heap may process more than one events, so if we only process
|
||||
* one network events at a time (such as when IOCP backend is used),
|
||||
* the ioqueue may have trouble keeping up with the request rate.
|
||||
*
|
||||
* For example, for each send() request, one network event will be
|
||||
* reported by ioqueue for the send() completion. If we don't poll
|
||||
* the ioqueue often enough, the send() completion will not be
|
||||
* reported in timely manner.
|
||||
*/
|
||||
do {
|
||||
c = pj_ioqueue_poll( srv->core.ioqueue, &timeout);
|
||||
if (c < 0) {
|
||||
pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
|
||||
return;
|
||||
} else if (c == 0) {
|
||||
break;
|
||||
} else {
|
||||
net_event_count += c;
|
||||
timeout.sec = timeout.msec = 0;
|
||||
}
|
||||
} while (c > 0 && net_event_count < MAX_NET_EVENTS);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Server worker thread proc.
|
||||
*/
|
||||
static int server_thread_proc(void *arg)
|
||||
{
|
||||
pj_turn_srv *srv = (pj_turn_srv*)arg;
|
||||
|
||||
while (!srv->core.quit) {
|
||||
pj_time_val timeout_max = {0, 100};
|
||||
srv_handle_events(srv, &timeout_max);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the server.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_srv_destroy(pj_turn_srv *srv)
|
||||
{
|
||||
pj_hash_iterator_t itbuf, *it;
|
||||
unsigned i;
|
||||
|
||||
/* Stop all worker threads */
|
||||
srv->core.quit = PJ_TRUE;
|
||||
for (i=0; i<srv->core.thread_cnt; ++i) {
|
||||
if (srv->core.thread[i]) {
|
||||
pj_thread_join(srv->core.thread[i]);
|
||||
pj_thread_destroy(srv->core.thread[i]);
|
||||
srv->core.thread[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Destroy all allocations FIRST */
|
||||
if (srv->tables.alloc) {
|
||||
it = pj_hash_first(srv->tables.alloc, &itbuf);
|
||||
while (it != NULL) {
|
||||
pj_turn_allocation *alloc = (pj_turn_allocation*)
|
||||
pj_hash_this(srv->tables.alloc, it);
|
||||
pj_hash_iterator_t *next = pj_hash_next(srv->tables.alloc, it);
|
||||
pj_turn_allocation_destroy(alloc);
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Destroy all listeners. */
|
||||
for (i=0; i<srv->core.lis_cnt; ++i) {
|
||||
if (srv->core.listener[i]) {
|
||||
pj_turn_listener_destroy(srv->core.listener[i]);
|
||||
srv->core.listener[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Destroy STUN session */
|
||||
if (srv->core.stun_sess) {
|
||||
pj_stun_session_destroy(srv->core.stun_sess);
|
||||
srv->core.stun_sess = NULL;
|
||||
}
|
||||
|
||||
/* Destroy hash tables (well, sort of) */
|
||||
if (srv->tables.alloc) {
|
||||
srv->tables.alloc = NULL;
|
||||
srv->tables.res = NULL;
|
||||
}
|
||||
|
||||
/* Destroy timer heap */
|
||||
if (srv->core.timer_heap) {
|
||||
pj_timer_heap_destroy(srv->core.timer_heap);
|
||||
srv->core.timer_heap = NULL;
|
||||
}
|
||||
|
||||
/* Destroy ioqueue */
|
||||
if (srv->core.ioqueue) {
|
||||
pj_ioqueue_destroy(srv->core.ioqueue);
|
||||
srv->core.ioqueue = NULL;
|
||||
}
|
||||
|
||||
/* Destroy thread local IDs */
|
||||
if (srv->core.tls_key != -1) {
|
||||
pj_thread_local_free(srv->core.tls_key);
|
||||
srv->core.tls_key = -1;
|
||||
}
|
||||
if (srv->core.tls_data != -1) {
|
||||
pj_thread_local_free(srv->core.tls_data);
|
||||
srv->core.tls_data = -1;
|
||||
}
|
||||
|
||||
/* Destroy server lock */
|
||||
if (srv->core.lock) {
|
||||
pj_lock_destroy(srv->core.lock);
|
||||
srv->core.lock = NULL;
|
||||
}
|
||||
|
||||
/* Release pool */
|
||||
if (srv->core.pool) {
|
||||
pj_pool_t *pool = srv->core.pool;
|
||||
srv->core.pool = NULL;
|
||||
pj_pool_release(pool);
|
||||
}
|
||||
|
||||
/* Done */
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add listener.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_srv_add_listener(pj_turn_srv *srv,
|
||||
pj_turn_listener *lis)
|
||||
{
|
||||
unsigned index;
|
||||
|
||||
PJ_ASSERT_RETURN(srv && lis, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(srv->core.lis_cnt < MAX_LISTENERS, PJ_ETOOMANY);
|
||||
|
||||
/* Add to array */
|
||||
index = srv->core.lis_cnt;
|
||||
srv->core.listener[index] = lis;
|
||||
lis->server = srv;
|
||||
lis->id = index;
|
||||
srv->core.lis_cnt++;
|
||||
|
||||
PJ_LOG(4,(srv->obj_name, "Listener %s/%s added at index %d",
|
||||
lis->obj_name, lis->info, lis->id));
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy listener.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_listener_destroy(pj_turn_listener *listener)
|
||||
{
|
||||
pj_turn_srv *srv = listener->server;
|
||||
unsigned i;
|
||||
|
||||
/* Remove from our listener list */
|
||||
pj_lock_acquire(srv->core.lock);
|
||||
for (i=0; i<srv->core.lis_cnt; ++i) {
|
||||
if (srv->core.listener[i] == listener) {
|
||||
srv->core.listener[i] = NULL;
|
||||
srv->core.lis_cnt--;
|
||||
listener->id = PJ_TURN_INVALID_LIS_ID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pj_lock_release(srv->core.lock);
|
||||
|
||||
/* Destroy */
|
||||
return listener->destroy(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a reference to a transport.
|
||||
*/
|
||||
PJ_DEF(void) pj_turn_transport_add_ref( pj_turn_transport *transport,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
transport->add_ref(transport, alloc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrement transport reference counter.
|
||||
*/
|
||||
PJ_DEF(void) pj_turn_transport_dec_ref( pj_turn_transport *transport,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
transport->dec_ref(transport, alloc);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Register an allocation to the hash tables.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_srv_register_allocation(pj_turn_srv *srv,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
/* Add to hash tables */
|
||||
pj_lock_acquire(srv->core.lock);
|
||||
pj_hash_set(alloc->pool, srv->tables.alloc,
|
||||
&alloc->hkey, sizeof(alloc->hkey), 0, alloc);
|
||||
pj_hash_set(alloc->pool, srv->tables.res,
|
||||
&alloc->relay.hkey, sizeof(alloc->relay.hkey), 0,
|
||||
&alloc->relay);
|
||||
pj_lock_release(srv->core.lock);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Unregister an allocation from the hash tables.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pj_turn_srv_unregister_allocation(pj_turn_srv *srv,
|
||||
pj_turn_allocation *alloc)
|
||||
{
|
||||
/* Unregister from hash tables */
|
||||
pj_lock_acquire(srv->core.lock);
|
||||
pj_hash_set(alloc->pool, srv->tables.alloc,
|
||||
&alloc->hkey, sizeof(alloc->hkey), 0, NULL);
|
||||
pj_hash_set(alloc->pool, srv->tables.res,
|
||||
&alloc->relay.hkey, sizeof(alloc->relay.hkey), 0, NULL);
|
||||
pj_lock_release(srv->core.lock);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/* Callback from our own STUN session whenever it needs to send
|
||||
* outgoing STUN packet.
|
||||
*/
|
||||
static pj_status_t on_tx_stun_msg( pj_stun_session *sess,
|
||||
void *token,
|
||||
const void *pdu,
|
||||
pj_size_t pdu_size,
|
||||
const pj_sockaddr_t *dst_addr,
|
||||
unsigned addr_len)
|
||||
{
|
||||
pj_turn_transport *transport = (pj_turn_transport*) token;
|
||||
|
||||
PJ_ASSERT_RETURN(transport!=NULL, PJ_EINVALIDOP);
|
||||
|
||||
PJ_UNUSED_ARG(sess);
|
||||
|
||||
return transport->sendto(transport, pdu, pdu_size, 0,
|
||||
dst_addr, addr_len);
|
||||
}
|
||||
|
||||
|
||||
/* Respond to STUN request */
|
||||
static pj_status_t stun_respond(pj_stun_session *sess,
|
||||
pj_turn_transport *transport,
|
||||
const pj_stun_rx_data *rdata,
|
||||
unsigned code,
|
||||
const char *errmsg,
|
||||
pj_bool_t cache,
|
||||
const pj_sockaddr_t *dst_addr,
|
||||
unsigned addr_len)
|
||||
{
|
||||
pj_status_t status;
|
||||
pj_str_t reason;
|
||||
pj_stun_tx_data *tdata;
|
||||
|
||||
/* Create response */
|
||||
status = pj_stun_session_create_res(sess, rdata, code,
|
||||
(errmsg?pj_cstr(&reason,errmsg):NULL),
|
||||
&tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
/* Send the response */
|
||||
return pj_stun_session_send_msg(sess, transport, cache, PJ_FALSE,
|
||||
dst_addr, addr_len, tdata);
|
||||
}
|
||||
|
||||
|
||||
/* Callback from our own STUN session when incoming request arrives.
|
||||
* This function is triggered by pj_stun_session_on_rx_pkt() call in
|
||||
* pj_turn_srv_on_rx_pkt() function below.
|
||||
*/
|
||||
static pj_status_t on_rx_stun_request(pj_stun_session *sess,
|
||||
const pj_uint8_t *pdu,
|
||||
unsigned pdu_len,
|
||||
const pj_stun_rx_data *rdata,
|
||||
void *token,
|
||||
const pj_sockaddr_t *src_addr,
|
||||
unsigned src_addr_len)
|
||||
{
|
||||
pj_turn_transport *transport;
|
||||
const pj_stun_msg *msg = rdata->msg;
|
||||
pj_turn_allocation *alloc;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_UNUSED_ARG(pdu);
|
||||
PJ_UNUSED_ARG(pdu_len);
|
||||
|
||||
transport = (pj_turn_transport*) token;
|
||||
|
||||
/* Respond any requests other than ALLOCATE with 437 response */
|
||||
if (msg->hdr.type != PJ_STUN_ALLOCATE_REQUEST) {
|
||||
stun_respond(sess, transport, rdata, PJ_STUN_SC_ALLOCATION_MISMATCH,
|
||||
NULL, PJ_FALSE, src_addr, src_addr_len);
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/* Create new allocation. The relay resource will be allocated
|
||||
* in this function.
|
||||
*/
|
||||
status = pj_turn_allocation_create(transport, src_addr, src_addr_len,
|
||||
rdata, sess, &alloc);
|
||||
if (status != PJ_SUCCESS) {
|
||||
/* STUN response has been sent, no need to reply here */
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/* Done. */
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
/* Handle STUN Binding request */
|
||||
static void handle_binding_request(pj_turn_pkt *pkt,
|
||||
unsigned options)
|
||||
{
|
||||
pj_stun_msg *request, *response;
|
||||
pj_uint8_t pdu[200];
|
||||
pj_size_t len;
|
||||
pj_status_t status;
|
||||
|
||||
/* Decode request */
|
||||
status = pj_stun_msg_decode(pkt->pool, pkt->pkt, pkt->len, options,
|
||||
&request, NULL, NULL);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Create response */
|
||||
status = pj_stun_msg_create_response(pkt->pool, request, 0, NULL,
|
||||
&response);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Add XOR-MAPPED-ADDRESS */
|
||||
pj_stun_msg_add_sockaddr_attr(pkt->pool, response,
|
||||
PJ_STUN_ATTR_XOR_MAPPED_ADDR,
|
||||
PJ_TRUE,
|
||||
&pkt->src.clt_addr,
|
||||
pkt->src_addr_len);
|
||||
|
||||
/* Encode */
|
||||
status = pj_stun_msg_encode(response, pdu, sizeof(pdu), 0, NULL, &len);
|
||||
if (status != PJ_SUCCESS)
|
||||
return;
|
||||
|
||||
/* Send response */
|
||||
pkt->transport->sendto(pkt->transport, pdu, len, 0,
|
||||
&pkt->src.clt_addr, pkt->src_addr_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* This callback is called by UDP listener on incoming packet. This is
|
||||
* the first entry for incoming packet (from client) to the server. From
|
||||
* here, the packet may be handed over to an allocation if an allocation
|
||||
* is found for the client address, or handed over to owned STUN session
|
||||
* if an allocation is not found.
|
||||
*/
|
||||
PJ_DEF(void) pj_turn_srv_on_rx_pkt(pj_turn_srv *srv,
|
||||
pj_turn_pkt *pkt)
|
||||
{
|
||||
pj_turn_allocation *alloc;
|
||||
|
||||
/* Get TURN allocation from the source address */
|
||||
pj_lock_acquire(srv->core.lock);
|
||||
alloc = (pj_turn_allocation*)
|
||||
pj_hash_get(srv->tables.alloc, &pkt->src, sizeof(pkt->src), NULL);
|
||||
pj_lock_release(srv->core.lock);
|
||||
|
||||
/* If allocation is found, just hand over the packet to the
|
||||
* allocation.
|
||||
*/
|
||||
if (alloc) {
|
||||
pj_turn_allocation_on_rx_client_pkt(alloc, pkt);
|
||||
} else {
|
||||
/* Otherwise this is a new client */
|
||||
unsigned options;
|
||||
pj_size_t parsed_len;
|
||||
pj_status_t status;
|
||||
|
||||
/* Check that this is a STUN message */
|
||||
options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK;
|
||||
if (pkt->transport->listener->tp_type == PJ_TURN_TP_UDP)
|
||||
options |= PJ_STUN_IS_DATAGRAM;
|
||||
|
||||
status = pj_stun_msg_check(pkt->pkt, pkt->len, options);
|
||||
if (status != PJ_SUCCESS) {
|
||||
/* If the first byte are not STUN, drop the packet. First byte
|
||||
* of STUN message is always 0x00 or 0x01. Otherwise wait for
|
||||
* more data as the data might have come from TCP.
|
||||
*
|
||||
* Also drop packet if it's unreasonably too big, as this might
|
||||
* indicate invalid data that's building up in the buffer.
|
||||
*
|
||||
* Or if packet is a datagram.
|
||||
*/
|
||||
if ((*pkt->pkt != 0x00 && *pkt->pkt != 0x01) ||
|
||||
pkt->len > 1600 ||
|
||||
(options & PJ_STUN_IS_DATAGRAM))
|
||||
{
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
char ip[PJ_INET6_ADDRSTRLEN+10];
|
||||
|
||||
pkt->len = 0;
|
||||
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(5,(srv->obj_name,
|
||||
"Non-STUN packet from %s is dropped: %s",
|
||||
pj_sockaddr_print(&pkt->src.clt_addr, ip, sizeof(ip), 3),
|
||||
errmsg));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Special handling for Binding Request. We won't give it to the
|
||||
* STUN session since this request is not authenticated.
|
||||
*/
|
||||
if (pkt->pkt[1] == 1) {
|
||||
handle_binding_request(pkt, options);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Hand over processing to STUN session. This will trigger
|
||||
* on_rx_stun_request() callback to be called if the STUN
|
||||
* message is a request.
|
||||
*/
|
||||
options &= ~PJ_STUN_CHECK_PACKET;
|
||||
parsed_len = 0;
|
||||
status = pj_stun_session_on_rx_pkt(srv->core.stun_sess, pkt->pkt,
|
||||
pkt->len, options, pkt->transport,
|
||||
&parsed_len, &pkt->src.clt_addr,
|
||||
pkt->src_addr_len);
|
||||
if (status != PJ_SUCCESS) {
|
||||
char errmsg[PJ_ERR_MSG_SIZE];
|
||||
char ip[PJ_INET6_ADDRSTRLEN+10];
|
||||
|
||||
pj_strerror(status, errmsg, sizeof(errmsg));
|
||||
PJ_LOG(5,(srv->obj_name,
|
||||
"Error processing STUN packet from %s: %s",
|
||||
pj_sockaddr_print(&pkt->src.clt_addr, ip, sizeof(ip), 3),
|
||||
errmsg));
|
||||
}
|
||||
|
||||
if (pkt->transport->listener->tp_type == PJ_TURN_TP_UDP) {
|
||||
pkt->len = 0;
|
||||
} else if (parsed_len > 0) {
|
||||
if (parsed_len == pkt->len) {
|
||||
pkt->len = 0;
|
||||
} else {
|
||||
pj_memmove(pkt->pkt, pkt->pkt+parsed_len,
|
||||
pkt->len - parsed_len);
|
||||
pkt->len -= parsed_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
/* $Id$ */
|
||||
/*
|
||||
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
||||
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef __PJ_TURN_SRV_TURN_H__
|
||||
#define __PJ_TURN_SRV_TURN_H__
|
||||
|
||||
#include <pjlib.h>
|
||||
#include <pjnath.h>
|
||||
|
||||
typedef struct pj_turn_relay_res pj_turn_relay_res;
|
||||
typedef struct pj_turn_listener pj_turn_listener;
|
||||
typedef struct pj_turn_transport pj_turn_transport;
|
||||
typedef struct pj_turn_permission pj_turn_permission;
|
||||
typedef struct pj_turn_allocation pj_turn_allocation;
|
||||
typedef struct pj_turn_srv pj_turn_srv;
|
||||
typedef struct pj_turn_pkt pj_turn_pkt;
|
||||
|
||||
|
||||
#define PJ_TURN_INVALID_LIS_ID ((unsigned)-1)
|
||||
|
||||
/**
|
||||
* Get transport type name string.
|
||||
*/
|
||||
PJ_DECL(const char*) pj_turn_tp_type_name(int tp_type);
|
||||
|
||||
/**
|
||||
* This structure describes TURN relay resource. An allocation allocates
|
||||
* one relay resource, and optionally it may reserve another resource.
|
||||
*/
|
||||
struct pj_turn_relay_res
|
||||
{
|
||||
/** Hash table key */
|
||||
struct {
|
||||
/** Transport type. */
|
||||
int tp_type;
|
||||
|
||||
/** Transport/relay address */
|
||||
pj_sockaddr addr;
|
||||
} hkey;
|
||||
|
||||
/** Allocation who requested or reserved this resource. */
|
||||
pj_turn_allocation *allocation;
|
||||
|
||||
/** Username used in credential */
|
||||
pj_str_t user;
|
||||
|
||||
/** Realm used in credential. */
|
||||
pj_str_t realm;
|
||||
|
||||
/** Lifetime, in seconds. */
|
||||
unsigned lifetime;
|
||||
|
||||
/** Relay/allocation expiration time */
|
||||
pj_time_val expiry;
|
||||
|
||||
/** Timeout timer entry */
|
||||
pj_timer_entry timer;
|
||||
|
||||
/** Transport. */
|
||||
struct {
|
||||
/** Transport/relay socket */
|
||||
pj_sock_t sock;
|
||||
|
||||
/** Transport/relay ioqueue */
|
||||
pj_ioqueue_key_t *key;
|
||||
|
||||
/** Read operation key. */
|
||||
pj_ioqueue_op_key_t read_key;
|
||||
|
||||
/** The incoming packet buffer */
|
||||
char rx_pkt[PJ_TURN_MAX_PKT_LEN];
|
||||
|
||||
/** Source address of the packet. */
|
||||
pj_sockaddr src_addr;
|
||||
|
||||
/** Source address length */
|
||||
int src_addr_len;
|
||||
|
||||
/** The outgoing packet buffer. This must be 3wbit aligned. */
|
||||
char tx_pkt[PJ_TURN_MAX_PKT_LEN+4];
|
||||
} tp;
|
||||
};
|
||||
|
||||
|
||||
/****************************************************************************/
|
||||
/*
|
||||
* TURN Allocation API
|
||||
*/
|
||||
|
||||
/**
|
||||
* This structure describes key to lookup TURN allocations in the
|
||||
* allocation hash table.
|
||||
*/
|
||||
typedef struct pj_turn_allocation_key
|
||||
{
|
||||
int tp_type; /**< Transport type. */
|
||||
pj_sockaddr clt_addr; /**< Client's address. */
|
||||
} pj_turn_allocation_key;
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes TURN pj_turn_allocation session.
|
||||
*/
|
||||
struct pj_turn_allocation
|
||||
{
|
||||
/** Hash table key to identify client. */
|
||||
pj_turn_allocation_key hkey;
|
||||
|
||||
/** Pool for this allocation. */
|
||||
pj_pool_t *pool;
|
||||
|
||||
/** Object name for logging identification */
|
||||
char *obj_name;
|
||||
|
||||
/** Client info (IP address and port) */
|
||||
char info[80];
|
||||
|
||||
/** Mutex */
|
||||
pj_lock_t *lock;
|
||||
|
||||
/** Server instance. */
|
||||
pj_turn_srv *server;
|
||||
|
||||
/** Transport to send/receive packets to/from client. */
|
||||
pj_turn_transport *transport;
|
||||
|
||||
/** The relay resource for this allocation. */
|
||||
pj_turn_relay_res relay;
|
||||
|
||||
/** Relay resource reserved by this allocation, if any */
|
||||
pj_turn_relay_res *resv;
|
||||
|
||||
/** Requested bandwidth */
|
||||
unsigned bandwidth;
|
||||
|
||||
/** STUN session for this client */
|
||||
pj_stun_session *sess;
|
||||
|
||||
/** Credential for this STUN session. */
|
||||
pj_stun_auth_cred cred;
|
||||
|
||||
/** Peer hash table (keyed by peer address) */
|
||||
pj_hash_table_t *peer_table;
|
||||
|
||||
/** Channel hash table (keyed by channel number) */
|
||||
pj_hash_table_t *ch_table;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes the hash table key to lookup TURN
|
||||
* permission.
|
||||
*/
|
||||
typedef struct pj_turn_permission_key
|
||||
{
|
||||
/** Peer address. */
|
||||
pj_sockaddr peer_addr;
|
||||
|
||||
} pj_turn_permission_key;
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes TURN pj_turn_permission or channel.
|
||||
*/
|
||||
struct pj_turn_permission
|
||||
{
|
||||
/** Hash table key */
|
||||
pj_turn_permission_key hkey;
|
||||
|
||||
/** TURN allocation that owns this permission/channel */
|
||||
pj_turn_allocation *allocation;
|
||||
|
||||
/** Optional channel number, or PJ_TURN_INVALID_CHANNEL if channel number
|
||||
* is not requested for this permission.
|
||||
*/
|
||||
pj_uint16_t channel;
|
||||
|
||||
/** Permission expiration time. */
|
||||
pj_time_val expiry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new allocation.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_allocation_create(pj_turn_transport *transport,
|
||||
const pj_sockaddr_t *src_addr,
|
||||
unsigned src_addr_len,
|
||||
const pj_stun_rx_data *rdata,
|
||||
pj_stun_session *srv_sess,
|
||||
pj_turn_allocation **p_alloc);
|
||||
/**
|
||||
* Destroy allocation.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_allocation_destroy(pj_turn_allocation *alloc);
|
||||
|
||||
|
||||
/**
|
||||
* Handle incoming packet from client.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_allocation_on_rx_client_pkt(pj_turn_allocation *alloc,
|
||||
pj_turn_pkt *pkt);
|
||||
|
||||
/**
|
||||
* Handle transport closure.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_allocation_on_transport_closed(pj_turn_allocation *alloc,
|
||||
pj_turn_transport *tp);
|
||||
|
||||
/****************************************************************************/
|
||||
/*
|
||||
* TURN Listener API
|
||||
*/
|
||||
|
||||
/**
|
||||
* This structure describes TURN listener socket. A TURN listener socket
|
||||
* listens for incoming connections from clients.
|
||||
*/
|
||||
struct pj_turn_listener
|
||||
{
|
||||
/** Object name/identification */
|
||||
char *obj_name;
|
||||
|
||||
/** Slightly longer info about this listener */
|
||||
char info[80];
|
||||
|
||||
/** TURN server instance. */
|
||||
pj_turn_srv *server;
|
||||
|
||||
/** Listener index in the server */
|
||||
unsigned id;
|
||||
|
||||
/** Pool for this listener. */
|
||||
pj_pool_t *pool;
|
||||
|
||||
/** Transport type. */
|
||||
int tp_type;
|
||||
|
||||
/** Bound address of this listener. */
|
||||
pj_sockaddr addr;
|
||||
|
||||
/** Socket. */
|
||||
pj_sock_t sock;
|
||||
|
||||
/** Flags. */
|
||||
unsigned flags;
|
||||
|
||||
/** Destroy handler */
|
||||
pj_status_t (*destroy)(pj_turn_listener*);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This structure describes TURN transport socket which is used to send and
|
||||
* receive packets towards client.
|
||||
*/
|
||||
struct pj_turn_transport
|
||||
{
|
||||
/** Object name/identification */
|
||||
char *obj_name;
|
||||
|
||||
/** Slightly longer info about this listener */
|
||||
char *info;
|
||||
|
||||
/** Listener instance */
|
||||
pj_turn_listener *listener;
|
||||
|
||||
/** Sendto handler */
|
||||
pj_status_t (*sendto)(pj_turn_transport *tp,
|
||||
const void *packet,
|
||||
pj_size_t size,
|
||||
unsigned flag,
|
||||
const pj_sockaddr_t *addr,
|
||||
int addr_len);
|
||||
|
||||
/** Addref handler */
|
||||
void (*add_ref)(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
/** Decref handler */
|
||||
void (*dec_ref)(pj_turn_transport *tp,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An incoming packet.
|
||||
*/
|
||||
struct pj_turn_pkt
|
||||
{
|
||||
/** Pool for this packet */
|
||||
pj_pool_t *pool;
|
||||
|
||||
/** Transport where the packet was received. */
|
||||
pj_turn_transport *transport;
|
||||
|
||||
/** Packet buffer (must be 32bit aligned). */
|
||||
pj_uint8_t pkt[PJ_TURN_MAX_PKT_LEN];
|
||||
|
||||
/** Size of the packet */
|
||||
pj_size_t len;
|
||||
|
||||
/** Arrival time. */
|
||||
pj_time_val rx_time;
|
||||
|
||||
/** Source transport type and source address. */
|
||||
pj_turn_allocation_key src;
|
||||
|
||||
/** Source address length. */
|
||||
int src_addr_len;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a UDP listener on the specified port.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_listener_create_udp(pj_turn_srv *srv,
|
||||
int af,
|
||||
const pj_str_t *bound_addr,
|
||||
unsigned port,
|
||||
unsigned concurrency_cnt,
|
||||
unsigned flags,
|
||||
pj_turn_listener **p_lis);
|
||||
|
||||
/**
|
||||
* Create a TCP listener on the specified port.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_listener_create_tcp(pj_turn_srv *srv,
|
||||
int af,
|
||||
const pj_str_t *bound_addr,
|
||||
unsigned port,
|
||||
unsigned concurrency_cnt,
|
||||
unsigned flags,
|
||||
pj_turn_listener **p_lis);
|
||||
|
||||
/**
|
||||
* Destroy listener.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_listener_destroy(pj_turn_listener *listener);
|
||||
|
||||
|
||||
/**
|
||||
* Add a reference to a transport.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_transport_add_ref(pj_turn_transport *transport,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
|
||||
/**
|
||||
* Decrement transport reference counter.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_transport_dec_ref(pj_turn_transport *transport,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
|
||||
|
||||
/****************************************************************************/
|
||||
/*
|
||||
* TURN Server API
|
||||
*/
|
||||
/**
|
||||
* This structure describes TURN pj_turn_srv instance.
|
||||
*/
|
||||
struct pj_turn_srv
|
||||
{
|
||||
/** Object name */
|
||||
char *obj_name;
|
||||
|
||||
/** Core settings */
|
||||
struct {
|
||||
/** Pool factory */
|
||||
pj_pool_factory *pf;
|
||||
|
||||
/** Pool for this server instance. */
|
||||
pj_pool_t *pool;
|
||||
|
||||
/** Global Ioqueue */
|
||||
pj_ioqueue_t *ioqueue;
|
||||
|
||||
/** Mutex */
|
||||
pj_lock_t *lock;
|
||||
|
||||
/** Global timer heap instance. */
|
||||
pj_timer_heap_t *timer_heap;
|
||||
|
||||
/** Number of listeners */
|
||||
unsigned lis_cnt;
|
||||
|
||||
/** Array of listeners. */
|
||||
pj_turn_listener **listener;
|
||||
|
||||
/** STUN session to handle initial Allocate request. */
|
||||
pj_stun_session *stun_sess;
|
||||
|
||||
/** Number of worker threads. */
|
||||
unsigned thread_cnt;
|
||||
|
||||
/** Array of worker threads. */
|
||||
pj_thread_t **thread;
|
||||
|
||||
/** Thread quit signal */
|
||||
pj_bool_t quit;
|
||||
|
||||
/** STUN config. */
|
||||
pj_stun_config stun_cfg;
|
||||
|
||||
/** STUN auth credential. */
|
||||
pj_stun_auth_cred cred;
|
||||
|
||||
/** Thread local ID for storing credential */
|
||||
long tls_key, tls_data;
|
||||
|
||||
} core;
|
||||
|
||||
|
||||
/** Hash tables */
|
||||
struct {
|
||||
/** Allocations hash table, indexed by transport type and
|
||||
* client address.
|
||||
*/
|
||||
pj_hash_table_t *alloc;
|
||||
|
||||
/** Relay resource hash table, indexed by transport type and
|
||||
* relay address.
|
||||
*/
|
||||
pj_hash_table_t *res;
|
||||
|
||||
} tables;
|
||||
|
||||
/** Ports settings */
|
||||
struct {
|
||||
/** Minimum UDP port number. */
|
||||
pj_uint16_t min_udp;
|
||||
|
||||
/** Maximum UDP port number. */
|
||||
pj_uint16_t max_udp;
|
||||
|
||||
/** Next UDP port number. */
|
||||
pj_uint16_t next_udp;
|
||||
|
||||
|
||||
/** Minimum TCP port number. */
|
||||
pj_uint16_t min_tcp;
|
||||
|
||||
/** Maximum TCP port number. */
|
||||
pj_uint16_t max_tcp;
|
||||
|
||||
/** Next TCP port number. */
|
||||
pj_uint16_t next_tcp;
|
||||
|
||||
} ports;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create server.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_srv_create(pj_pool_factory *pf,
|
||||
pj_turn_srv **p_srv);
|
||||
|
||||
/**
|
||||
* Destroy server.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_srv_destroy(pj_turn_srv *srv);
|
||||
|
||||
/**
|
||||
* Add listener.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_srv_add_listener(pj_turn_srv *srv,
|
||||
pj_turn_listener *lis);
|
||||
|
||||
/**
|
||||
* Register an allocation.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_srv_register_allocation(pj_turn_srv *srv,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
/**
|
||||
* Unregister an allocation.
|
||||
*/
|
||||
PJ_DECL(pj_status_t) pj_turn_srv_unregister_allocation(pj_turn_srv *srv,
|
||||
pj_turn_allocation *alloc);
|
||||
|
||||
/**
|
||||
* This callback is called by UDP listener on incoming packet.
|
||||
*/
|
||||
PJ_DECL(void) pj_turn_srv_on_rx_pkt(pj_turn_srv *srv,
|
||||
pj_turn_pkt *pkt);
|
||||
|
||||
|
||||
#endif /* __PJ_TURN_SRV_TURN_H__ */
|
||||
|
||||
Reference in new issue
Block a user