blob: 96959738b88ad1e64d2e2e892b09b459e96a2b9a [file] [log] [blame]
/* $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_session.h>
#include <pjnath/errno.h>
#include <pjlib.h>
struct pj_stun_session
{
pj_stun_config *cfg;
pj_pool_t *pool;
pj_grp_lock_t *grp_lock;
pj_stun_session_cb cb;
void *user_data;
pj_bool_t is_destroying;
pj_bool_t use_fingerprint;
pj_pool_t *rx_pool;
#if PJ_LOG_MAX_LEVEL >= 5
char dump_buf[1000];
#endif
unsigned log_flag;
pj_stun_auth_type auth_type;
pj_stun_auth_cred cred;
int auth_retry;
pj_str_t next_nonce;
pj_str_t server_realm;
pj_str_t srv_name;
pj_stun_tx_data pending_request_list;
pj_stun_tx_data cached_response_list;
};
#define SNAME(s_) ((s_)->pool->obj_name)
#define THIS_FILE "stun_session.c"
#if 1
# define TRACE_(expr) PJ_LOG(5,expr)
#else
# define TRACE_(expr)
#endif
#define LOG_ERR_(sess,title,rc) PJ_PERROR(3,(sess->pool->obj_name,rc,title))
#define TDATA_POOL_SIZE PJNATH_POOL_LEN_STUN_TDATA
#define TDATA_POOL_INC PJNATH_POOL_INC_STUN_TDATA
static void stun_tsx_on_complete(pj_stun_client_tsx *tsx,
pj_status_t status,
const pj_stun_msg *response,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len);
static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx,
const void *stun_pkt,
pj_size_t pkt_size);
static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx);
static void stun_sess_on_destroy(void *comp);
static pj_stun_tsx_cb tsx_cb =
{
&stun_tsx_on_complete,
&stun_tsx_on_send_msg,
&stun_tsx_on_destroy
};
static pj_status_t tsx_add(pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
pj_list_push_front(&sess->pending_request_list, tdata);
return PJ_SUCCESS;
}
static pj_status_t tsx_erase(pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
PJ_UNUSED_ARG(sess);
pj_list_erase(tdata);
return PJ_SUCCESS;
}
static pj_stun_tx_data* tsx_lookup(pj_stun_session *sess,
const pj_stun_msg *msg)
{
pj_stun_tx_data *tdata;
tdata = sess->pending_request_list.next;
while (tdata != &sess->pending_request_list) {
pj_assert(sizeof(tdata->msg_key)==sizeof(msg->hdr.tsx_id));
if (tdata->msg_magic == msg->hdr.magic &&
pj_memcmp(tdata->msg_key, msg->hdr.tsx_id,
sizeof(msg->hdr.tsx_id))==0)
{
return tdata;
}
tdata = tdata->next;
}
return NULL;
}
static pj_status_t create_tdata(pj_stun_session *sess,
pj_stun_tx_data **p_tdata)
{
pj_pool_t *pool;
pj_stun_tx_data *tdata;
/* Create pool and initialize basic tdata attributes */
pool = pj_pool_create(sess->cfg->pf, "tdata%p",
TDATA_POOL_SIZE, TDATA_POOL_INC, NULL);
PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
tdata = PJ_POOL_ZALLOC_T(pool, pj_stun_tx_data);
tdata->pool = pool;
tdata->sess = sess;
pj_list_init(tdata);
*p_tdata = tdata;
return PJ_SUCCESS;
}
static void stun_tsx_on_destroy(pj_stun_client_tsx *tsx)
{
pj_stun_tx_data *tdata;
tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
pj_stun_client_tsx_stop(tsx);
if (tdata) {
tsx_erase(tdata->sess, tdata);
pj_pool_release(tdata->pool);
}
TRACE_((THIS_FILE, "STUN transaction %p destroyed", tsx));
}
static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force)
{
TRACE_((THIS_FILE, "tdata %p destroy request, force=%d, tsx=%p", tdata,
force, tdata->client_tsx));
if (tdata->res_timer.id != PJ_FALSE) {
pj_timer_heap_cancel_if_active(tdata->sess->cfg->timer_heap,
&tdata->res_timer, PJ_FALSE);
pj_list_erase(tdata);
}
if (force) {
pj_list_erase(tdata);
if (tdata->client_tsx) {
pj_stun_client_tsx_stop(tdata->client_tsx);
pj_stun_client_tsx_set_data(tdata->client_tsx, NULL);
}
pj_pool_release(tdata->pool);
} else {
if (tdata->client_tsx) {
/* "Probably" this is to absorb retransmission */
pj_time_val delay = {0, 300};
pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay);
} else {
pj_pool_release(tdata->pool);
}
}
}
/*
* Destroy the transmit data.
*/
PJ_DEF(void) pj_stun_msg_destroy_tdata( pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
PJ_UNUSED_ARG(sess);
destroy_tdata(tdata, PJ_FALSE);
}
/* Timer callback to be called when it's time to destroy response cache */
static void on_cache_timeout(pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry)
{
pj_stun_tx_data *tdata;
PJ_UNUSED_ARG(timer_heap);
entry->id = PJ_FALSE;
tdata = (pj_stun_tx_data*) entry->user_data;
PJ_LOG(5,(SNAME(tdata->sess), "Response cache deleted"));
pj_list_erase(tdata);
destroy_tdata(tdata, PJ_FALSE);
}
static pj_status_t apply_msg_options(pj_stun_session *sess,
pj_pool_t *pool,
const pj_stun_req_cred_info *auth_info,
pj_stun_msg *msg)
{
pj_status_t status = 0;
pj_str_t realm, username, nonce, auth_key;
/* If the agent is sending a request, it SHOULD add a SOFTWARE attribute
* to the request. The server SHOULD include a SOFTWARE attribute in all
* responses.
*
* If magic value is not PJ_STUN_MAGIC, only apply the attribute for
* responses.
*/
if (sess->srv_name.slen &&
pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_SOFTWARE, 0)==NULL &&
(PJ_STUN_IS_RESPONSE(msg->hdr.type) ||
(PJ_STUN_IS_REQUEST(msg->hdr.type) && msg->hdr.magic==PJ_STUN_MAGIC)))
{
pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SOFTWARE,
&sess->srv_name);
}
if (pj_stun_auth_valid_for_msg(msg) && auth_info) {
realm = auth_info->realm;
username = auth_info->username;
nonce = auth_info->nonce;
auth_key = auth_info->auth_key;
} else {
realm.slen = username.slen = nonce.slen = auth_key.slen = 0;
}
/* Create and add USERNAME attribute if needed */
if (username.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) {
status = pj_stun_msg_add_string_attr(pool, msg,
PJ_STUN_ATTR_USERNAME,
&username);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
}
/* Add REALM only when long term credential is used */
if (realm.slen && PJ_STUN_IS_REQUEST(msg->hdr.type)) {
status = pj_stun_msg_add_string_attr(pool, msg,
PJ_STUN_ATTR_REALM,
&realm);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
}
/* Add NONCE when desired */
if (nonce.slen &&
(PJ_STUN_IS_REQUEST(msg->hdr.type) ||
PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)))
{
status = pj_stun_msg_add_string_attr(pool, msg,
PJ_STUN_ATTR_NONCE,
&nonce);
}
/* Add MESSAGE-INTEGRITY attribute */
if (username.slen && auth_key.slen) {
status = pj_stun_msg_add_msgint_attr(pool, msg);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
}
/* Add FINGERPRINT attribute if necessary */
if (sess->use_fingerprint) {
status = pj_stun_msg_add_uint_attr(pool, msg,
PJ_STUN_ATTR_FINGERPRINT, 0);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
}
return PJ_SUCCESS;
}
static pj_status_t handle_auth_challenge(pj_stun_session *sess,
const pj_stun_tx_data *request,
const pj_stun_msg *response,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len,
pj_bool_t *notify_user)
{
const pj_stun_errcode_attr *ea;
*notify_user = PJ_TRUE;
if (response==NULL)
return PJ_SUCCESS;
if (sess->auth_type != PJ_STUN_AUTH_LONG_TERM)
return PJ_SUCCESS;
if (!PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) {
sess->auth_retry = 0;
return PJ_SUCCESS;
}
ea = (const pj_stun_errcode_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0);
if (!ea) {
PJ_LOG(4,(SNAME(sess), "Invalid error response: no ERROR-CODE"
" attribute"));
*notify_user = PJ_FALSE;
return PJNATH_EINSTUNMSG;
}
if (ea->err_code == PJ_STUN_SC_UNAUTHORIZED ||
ea->err_code == PJ_STUN_SC_STALE_NONCE)
{
const pj_stun_nonce_attr *anonce;
const pj_stun_realm_attr *arealm;
pj_stun_tx_data *tdata;
unsigned i;
pj_status_t status;
anonce = (const pj_stun_nonce_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_NONCE, 0);
if (!anonce) {
PJ_LOG(4,(SNAME(sess), "Invalid response: missing NONCE"));
*notify_user = PJ_FALSE;
return PJNATH_EINSTUNMSG;
}
/* Bail out if we've supplied the correct nonce */
if (pj_strcmp(&anonce->value, &sess->next_nonce)==0) {
return PJ_SUCCESS;
}
/* Bail out if we've tried too many */
if (++sess->auth_retry > 3) {
PJ_LOG(4,(SNAME(sess), "Error: authentication failed (too "
"many retries)"));
return PJ_STATUS_FROM_STUN_CODE(401);
}
/* Save next_nonce */
pj_strdup(sess->pool, &sess->next_nonce, &anonce->value);
/* Copy the realm from the response */
arealm = (pj_stun_realm_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_REALM, 0);
if (arealm) {
pj_strdup(sess->pool, &sess->server_realm, &arealm->value);
while (sess->server_realm.slen &&
!sess->server_realm.ptr[sess->server_realm.slen-1])
{
--sess->server_realm.slen;
}
}
/* Create new request */
status = pj_stun_session_create_req(sess, request->msg->hdr.type,
request->msg->hdr.magic,
NULL, &tdata);
if (status != PJ_SUCCESS)
return status;
/* Duplicate all the attributes in the old request, except
* USERNAME, REALM, M-I, and NONCE, which will be filled in
* later.
*/
for (i=0; i<request->msg->attr_count; ++i) {
const pj_stun_attr_hdr *asrc = request->msg->attr[i];
if (asrc->type == PJ_STUN_ATTR_USERNAME ||
asrc->type == PJ_STUN_ATTR_REALM ||
asrc->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY ||
asrc->type == PJ_STUN_ATTR_NONCE)
{
continue;
}
tdata->msg->attr[tdata->msg->attr_count++] =
pj_stun_attr_clone(tdata->pool, asrc);
}
/* Will retry the request with authentication, no need to
* notify user.
*/
*notify_user = PJ_FALSE;
PJ_LOG(4,(SNAME(sess), "Retrying request with new authentication"));
/* Retry the request */
status = pj_stun_session_send_msg(sess, request->token, PJ_TRUE,
request->retransmit, src_addr,
src_addr_len, tdata);
} else {
sess->auth_retry = 0;
}
return PJ_SUCCESS;
}
static void stun_tsx_on_complete(pj_stun_client_tsx *tsx,
pj_status_t status,
const pj_stun_msg *response,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_stun_session *sess;
pj_bool_t notify_user = PJ_TRUE;
pj_stun_tx_data *tdata;
tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
sess = tdata->sess;
/* Lock the session and prevent user from destroying us in the callback */
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_stun_msg_destroy_tdata(sess, tdata);
pj_grp_lock_release(sess->grp_lock);
return;
}
/* Handle authentication challenge */
handle_auth_challenge(sess, tdata, response, src_addr,
src_addr_len, &notify_user);
if (notify_user && sess->cb.on_request_complete) {
(*sess->cb.on_request_complete)(sess, status, tdata->token, tdata,
response, src_addr, src_addr_len);
}
/* Destroy the transmit data. This will remove the transaction
* from the pending list too.
*/
if (status == PJNATH_ESTUNTIMEDOUT)
destroy_tdata(tdata, PJ_TRUE);
else
destroy_tdata(tdata, PJ_FALSE);
tdata = NULL;
pj_grp_lock_release(sess->grp_lock);
}
static pj_status_t stun_tsx_on_send_msg(pj_stun_client_tsx *tsx,
const void *stun_pkt,
pj_size_t pkt_size)
{
pj_stun_tx_data *tdata;
pj_stun_session *sess;
pj_status_t status;
tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
sess = tdata->sess;
/* Lock the session and prevent user from destroying us in the callback */
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
/* Stray timer */
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
status = sess->cb.on_send_msg(tdata->sess, tdata->token, stun_pkt,
pkt_size, tdata->dst_addr,
tdata->addr_len);
if (pj_grp_lock_release(sess->grp_lock))
return PJ_EGONE;
return status;
}
/* **************************************************************************/
PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_config *cfg,
const char *name,
const pj_stun_session_cb *cb,
pj_bool_t fingerprint,
pj_grp_lock_t *grp_lock,
pj_stun_session **p_sess)
{
pj_pool_t *pool;
pj_stun_session *sess;
pj_status_t status;
PJ_ASSERT_RETURN(cfg && cb && p_sess, PJ_EINVAL);
if (name==NULL)
name = "stuse%p";
pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_STUN_SESS,
PJNATH_POOL_INC_STUN_SESS, NULL);
PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
sess = PJ_POOL_ZALLOC_T(pool, pj_stun_session);
sess->cfg = cfg;
sess->pool = pool;
pj_memcpy(&sess->cb, cb, sizeof(*cb));
sess->use_fingerprint = fingerprint;
sess->log_flag = 0xFFFF;
if (grp_lock) {
sess->grp_lock = grp_lock;
} else {
status = pj_grp_lock_create(pool, NULL, &sess->grp_lock);
if (status != PJ_SUCCESS) {
pj_pool_release(pool);
return status;
}
}
pj_grp_lock_add_ref(sess->grp_lock);
pj_grp_lock_add_handler(sess->grp_lock, pool, sess,
&stun_sess_on_destroy);
pj_stun_session_set_software_name(sess, &cfg->software_name);
sess->rx_pool = pj_pool_create(sess->cfg->pf, name,
PJNATH_POOL_LEN_STUN_TDATA,
PJNATH_POOL_INC_STUN_TDATA, NULL);
pj_list_init(&sess->pending_request_list);
pj_list_init(&sess->cached_response_list);
*p_sess = sess;
return PJ_SUCCESS;
}
static void stun_sess_on_destroy(void *comp)
{
pj_stun_session *sess = (pj_stun_session*)comp;
while (!pj_list_empty(&sess->pending_request_list)) {
pj_stun_tx_data *tdata = sess->pending_request_list.next;
destroy_tdata(tdata, PJ_TRUE);
}
while (!pj_list_empty(&sess->cached_response_list)) {
pj_stun_tx_data *tdata = sess->cached_response_list.next;
destroy_tdata(tdata, PJ_TRUE);
}
if (sess->rx_pool) {
pj_pool_release(sess->rx_pool);
sess->rx_pool = NULL;
}
pj_pool_release(sess->pool);
TRACE_((THIS_FILE, "STUN session %p destroyed", sess));
}
PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess)
{
pj_stun_tx_data *tdata;
PJ_ASSERT_RETURN(sess, PJ_EINVAL);
TRACE_((SNAME(sess), "STUN session %p destroy request, ref_cnt=%d",
sess, pj_grp_lock_get_ref(sess->grp_lock)));
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
/* Prevent from decrementing the ref counter more than once */
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
sess->is_destroying = PJ_TRUE;
/* We need to stop transactions and cached response because they are
* holding the group lock's reference counter while retransmitting.
*/
tdata = sess->pending_request_list.next;
while (tdata != &sess->pending_request_list) {
if (tdata->client_tsx)
pj_stun_client_tsx_stop(tdata->client_tsx);
tdata = tdata->next;
}
tdata = sess->cached_response_list.next;
while (tdata != &sess->cached_response_list) {
pj_timer_heap_cancel_if_active(tdata->sess->cfg->timer_heap,
&tdata->res_timer, PJ_FALSE);
tdata = tdata->next;
}
pj_grp_lock_dec_ref(sess->grp_lock);
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pj_stun_session_set_user_data( pj_stun_session *sess,
void *user_data)
{
PJ_ASSERT_RETURN(sess, PJ_EINVAL);
pj_grp_lock_acquire(sess->grp_lock);
sess->user_data = user_data;
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
PJ_DEF(void*) pj_stun_session_get_user_data(pj_stun_session *sess)
{
PJ_ASSERT_RETURN(sess, NULL);
return sess->user_data;
}
PJ_DEF(pj_grp_lock_t *) pj_stun_session_get_grp_lock(pj_stun_session *sess)
{
PJ_ASSERT_RETURN(sess, NULL);
return sess->grp_lock;
}
PJ_DEF(pj_status_t) pj_stun_session_set_software_name(pj_stun_session *sess,
const pj_str_t *sw)
{
PJ_ASSERT_RETURN(sess, PJ_EINVAL);
pj_grp_lock_acquire(sess->grp_lock);
if (sw && sw->slen)
pj_strdup(sess->pool, &sess->srv_name, sw);
else
sess->srv_name.slen = 0;
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess,
pj_stun_auth_type auth_type,
const pj_stun_auth_cred *cred)
{
PJ_ASSERT_RETURN(sess, PJ_EINVAL);
pj_grp_lock_acquire(sess->grp_lock);
sess->auth_type = auth_type;
if (cred) {
pj_stun_auth_cred_dup(sess->pool, &sess->cred, cred);
} else {
sess->auth_type = PJ_STUN_AUTH_NONE;
pj_bzero(&sess->cred, sizeof(sess->cred));
}
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
PJ_DEF(void) pj_stun_session_set_log( pj_stun_session *sess,
unsigned flags)
{
PJ_ASSERT_ON_FAIL(sess, return);
sess->log_flag = flags;
}
PJ_DEF(pj_bool_t) pj_stun_session_use_fingerprint(pj_stun_session *sess,
pj_bool_t use)
{
pj_bool_t old_use;
PJ_ASSERT_RETURN(sess, PJ_FALSE);
old_use = sess->use_fingerprint;
sess->use_fingerprint = use;
return old_use;
}
static pj_status_t get_auth(pj_stun_session *sess,
pj_stun_tx_data *tdata)
{
if (sess->cred.type == PJ_STUN_AUTH_CRED_STATIC) {
//tdata->auth_info.realm = sess->cred.data.static_cred.realm;
tdata->auth_info.realm = sess->server_realm;
tdata->auth_info.username = sess->cred.data.static_cred.username;
tdata->auth_info.nonce = sess->cred.data.static_cred.nonce;
pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key,
&tdata->auth_info.realm,
&tdata->auth_info.username,
sess->cred.data.static_cred.data_type,
&sess->cred.data.static_cred.data);
} else if (sess->cred.type == PJ_STUN_AUTH_CRED_DYNAMIC) {
pj_str_t password;
void *user_data = sess->cred.data.dyn_cred.user_data;
pj_stun_passwd_type data_type = PJ_STUN_PASSWD_PLAIN;
pj_status_t rc;
rc = (*sess->cred.data.dyn_cred.get_cred)(tdata->msg, user_data,
tdata->pool,
&tdata->auth_info.realm,
&tdata->auth_info.username,
&tdata->auth_info.nonce,
&data_type, &password);
if (rc != PJ_SUCCESS)
return rc;
pj_stun_create_key(tdata->pool, &tdata->auth_info.auth_key,
&tdata->auth_info.realm, &tdata->auth_info.username,
data_type, &password);
} else {
pj_assert(!"Unknown credential type");
return PJ_EBUG;
}
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pj_stun_session_create_req(pj_stun_session *sess,
int method,
pj_uint32_t magic,
const pj_uint8_t tsx_id[12],
pj_stun_tx_data **p_tdata)
{
pj_stun_tx_data *tdata = NULL;
pj_status_t status;
PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL);
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
status = create_tdata(sess, &tdata);
if (status != PJ_SUCCESS)
goto on_error;
/* Create STUN message */
status = pj_stun_msg_create(tdata->pool, method, magic,
tsx_id, &tdata->msg);
if (status != PJ_SUCCESS)
goto on_error;
/* copy the request's transaction ID as the transaction key. */
pj_assert(sizeof(tdata->msg_key)==sizeof(tdata->msg->hdr.tsx_id));
tdata->msg_magic = tdata->msg->hdr.magic;
pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id,
sizeof(tdata->msg->hdr.tsx_id));
/* Get authentication information for the request */
if (sess->auth_type == PJ_STUN_AUTH_NONE) {
/* No authentication */
} else if (sess->auth_type == PJ_STUN_AUTH_SHORT_TERM) {
/* MUST put authentication in request */
status = get_auth(sess, tdata);
if (status != PJ_SUCCESS)
goto on_error;
} else if (sess->auth_type == PJ_STUN_AUTH_LONG_TERM) {
/* Only put authentication information if we've received
* response from server.
*/
if (sess->next_nonce.slen != 0) {
status = get_auth(sess, tdata);
if (status != PJ_SUCCESS)
goto on_error;
tdata->auth_info.nonce = sess->next_nonce;
tdata->auth_info.realm = sess->server_realm;
}
} else {
pj_assert(!"Invalid authentication type");
status = PJ_EBUG;
goto on_error;
}
*p_tdata = tdata;
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
on_error:
if (tdata)
pj_pool_release(tdata->pool);
pj_grp_lock_release(sess->grp_lock);
return status;
}
PJ_DEF(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess,
int msg_type,
pj_stun_tx_data **p_tdata)
{
pj_stun_tx_data *tdata = NULL;
pj_status_t status;
PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL);
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
status = create_tdata(sess, &tdata);
if (status != PJ_SUCCESS) {
pj_grp_lock_release(sess->grp_lock);
return status;
}
/* Create STUN message */
msg_type |= PJ_STUN_INDICATION_BIT;
status = pj_stun_msg_create(tdata->pool, msg_type, PJ_STUN_MAGIC,
NULL, &tdata->msg);
if (status != PJ_SUCCESS) {
pj_pool_release(tdata->pool);
pj_grp_lock_release(sess->grp_lock);
return status;
}
*p_tdata = tdata;
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
/*
* Create a STUN response message.
*/
PJ_DEF(pj_status_t) pj_stun_session_create_res( pj_stun_session *sess,
const pj_stun_rx_data *rdata,
unsigned err_code,
const pj_str_t *err_msg,
pj_stun_tx_data **p_tdata)
{
pj_status_t status;
pj_stun_tx_data *tdata = NULL;
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
status = create_tdata(sess, &tdata);
if (status != PJ_SUCCESS) {
pj_grp_lock_release(sess->grp_lock);
return status;
}
/* Create STUN response message */
status = pj_stun_msg_create_response(tdata->pool, rdata->msg,
err_code, err_msg, &tdata->msg);
if (status != PJ_SUCCESS) {
pj_pool_release(tdata->pool);
pj_grp_lock_release(sess->grp_lock);
return status;
}
/* copy the request's transaction ID as the transaction key. */
pj_assert(sizeof(tdata->msg_key)==sizeof(rdata->msg->hdr.tsx_id));
tdata->msg_magic = rdata->msg->hdr.magic;
pj_memcpy(tdata->msg_key, rdata->msg->hdr.tsx_id,
sizeof(rdata->msg->hdr.tsx_id));
/* copy the credential found in the request */
pj_stun_req_cred_info_dup(tdata->pool, &tdata->auth_info, &rdata->info);
*p_tdata = tdata;
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
/* Print outgoing message to log */
static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg,
unsigned pkt_size, const pj_sockaddr_t *addr)
{
char dst_name[PJ_INET6_ADDRSTRLEN+10];
if ((PJ_STUN_IS_REQUEST(msg->hdr.type) &&
(sess->log_flag & PJ_STUN_SESS_LOG_TX_REQ)==0) ||
(PJ_STUN_IS_RESPONSE(msg->hdr.type) &&
(sess->log_flag & PJ_STUN_SESS_LOG_TX_RES)==0) ||
(PJ_STUN_IS_INDICATION(msg->hdr.type) &&
(sess->log_flag & PJ_STUN_SESS_LOG_TX_IND)==0))
{
return;
}
pj_sockaddr_print(addr, dst_name, sizeof(dst_name), 3);
PJ_LOG(5,(SNAME(sess),
"TX %d bytes STUN message to %s:\n"
"--- begin STUN message ---\n"
"%s"
"--- end of STUN message ---\n",
pkt_size, dst_name,
pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf),
NULL)));
}
PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
void *token,
pj_bool_t cache_res,
pj_bool_t retransmit,
const pj_sockaddr_t *server,
unsigned addr_len,
pj_stun_tx_data *tdata)
{
pj_status_t status;
PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL);
/* Lock the session and prevent user from destroying us in the callback */
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
pj_log_push_indent();
/* Allocate packet */
tdata->max_len = PJ_STUN_MAX_PKT_LEN;
tdata->pkt = pj_pool_alloc(tdata->pool, tdata->max_len);
tdata->token = token;
tdata->retransmit = retransmit;
/* Apply options */
status = apply_msg_options(sess, tdata->pool, &tdata->auth_info,
tdata->msg);
if (status != PJ_SUCCESS) {
pj_stun_msg_destroy_tdata(sess, tdata);
LOG_ERR_(sess, "Error applying options", status);
goto on_return;
}
/* Encode message */
status = pj_stun_msg_encode(tdata->msg, (pj_uint8_t*)tdata->pkt,
tdata->max_len, 0,
&tdata->auth_info.auth_key,
&tdata->pkt_size);
if (status != PJ_SUCCESS) {
pj_stun_msg_destroy_tdata(sess, tdata);
LOG_ERR_(sess, "STUN encode() error", status);
goto on_return;
}
/* Dump packet */
dump_tx_msg(sess, tdata->msg, (unsigned)tdata->pkt_size, server);
/* If this is a STUN request message, then send the request with
* a new STUN client transaction.
*/
if (PJ_STUN_IS_REQUEST(tdata->msg->hdr.type)) {
/* Create STUN client transaction */
status = pj_stun_client_tsx_create(sess->cfg, tdata->pool,
sess->grp_lock,
&tsx_cb, &tdata->client_tsx);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_stun_client_tsx_set_data(tdata->client_tsx, (void*)tdata);
/* Save the remote address */
tdata->addr_len = addr_len;
tdata->dst_addr = server;
/* Send the request! */
status = pj_stun_client_tsx_send_msg(tdata->client_tsx, retransmit,
tdata->pkt,
(unsigned)tdata->pkt_size);
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
pj_stun_msg_destroy_tdata(sess, tdata);
LOG_ERR_(sess, "Error sending STUN request", status);
goto on_return;
}
/* Add to pending request list */
tsx_add(sess, tdata);
} else {
if (cache_res &&
(PJ_STUN_IS_SUCCESS_RESPONSE(tdata->msg->hdr.type) ||
PJ_STUN_IS_ERROR_RESPONSE(tdata->msg->hdr.type)))
{
/* Requested to keep the response in the cache */
pj_time_val timeout;
pj_memset(&tdata->res_timer, 0, sizeof(tdata->res_timer));
pj_timer_entry_init(&tdata->res_timer, PJ_FALSE, tdata,
&on_cache_timeout);
timeout.sec = sess->cfg->res_cache_msec / 1000;
timeout.msec = sess->cfg->res_cache_msec % 1000;
status = pj_timer_heap_schedule_w_grp_lock(sess->cfg->timer_heap,
&tdata->res_timer,
&timeout, PJ_TRUE,
sess->grp_lock);
if (status != PJ_SUCCESS) {
pj_stun_msg_destroy_tdata(sess, tdata);
LOG_ERR_(sess, "Error scheduling response timer", status);
goto on_return;
}
pj_list_push_back(&sess->cached_response_list, tdata);
}
/* Otherwise for non-request message, send directly to transport. */
status = sess->cb.on_send_msg(sess, token, tdata->pkt,
tdata->pkt_size, server, addr_len);
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
pj_stun_msg_destroy_tdata(sess, tdata);
LOG_ERR_(sess, "Error sending STUN request", status);
goto on_return;
}
/* Destroy only when response is not cached*/
if (tdata->res_timer.id == 0) {
pj_stun_msg_destroy_tdata(sess, tdata);
}
}
on_return:
pj_log_pop_indent();
if (pj_grp_lock_release(sess->grp_lock))
return PJ_EGONE;
return status;
}
/*
* Create and send STUN response message.
*/
PJ_DEF(pj_status_t) pj_stun_session_respond( pj_stun_session *sess,
const pj_stun_rx_data *rdata,
unsigned code,
const char *errmsg,
void *token,
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;
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
status = pj_stun_session_create_res(sess, rdata, code,
(errmsg?pj_cstr(&reason,errmsg):NULL),
&tdata);
if (status != PJ_SUCCESS) {
pj_grp_lock_release(sess->grp_lock);
return status;
}
status = pj_stun_session_send_msg(sess, token, cache, PJ_FALSE,
dst_addr, addr_len, tdata);
pj_grp_lock_release(sess->grp_lock);
return status;
}
/*
* Cancel outgoing STUN transaction.
*/
PJ_DEF(pj_status_t) pj_stun_session_cancel_req( pj_stun_session *sess,
pj_stun_tx_data *tdata,
pj_bool_t notify,
pj_status_t notify_status)
{
PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
PJ_ASSERT_RETURN(!notify || notify_status!=PJ_SUCCESS, PJ_EINVAL);
PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL);
/* Lock the session and prevent user from destroying us in the callback */
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
if (notify) {
(sess->cb.on_request_complete)(sess, notify_status, tdata->token,
tdata, NULL, NULL, 0);
}
/* Just destroy tdata. This will destroy the transaction as well */
pj_stun_msg_destroy_tdata(sess, tdata);
pj_grp_lock_release(sess->grp_lock);
return PJ_SUCCESS;
}
/*
* Explicitly request retransmission of the request.
*/
PJ_DEF(pj_status_t) pj_stun_session_retransmit_req(pj_stun_session *sess,
pj_stun_tx_data *tdata,
pj_bool_t mod_count)
{
pj_status_t status;
PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(tdata->msg->hdr.type), PJ_EINVAL);
/* Lock the session and prevent user from destroying us in the callback */
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
status = pj_stun_client_tsx_retransmit(tdata->client_tsx, mod_count);
pj_grp_lock_release(sess->grp_lock);
return status;
}
/* Send response */
static pj_status_t send_response(pj_stun_session *sess, void *token,
pj_pool_t *pool, pj_stun_msg *response,
const pj_stun_req_cred_info *auth_info,
pj_bool_t retransmission,
const pj_sockaddr_t *addr, unsigned addr_len)
{
pj_uint8_t *out_pkt;
pj_size_t out_max_len, out_len;
pj_status_t status;
/* Apply options */
if (!retransmission) {
status = apply_msg_options(sess, pool, auth_info, response);
if (status != PJ_SUCCESS)
return status;
}
/* Alloc packet buffer */
out_max_len = PJ_STUN_MAX_PKT_LEN;
out_pkt = (pj_uint8_t*) pj_pool_alloc(pool, out_max_len);
/* Encode */
status = pj_stun_msg_encode(response, out_pkt, out_max_len, 0,
&auth_info->auth_key, &out_len);
if (status != PJ_SUCCESS) {
LOG_ERR_(sess, "Error encoding message", status);
return status;
}
/* Print log */
dump_tx_msg(sess, response, (unsigned)out_len, addr);
/* Send packet */
status = sess->cb.on_send_msg(sess, token, out_pkt, (unsigned)out_len,
addr, addr_len);
return status;
}
/* Authenticate incoming message */
static pj_status_t authenticate_req(pj_stun_session *sess,
void *token,
const pj_uint8_t *pkt,
unsigned pkt_len,
pj_stun_rx_data *rdata,
pj_pool_t *tmp_pool,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_stun_msg *response;
pj_status_t status;
if (PJ_STUN_IS_ERROR_RESPONSE(rdata->msg->hdr.type) ||
sess->auth_type == PJ_STUN_AUTH_NONE)
{
return PJ_SUCCESS;
}
status = pj_stun_authenticate_request(pkt, pkt_len, rdata->msg,
&sess->cred, tmp_pool, &rdata->info,
&response);
if (status != PJ_SUCCESS && response != NULL) {
PJ_LOG(5,(SNAME(sess), "Message authentication failed"));
send_response(sess, token, tmp_pool, response, &rdata->info,
PJ_FALSE, src_addr, src_addr_len);
}
return status;
}
/* Handle incoming response */
static pj_status_t on_incoming_response(pj_stun_session *sess,
unsigned options,
const pj_uint8_t *pkt,
unsigned pkt_len,
pj_stun_msg *msg,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_stun_tx_data *tdata;
pj_status_t status;
/* Lookup pending client transaction */
tdata = tsx_lookup(sess, msg);
if (tdata == NULL) {
PJ_LOG(5,(SNAME(sess),
"Transaction not found, response silently discarded"));
return PJ_SUCCESS;
}
if (sess->auth_type == PJ_STUN_AUTH_NONE)
options |= PJ_STUN_NO_AUTHENTICATE;
/* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE
* is specified in the option.
*/
if ((options & PJ_STUN_NO_AUTHENTICATE) == 0 &&
tdata->auth_info.auth_key.slen != 0 &&
pj_stun_auth_valid_for_msg(msg))
{
status = pj_stun_authenticate_response(pkt, pkt_len, msg,
&tdata->auth_info.auth_key);
if (status != PJ_SUCCESS) {
PJ_LOG(5,(SNAME(sess),
"Response authentication failed"));
return status;
}
}
/* Pass the response to the transaction.
* If the message is accepted, transaction callback will be called,
* and this will call the session callback too.
*/
status = pj_stun_client_tsx_on_rx_msg(tdata->client_tsx, msg,
src_addr, src_addr_len);
if (status != PJ_SUCCESS) {
return status;
}
return PJ_SUCCESS;
}
/* For requests, check if we cache the response */
static pj_status_t check_cached_response(pj_stun_session *sess,
pj_pool_t *tmp_pool,
const pj_stun_msg *msg,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_stun_tx_data *t;
/* First lookup response in response cache */
t = sess->cached_response_list.next;
while (t != &sess->cached_response_list) {
if (t->msg_magic == msg->hdr.magic &&
t->msg->hdr.type == msg->hdr.type &&
pj_memcmp(t->msg_key, msg->hdr.tsx_id,
sizeof(msg->hdr.tsx_id))==0)
{
break;
}
t = t->next;
}
if (t != &sess->cached_response_list) {
/* Found response in the cache */
PJ_LOG(5,(SNAME(sess),
"Request retransmission, sending cached response"));
send_response(sess, t->token, tmp_pool, t->msg, &t->auth_info,
PJ_TRUE, src_addr, src_addr_len);
return PJ_SUCCESS;
}
return PJ_ENOTFOUND;
}
/* Handle incoming request */
static pj_status_t on_incoming_request(pj_stun_session *sess,
unsigned options,
void *token,
pj_pool_t *tmp_pool,
const pj_uint8_t *in_pkt,
unsigned in_pkt_len,
pj_stun_msg *msg,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_stun_rx_data rdata;
pj_status_t status;
/* Init rdata */
rdata.msg = msg;
pj_bzero(&rdata.info, sizeof(rdata.info));
if (sess->auth_type == PJ_STUN_AUTH_NONE)
options |= PJ_STUN_NO_AUTHENTICATE;
/* Authenticate the message, unless PJ_STUN_NO_AUTHENTICATE
* is specified in the option.
*/
if ((options & PJ_STUN_NO_AUTHENTICATE) == 0) {
status = authenticate_req(sess, token, (const pj_uint8_t*) in_pkt,
in_pkt_len,&rdata, tmp_pool, src_addr,
src_addr_len);
if (status != PJ_SUCCESS) {
return status;
}
}
/* Distribute to handler, or respond with Bad Request */
if (sess->cb.on_rx_request) {
status = (*sess->cb.on_rx_request)(sess, in_pkt, in_pkt_len, &rdata,
token, src_addr, src_addr_len);
} else {
pj_str_t err_text;
pj_stun_msg *response;
err_text = pj_str("Callback is not set to handle request");
status = pj_stun_msg_create_response(tmp_pool, msg,
PJ_STUN_SC_BAD_REQUEST,
&err_text, &response);
if (status == PJ_SUCCESS && response) {
status = send_response(sess, token, tmp_pool, response,
NULL, PJ_FALSE, src_addr, src_addr_len);
}
}
return status;
}
/* Handle incoming indication */
static pj_status_t on_incoming_indication(pj_stun_session *sess,
void *token,
pj_pool_t *tmp_pool,
const pj_uint8_t *in_pkt,
unsigned in_pkt_len,
const pj_stun_msg *msg,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
PJ_UNUSED_ARG(tmp_pool);
/* Distribute to handler */
if (sess->cb.on_rx_indication) {
return (*sess->cb.on_rx_indication)(sess, in_pkt, in_pkt_len, msg,
token, src_addr, src_addr_len);
} else {
return PJ_SUCCESS;
}
}
/* Print outgoing message to log */
static void dump_rx_msg(pj_stun_session *sess, const pj_stun_msg *msg,
unsigned pkt_size, const pj_sockaddr_t *addr)
{
char src_info[PJ_INET6_ADDRSTRLEN+10];
if ((PJ_STUN_IS_REQUEST(msg->hdr.type) &&
(sess->log_flag & PJ_STUN_SESS_LOG_RX_REQ)==0) ||
(PJ_STUN_IS_RESPONSE(msg->hdr.type) &&
(sess->log_flag & PJ_STUN_SESS_LOG_RX_RES)==0) ||
(PJ_STUN_IS_INDICATION(msg->hdr.type) &&
(sess->log_flag & PJ_STUN_SESS_LOG_RX_IND)==0))
{
return;
}
pj_sockaddr_print(addr, src_info, sizeof(src_info), 3);
PJ_LOG(5,(SNAME(sess),
"RX %d bytes STUN message from %s:\n"
"--- begin STUN message ---\n"
"%s"
"--- end of STUN message ---\n",
pkt_size, src_info,
pj_stun_msg_dump(msg, sess->dump_buf, sizeof(sess->dump_buf),
NULL)));
}
/* Incoming packet */
PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
const void *packet,
pj_size_t pkt_size,
unsigned options,
void *token,
pj_size_t *parsed_len,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_stun_msg *msg, *response;
pj_status_t status;
PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL);
/* Lock the session and prevent user from destroying us in the callback */
pj_grp_lock_acquire(sess->grp_lock);
if (sess->is_destroying) {
pj_grp_lock_release(sess->grp_lock);
return PJ_EINVALIDOP;
}
pj_log_push_indent();
/* Reset pool */
pj_pool_reset(sess->rx_pool);
/* Try to parse the message */
status = pj_stun_msg_decode(sess->rx_pool, (const pj_uint8_t*)packet,
pkt_size, options,
&msg, parsed_len, &response);
if (status != PJ_SUCCESS) {
LOG_ERR_(sess, "STUN msg_decode() error", status);
if (response) {
send_response(sess, token, sess->rx_pool, response, NULL,
PJ_FALSE, src_addr, src_addr_len);
}
goto on_return;
}
dump_rx_msg(sess, msg, (unsigned)pkt_size, src_addr);
/* For requests, check if we have cached response */
status = check_cached_response(sess, sess->rx_pool, msg,
src_addr, src_addr_len);
if (status == PJ_SUCCESS) {
goto on_return;
}
/* Handle message */
if (PJ_STUN_IS_SUCCESS_RESPONSE(msg->hdr.type) ||
PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
{
status = on_incoming_response(sess, options,
(const pj_uint8_t*) packet,
(unsigned)pkt_size, msg,
src_addr, src_addr_len);
} else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
status = on_incoming_request(sess, options, token, sess->rx_pool,
(const pj_uint8_t*) packet,
(unsigned)pkt_size,
msg, src_addr, src_addr_len);
} else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) {
status = on_incoming_indication(sess, token, sess->rx_pool,
(const pj_uint8_t*) packet,
(unsigned)pkt_size, msg, src_addr,
src_addr_len);
} else {
pj_assert(!"Unexpected!");
status = PJ_EBUG;
}
on_return:
pj_log_pop_indent();
if (pj_grp_lock_release(sess->grp_lock))
return PJ_EGONE;
return status;
}