| /* $Id$ */ |
| /* |
| * Copyright (C) 2003-2005 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 "stun_session.h" |
| #include <pjlib.h> |
| |
| struct pj_stun_session |
| { |
| pj_stun_endpoint *endpt; |
| pj_pool_t *pool; |
| pj_stun_session_cb cb; |
| void *user_data; |
| |
| /* Long term credential */ |
| pj_str_t l_realm; |
| pj_str_t l_username; |
| pj_str_t l_password; |
| |
| /* Short term credential */ |
| pj_str_t s_username; |
| pj_str_t s_password; |
| |
| pj_stun_tx_data pending_request_list; |
| }; |
| |
| #define SNAME(s_) ((s_)->pool->obj_name) |
| |
| #if PJ_LOG_MAX_LEVEL >= 5 |
| # define TRACE_(expr) PJ_LOG(5,expr) |
| #else |
| # define TRACE_(expr) |
| #endif |
| |
| #if PJ_LOG_MAX_LEVEL >= 4 |
| # define LOG_ERR_(sess, title, rc) |
| static void stun_perror(pj_stun_session *sess, const char *title, |
| pj_status_t status) |
| { |
| char errmsg[PJ_ERR_MSG_SIZE]; |
| |
| pj_strerror(status, errmsg, sizeof(errmsg)); |
| |
| PJ_LOG(4,(SNAME(sess), "%s: %s", title, errmsg)); |
| } |
| |
| #else |
| # define ERR_(sess, title, rc) |
| #endif |
| |
| #define TDATA_POOL_SIZE 1024 |
| #define TDATA_POOL_INC 1024 |
| |
| |
| static void tsx_on_complete(pj_stun_client_tsx *tsx, |
| pj_status_t status, |
| const pj_stun_msg *response); |
| static pj_status_t tsx_on_send_msg(pj_stun_client_tsx *tsx, |
| const void *stun_pkt, |
| pj_size_t pkt_size); |
| |
| static pj_stun_tsx_cb tsx_cb = |
| { |
| &tsx_on_complete, |
| &tsx_on_send_msg |
| }; |
| |
| |
| static pj_status_t tsx_add(pj_stun_session *sess, |
| pj_stun_tx_data *tdata) |
| { |
| pj_list_push_back(&sess->pending_request_list, tdata); |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t tsx_erase(pj_stun_session *sess, |
| pj_stun_tx_data *tdata) |
| { |
| 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->client_key)==sizeof(msg->hdr.tsx_id)); |
| if (pj_memcmp(tdata->client_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, |
| unsigned msg_type, |
| void *user_data, |
| pj_stun_tx_data **p_tdata) |
| { |
| pj_pool_t *pool; |
| pj_status_t status; |
| pj_stun_tx_data *tdata; |
| |
| /* Create pool and initialize basic tdata attributes */ |
| pool = pj_pool_create(sess->endpt->pf, "tdata%p", |
| TDATA_POOL_SIZE, TDATA_POOL_INC, NULL); |
| PJ_ASSERT_RETURN(pool, PJ_ENOMEM); |
| |
| tdata = PJ_POOL_ZALLOC_TYPE(pool, pj_stun_tx_data); |
| tdata->pool = pool; |
| tdata->sess = sess; |
| tdata->user_data = tdata; |
| |
| /* Create STUN message */ |
| status = pj_stun_msg_create(pool, msg_type, PJ_STUN_MAGIC, |
| NULL, &tdata->msg); |
| if (status != PJ_SUCCESS) { |
| pj_pool_release(pool); |
| return status; |
| } |
| |
| /* If this is a request, then copy the request's transaction ID |
| * as the transaction key. |
| */ |
| if (PJ_STUN_IS_REQUEST(msg_type)) { |
| pj_assert(sizeof(tdata->client_key)==sizeof(tdata->msg->hdr.tsx_id)); |
| pj_memcpy(tdata->client_key, tdata->msg->hdr.tsx_id, |
| sizeof(tdata->msg->hdr.tsx_id)); |
| } |
| |
| *p_tdata = tdata; |
| |
| return PJ_SUCCESS; |
| } |
| |
| static void destroy_tdata(pj_stun_tx_data *tdata) |
| { |
| if (tdata->client_tsx) { |
| tsx_erase(tdata->sess, tdata); |
| pj_stun_client_tsx_destroy(tdata->client_tsx); |
| tdata->client_tsx = NULL; |
| } |
| |
| pj_pool_release(tdata->pool); |
| } |
| |
| static pj_status_t session_apply_req(pj_stun_session *sess, |
| pj_pool_t *pool, |
| unsigned options, |
| pj_stun_msg *msg) |
| { |
| pj_status_t status; |
| |
| /* From draft-ietf-behave-rfc3489bis-05.txt |
| * Section 8.3.1. Formulating the Request Message |
| */ |
| if (options & PJ_STUN_USE_LONG_TERM_CRED) { |
| pj_stun_generic_string_attr *auname; |
| pj_stun_msg_integrity_attr *amsgi; |
| pj_stun_generic_string_attr *arealm; |
| |
| /* Create and add USERNAME attribute */ |
| status = pj_stun_generic_string_attr_create(sess->pool, |
| PJ_STUN_ATTR_USERNAME, |
| &sess->l_username, |
| &auname); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| status = pj_stun_msg_add_attr(msg, &auname->hdr); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| /* Add REALM only when long term credential is used */ |
| status = pj_stun_generic_string_attr_create(sess->pool, |
| PJ_STUN_ATTR_REALM, |
| &sess->l_realm, |
| &arealm); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| status = pj_stun_msg_add_attr(msg, &arealm->hdr); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| /* Add MESSAGE-INTEGRITY attribute */ |
| status = pj_stun_msg_integrity_attr_create(sess->pool, &amsgi); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| status = pj_stun_msg_add_attr(msg, &amsgi->hdr); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| PJ_TODO(COMPUTE_MESSAGE_INTEGRITY1); |
| |
| } else if (options & PJ_STUN_USE_SHORT_TERM_CRED) { |
| pj_stun_generic_string_attr *auname; |
| pj_stun_msg_integrity_attr *amsgi; |
| |
| /* Create and add USERNAME attribute */ |
| status = pj_stun_generic_string_attr_create(sess->pool, |
| PJ_STUN_ATTR_USERNAME, |
| &sess->s_username, |
| &auname); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| status = pj_stun_msg_add_attr(msg, &auname->hdr); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| /* Add MESSAGE-INTEGRITY attribute */ |
| status = pj_stun_msg_integrity_attr_create(sess->pool, &amsgi); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| status = pj_stun_msg_add_attr(msg, &amsgi->hdr); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| PJ_TODO(COMPUTE_MESSAGE_INTEGRITY2); |
| } |
| |
| /* Add FINGERPRINT attribute if necessary */ |
| if (options & PJ_STUN_USE_FINGERPRINT) { |
| pj_stun_fingerprint_attr *af; |
| |
| status = pj_stun_generic_uint_attr_create(sess->pool, |
| PJ_STUN_ATTR_FINGERPRINT, |
| 0, &af); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| |
| status = pj_stun_msg_add_attr(msg, &af->hdr); |
| PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| static void tsx_on_complete(pj_stun_client_tsx *tsx, |
| pj_status_t status, |
| const pj_stun_msg *response) |
| { |
| pj_stun_tx_data *tdata; |
| |
| tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx); |
| |
| switch (PJ_STUN_GET_METHOD(tdata->msg->hdr.type)) { |
| case PJ_STUN_BINDING_METHOD: |
| tdata->sess->cb.on_bind_response(tdata->sess, status, tdata, response); |
| break; |
| case PJ_STUN_ALLOCATE_METHOD: |
| tdata->sess->cb.on_allocate_response(tdata->sess, status, |
| tdata, response); |
| break; |
| case PJ_STUN_SET_ACTIVE_DESTINATION_METHOD: |
| tdata->sess->cb.on_set_active_destination_response(tdata->sess, status, |
| tdata, response); |
| break; |
| case PJ_STUN_CONNECT_METHOD: |
| tdata->sess->cb.on_connect_response(tdata->sess, status, tdata, |
| response); |
| break; |
| default: |
| pj_assert(!"Unknown method"); |
| break; |
| } |
| } |
| |
| static pj_status_t tsx_on_send_msg(pj_stun_client_tsx *tsx, |
| const void *stun_pkt, |
| pj_size_t pkt_size) |
| { |
| pj_stun_tx_data *tdata; |
| |
| tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx); |
| |
| return tdata->sess->cb.on_send_msg(tdata, stun_pkt, pkt_size, |
| tdata->addr_len, tdata->dst_addr); |
| } |
| |
| /* **************************************************************************/ |
| |
| PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_endpoint *endpt, |
| const char *name, |
| const pj_stun_session_cb *cb, |
| pj_stun_session **p_sess) |
| { |
| pj_pool_t *pool; |
| pj_stun_session *sess; |
| |
| PJ_ASSERT_RETURN(endpt && cb && p_sess, PJ_EINVAL); |
| |
| pool = pj_pool_create(endpt->pf, name, 4000, 4000, NULL); |
| PJ_ASSERT_RETURN(pool, PJ_ENOMEM); |
| |
| sess = PJ_POOL_ZALLOC_TYPE(pool, pj_stun_session); |
| sess->endpt = endpt; |
| sess->pool = pool; |
| pj_memcpy(&sess->cb, cb, sizeof(*cb)); |
| |
| pj_list_init(&sess->pending_request_list); |
| |
| *p_sess = sess; |
| |
| PJ_TODO(MUTEX_PROTECTION); |
| |
| return PJ_SUCCESS; |
| } |
| |
| PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess) |
| { |
| PJ_ASSERT_RETURN(sess, PJ_EINVAL); |
| |
| pj_pool_release(sess->pool); |
| |
| 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); |
| sess->user_data = user_data; |
| 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_status_t) |
| pj_stun_session_set_long_term_credential(pj_stun_session *sess, |
| const pj_str_t *realm, |
| const pj_str_t *user, |
| const pj_str_t *passwd) |
| { |
| pj_str_t nil = { NULL, 0 }; |
| |
| PJ_ASSERT_RETURN(sess, PJ_EINVAL); |
| pj_strdup_with_null(sess->pool, &sess->l_realm, realm ? realm : &nil); |
| pj_strdup_with_null(sess->pool, &sess->l_username, user ? user : &nil); |
| pj_strdup_with_null(sess->pool, &sess->l_password, passwd ? passwd : &nil); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| PJ_DEF(pj_status_t) |
| pj_stun_session_set_short_term_credential(pj_stun_session *sess, |
| const pj_str_t *user, |
| const pj_str_t *passwd) |
| { |
| pj_str_t nil = { NULL, 0 }; |
| |
| PJ_ASSERT_RETURN(sess, PJ_EINVAL); |
| pj_strdup_with_null(sess->pool, &sess->s_username, user ? user : &nil); |
| pj_strdup_with_null(sess->pool, &sess->s_password, passwd ? passwd : &nil); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| PJ_DEF(pj_status_t) pj_stun_session_create_bind_req(pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| pj_stun_tx_data *tdata; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL); |
| |
| status = create_tdata(sess, PJ_STUN_BINDING_REQUEST, NULL, &tdata); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| *p_tdata = tdata; |
| return PJ_SUCCESS; |
| } |
| |
| PJ_DEF(pj_status_t) pj_stun_session_create_allocate_req(pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP); |
| } |
| |
| |
| PJ_DEF(pj_status_t) |
| pj_stun_session_create_set_active_destination_req(pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP); |
| } |
| |
| PJ_DEF(pj_status_t) pj_stun_session_create_connect_req( pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP); |
| } |
| |
| PJ_DEF(pj_status_t) |
| pj_stun_session_create_connection_status_ind(pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP); |
| } |
| |
| PJ_DEF(pj_status_t) pj_stun_session_create_send_ind( pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP); |
| } |
| |
| PJ_DEF(pj_status_t) pj_stun_session_create_data_ind( pj_stun_session *sess, |
| pj_stun_tx_data **p_tdata) |
| { |
| PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP); |
| } |
| |
| PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, |
| unsigned options, |
| unsigned addr_len, |
| const pj_sockaddr_t *server, |
| pj_stun_tx_data *tdata) |
| { |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL); |
| |
| /* Allocate packet */ |
| tdata->max_len = PJ_STUN_MAX_PKT_LEN; |
| tdata->pkt = pj_pool_alloc(tdata->pool, tdata->max_len); |
| |
| if (PJ_LOG_MAX_LEVEL >= 5) { |
| char *buf = (char*) tdata->pkt; |
| const char *dst_name; |
| int dst_port; |
| const pj_sockaddr *dst = (const pj_sockaddr*)server; |
| |
| if (dst->sa_family == PJ_AF_INET) { |
| const pj_sockaddr_in *dst4 = (const pj_sockaddr_in*)dst; |
| dst_name = pj_inet_ntoa(dst4->sin_addr); |
| dst_port = pj_ntohs(dst4->sin_port); |
| } else if (dst->sa_family == PJ_AF_INET6) { |
| const pj_sockaddr_in6 *dst6 = (const pj_sockaddr_in6*)dst; |
| dst_name = "IPv6"; |
| dst_port = pj_ntohs(dst6->sin6_port); |
| } else { |
| LOG_ERR_(sess, "Invalid address family", PJ_EINVAL); |
| return PJ_EINVAL; |
| } |
| |
| PJ_LOG(5,(SNAME(sess), |
| "Sending STUN message to %s:%d:\n" |
| "--- begin STUN message ---\n" |
| "%s" |
| "--- end of STUN message ---\n", |
| dst_name, dst_port, |
| pj_stun_msg_dump(tdata->msg, buf, tdata->max_len, NULL))); |
| } |
| |
| /* Apply options */ |
| status = session_apply_req(sess, tdata->pool, options, tdata->msg); |
| if (status != PJ_SUCCESS) { |
| LOG_ERR_(sess, "Error applying options", status); |
| destroy_tdata(tdata); |
| return status; |
| } |
| |
| /* Encode message */ |
| status = pj_stun_msg_encode(tdata->msg, tdata->pkt, tdata->max_len, |
| 0, &tdata->pkt_size); |
| if (status != PJ_SUCCESS) { |
| LOG_ERR_(sess, "STUN encode() error", status); |
| destroy_tdata(tdata); |
| return status; |
| } |
| |
| /* 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->endpt, tdata->pool, |
| &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, PJ_TRUE, |
| tdata->pkt, tdata->pkt_size); |
| if (status != PJ_SUCCESS && status != PJ_EPENDING) { |
| LOG_ERR_(sess, "Error sending STUN request", status); |
| destroy_tdata(tdata); |
| return status; |
| } |
| |
| /* Add to pending request list */ |
| tsx_add(sess, tdata); |
| |
| } else { |
| /* Otherwise for non-request message, send directly to transport. */ |
| status = sess->cb.on_send_msg(tdata, tdata->pkt, tdata->pkt_size, |
| addr_len, server); |
| |
| if (status != PJ_SUCCESS && status != PJ_EPENDING) { |
| LOG_ERR_(sess, "Error sending STUN request", status); |
| destroy_tdata(tdata); |
| return status; |
| } |
| } |
| |
| |
| return status; |
| } |
| |
| |
| PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess, |
| const void *packet, |
| pj_size_t pkt_size, |
| unsigned *parsed_len) |
| { |
| pj_stun_msg *msg; |
| pj_pool_t *tmp_pool; |
| char *dump; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL); |
| |
| tmp_pool = pj_pool_create(sess->endpt->pf, "tmpstun", 1024, 1024, NULL); |
| if (!tmp_pool) |
| return PJ_ENOMEM; |
| |
| /* Try to parse the message */ |
| status = pj_stun_msg_decode(tmp_pool, (const pj_uint8_t*)packet, |
| pkt_size, 0, &msg, parsed_len, |
| NULL, NULL, NULL); |
| if (status != PJ_SUCCESS) { |
| LOG_ERR_(sess, "STUN msg_decode() error", status); |
| pj_pool_release(tmp_pool); |
| return status; |
| } |
| |
| dump = pj_pool_alloc(tmp_pool, PJ_STUN_MAX_PKT_LEN); |
| |
| PJ_LOG(4,(SNAME(sess), |
| "RX STUN message:\n" |
| "--- begin STUN message ---" |
| "%s" |
| "--- end of STUN message ---\n", |
| pj_stun_msg_dump(msg, dump, PJ_STUN_MAX_PKT_LEN, NULL))); |
| |
| |
| if (PJ_STUN_IS_RESPONSE(msg->hdr.type) || |
| PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type)) |
| { |
| pj_stun_tx_data *tdata; |
| |
| /* Lookup pending client transaction */ |
| tdata = tsx_lookup(sess, msg); |
| if (tdata == NULL) { |
| LOG_ERR_(sess, "STUN error finding transaction", PJ_ENOTFOUND); |
| pj_pool_release(tmp_pool); |
| return PJ_ENOTFOUND; |
| } |
| |
| /* 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); |
| if (status != PJ_SUCCESS) { |
| pj_pool_release(tmp_pool); |
| return status; |
| } |
| |
| /* If transaction has completed, destroy the transmit data. |
| * This will remove the transaction from the pending list too. |
| */ |
| if (pj_stun_client_tsx_is_complete(tdata->client_tsx)) { |
| destroy_tdata(tdata); |
| tdata = NULL; |
| } |
| |
| pj_pool_release(tmp_pool); |
| return PJ_SUCCESS; |
| |
| } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) { |
| |
| PJ_TODO(HANDLE_INCOMING_STUN_REQUEST); |
| |
| } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) { |
| |
| PJ_TODO(HANDLE_INCOMING_STUN_INDICATION); |
| |
| } else { |
| pj_assert(!"Unexpected!"); |
| } |
| |
| pj_pool_release(tmp_pool); |
| return PJ_ENOTSUP; |
| } |
| |