summaryrefslogtreecommitdiff
path: root/pjlib-util/src/pjstun-client/stun_session.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjlib-util/src/pjstun-client/stun_session.c')
-rw-r--r--pjlib-util/src/pjstun-client/stun_session.c499
1 files changed, 499 insertions, 0 deletions
diff --git a/pjlib-util/src/pjstun-client/stun_session.c b/pjlib-util/src/pjstun-client/stun_session.c
new file mode 100644
index 00000000..571b723d
--- /dev/null
+++ b/pjlib-util/src/pjstun-client/stun_session.c
@@ -0,0 +1,499 @@
+/* $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;
+
+ pj_str_t realm;
+ pj_str_t username;
+ pj_str_t password;
+
+ pj_bool_t fingerprint_enabled;
+
+ 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,
+ pj_stun_msg *msg)
+{
+ pj_status_t status;
+
+ /* From draft-ietf-behave-rfc3489bis-05.txt
+ * Section 8.3.1. Formulating the Request Message
+ */
+ if (sess->realm.slen || sess->username.slen) {
+ 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->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);
+
+ if (sess->realm.slen) {
+ /* Add REALM only when long term credential is used */
+ pj_stun_generic_string_attr *arealm;
+ status = pj_stun_generic_string_attr_create(sess->pool,
+ PJ_STUN_ATTR_REALM,
+ &sess->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_INTEGRITY);
+
+ }
+
+ /* Add FINGERPRINT attribute if necessary */
+ if (sess->fingerprint_enabled) {
+ 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;
+}
+
+
+
+
+
+
+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_credential( pj_stun_session *sess,
+ const pj_str_t *realm,
+ const pj_str_t *user,
+ const pj_str_t *passwd)
+{
+ pj_str_t empty = { NULL, 0 };
+
+ PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+ pj_strdup_with_null(sess->pool, &sess->realm, realm ? realm : &empty);
+ pj_strdup_with_null(sess->pool, &sess->username, user ? user : &empty);
+ pj_strdup_with_null(sess->pool, &sess->password, passwd ? passwd : &empty);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_enable_fingerprint(pj_stun_session *sess,
+ pj_bool_t enabled)
+{
+ PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+ sess->fingerprint_enabled = enabled;
+ 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_pool_t *pool;
+ 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;
+
+ status = session_apply_req(sess, pool, tdata->msg);
+ if (status != PJ_SUCCESS) {
+ destroy_tdata(tdata);
+ 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 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);
+
+ if (PJ_LOG_MAX_LEVEL >= 5) {
+ char buf[512];
+ unsigned buflen = sizeof(buf);
+ 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"
+ "%s\n",
+ dst_name, dst_port,
+ pj_stun_msg_dump(tdata->msg, buf, &buflen)));
+ }
+
+
+ /* 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, &tsx_cb,
+ &tdata->client_tsx);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_stun_client_tsx_set_data(tdata->client_tsx, (void*)tdata);
+
+ /* Send the request! */
+ status = pj_stun_client_tsx_send_msg(tdata->client_tsx, PJ_TRUE,
+ tdata->msg);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ LOG_ERR_(sess, "Error sending STUN request", status);
+ 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, addr_len, server);
+ }
+
+
+ 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_status_t status;
+
+ PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL);
+
+ /* Try to parse the message */
+ status = pj_stun_msg_decode(tsx->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);
+ return status;
+ }
+
+ 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);
+ 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)
+ 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;
+ }
+
+ return PJ_SUCCESS;
+
+ } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+
+
+ } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) {
+
+
+ } else {
+ pj_assert(!"Unexpected!");
+ return PJ_EBUG;
+ }
+
+}
+