initial commit

This commit is contained in:
Dan Bastone committed 2021-10-29 14:41:03 -04:00
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, &current_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;
}
+212
View File
@@ -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__ */