| /* $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, ¬ify_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; |
| } |
| |