summaryrefslogtreecommitdiff
path: root/pjnath
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-03-08 00:54:04 +0000
committerBenny Prijono <bennylp@teluu.com>2008-03-08 00:54:04 +0000
commite30de41312b8f3552dea56db5eb73d99889f6941 (patch)
tree94af9773e03bffd53f58620a87c26da737db62df /pjnath
parent2eeebb98f8e4fc3f84fe8d16548035b605ff0929 (diff)
More work on ticket #485: more TURN-07 work
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1850 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjnath')
-rw-r--r--pjnath/build/pjturn_srv.dsp4
-rw-r--r--pjnath/include/pjnath/stun_msg.h52
-rw-r--r--pjnath/src/pjnath/stun_msg.c7
-rw-r--r--pjnath/src/pjturn-srv/allocation.c1039
-rw-r--r--pjnath/src/pjturn-srv/server.c154
-rw-r--r--pjnath/src/pjturn-srv/turn.h157
6 files changed, 1343 insertions, 70 deletions
diff --git a/pjnath/build/pjturn_srv.dsp b/pjnath/build/pjturn_srv.dsp
index 4a322345..34f90001 100644
--- a/pjnath/build/pjturn_srv.dsp
+++ b/pjnath/build/pjturn_srv.dsp
@@ -87,6 +87,10 @@ LINK32=link.exe
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
+SOURCE="..\src\pjturn-srv\allocation.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\pjturn-srv\listener_udp.c"
# End Source File
# Begin Source File
diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h
index 933c5e9f..8946a71b 100644
--- a/pjnath/include/pjnath/stun_msg.h
+++ b/pjnath/include/pjnath/stun_msg.h
@@ -67,11 +67,26 @@ enum pj_stun_method_e
PJ_STUN_ALLOCATE_METHOD = 3,
/**
- * STUN/TURN Send Indication as defined by draft-ietf-behave-turn
+ * STUN/TURN Refresh method as defined by draft-ietf-behave-turn
*/
PJ_STUN_REFRESH_METHOD = 4,
/**
+ * STUN/TURN Send indication as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_SEND_METHOD = 6,
+
+ /**
+ * STUN/TURN Data indication as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_DATA_METHOD = 7,
+
+ /**
+ * STUN/TURN ChannelBind as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_CHANNEL_BIND_METHOD = 9,
+
+ /**
* All known methods.
*/
PJ_STUN_METHOD_MAX
@@ -176,6 +191,7 @@ typedef enum pj_stun_msg_type
*/
PJ_STUN_BINDING_ERROR_RESPONSE = 0x0111,
+
/**
* STUN SHARED-SECRET reqeust.
*/
@@ -191,6 +207,7 @@ typedef enum pj_stun_msg_type
*/
PJ_STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112,
+
/**
* STUN/TURN Allocate Request
*/
@@ -206,6 +223,7 @@ typedef enum pj_stun_msg_type
*/
PJ_STUN_ALLOCATE_ERROR_RESPONSE = 0x0113,
+
/**
* STUN/TURN REFRESH Request
*/
@@ -219,7 +237,35 @@ typedef enum pj_stun_msg_type
/**
* Error response to STUN REFRESH request.
*/
- PJ_STUN_REFRESH_ERROR_RESPONSE = 0x0114
+ PJ_STUN_REFRESH_ERROR_RESPONSE = 0x0114,
+
+
+ /**
+ * TURN Send indication
+ */
+ PJ_STUN_SEND_INDICATION = 0x0016,
+
+
+ /**
+ * TURN Data indication
+ */
+ PJ_STUN_DATA_INDICATION = 0x0017,
+
+
+ /**
+ * STUN/TURN ChannelBind Request
+ */
+ PJ_STUN_CHANNEL_BIND_REQUEST = 0x0009,
+
+ /**
+ * Successful response to STUN ChannelBind request
+ */
+ PJ_STUN_CHANNEL_BIND_RESPONSE = 0x0109,
+
+ /**
+ * Error response to STUN ChannelBind request.
+ */
+ PJ_STUN_CHANNEL_BIND_ERROR_RESPONSE = 0x0119
} pj_stun_msg_type;
@@ -947,7 +993,7 @@ typedef struct pj_stun_uint_attr pj_stun_req_port_props_attr;
* Get the port number in TURN REQUESTED-PORT-PROPS attribute. The port
* number is returned in host byte order.
*/
-#define PJ_STUN_GET_RPP_PORT(u32) pj_ntons((pj_uint16_t)(u32 & 0x0000FFFFL))
+#define PJ_STUN_GET_RPP_PORT(u32) pj_ntohs((pj_uint16_t)(u32 & 0x0000FFFFL))
/**
* Convert port number in host byte order to 32bit value to be encoded in
diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
index d02d0fbd..f0b48c90 100644
--- a/pjnath/src/pjnath/stun_msg.c
+++ b/pjnath/src/pjnath/stun_msg.c
@@ -37,9 +37,14 @@ static const char *stun_method_names[PJ_STUN_METHOD_MAX] =
{
"Unknown", /* 0 */
"Binding", /* 1 */
- "Shared Secret", /* 2 */
+ "SharedSecret", /* 2 */
"Allocate", /* 3 */
"Refresh", /* 4 */
+ "???", /* 5 */
+ "Send", /* 6 */
+ "Data", /* 7 */
+ "???", /* 8 */
+ "ChannelBind", /* 9 */
};
static struct
diff --git a/pjnath/src/pjturn-srv/allocation.c b/pjnath/src/pjturn-srv/allocation.c
new file mode 100644
index 00000000..725863ce
--- /dev/null
+++ b/pjnath/src/pjturn-srv/allocation.c
@@ -0,0 +1,1039 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "turn.h"
+
+#define THIS_FILE "allocation.c"
+
+
+enum {
+ TIMER_ID_NONE,
+ TIMER_ID_TIMEOUT,
+ TIMER_ID_DESTROY
+};
+
+#define DESTROY_DELAY {0, 500}
+#define PEER_TABLE_SIZE 32
+
+/* ChannelData header */
+typedef struct channel_data_hdr
+{
+ pj_uint16_t ch_number;
+ pj_uint16_t length;
+} channel_data_hdr;
+
+
+/* Prototypes */
+static pj_status_t create_relay(pjturn_allocation *alloc,
+ const pjturn_allocation_req *req);
+static void on_rx_from_peer(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read);
+static void destroy_relay(pjturn_relay_res *relay);
+static pj_status_t stun_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+static pj_status_t stun_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+static pj_status_t stun_on_rx_indication(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+/* Log allocation error */
+static void alloc_err(pjturn_allocation *alloc, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(alloc->obj_name, "%s for client %s: %s",
+ title, alloc->info, errmsg));
+}
+
+/*
+ * Create new allocation.
+ */
+PJ_DEF(pj_status_t) pjturn_allocation_create(pjturn_listener *listener,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len,
+ const pj_stun_msg *msg,
+ const pjturn_allocation_req *req,
+ pjturn_allocation **p_alloc)
+{
+ pjturn_srv *srv = listener->server;
+ pj_pool_t *pool;
+ pjturn_allocation *alloc;
+ pj_stun_session_cb sess_cb;
+ char relay_info[80];
+ pj_status_t status;
+
+ pool = pj_pool_create(srv->core.pf, "alloc%p", 1000, 1000, NULL);
+
+ /* Init allocation structure */
+ alloc = PJ_POOL_ZALLOC_T(pool, pjturn_allocation);
+ alloc->pool = pool;
+ alloc->obj_name = pool->obj_name;
+ alloc->listener = listener;
+ alloc->clt_sock = PJ_INVALID_SOCKET;
+ alloc->relay.tp.sock = PJ_INVALID_SOCKET;
+
+ alloc->bandwidth = req->bandwidth;
+
+ alloc->hkey.tp_type = listener->tp_type;
+ pj_memcpy(&alloc->hkey.clt_addr, src_addr, src_addr_len);
+
+ status = pj_lock_create_recursive_mutex(pool, alloc->obj_name,
+ &alloc->lock);
+ if (status != PJ_SUCCESS) {
+ pjturn_allocation_destroy(alloc);
+ return status;
+ }
+
+ /* Create peer hash table */
+ alloc->peer_table = pj_hash_create(pool, PEER_TABLE_SIZE);
+
+ /* Create channel hash table */
+ alloc->ch_table = pj_hash_create(pool, PEER_TABLE_SIZE);
+
+ /* Print info */
+ pj_ansi_strcpy(alloc->info, pjturn_tp_type_name(listener->tp_type));
+ alloc->info[3] = ':';
+ pj_sockaddr_print(src_addr, alloc->info+4, sizeof(alloc->info)-4, 3);
+
+ /* Create STUN session to handle STUN communication with client */
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_send_msg = &stun_on_send_msg;
+ sess_cb.on_rx_request = &stun_on_rx_request;
+ sess_cb.on_rx_indication = &stun_on_rx_indication;
+ status = pj_stun_session_create(&srv->core.stun_cfg, alloc->obj_name,
+ &sess_cb, PJ_FALSE, &alloc->sess);
+ if (status != PJ_SUCCESS) {
+ pjturn_allocation_destroy(alloc);
+ return status;
+ }
+
+ /* Attach to STUN session */
+ pj_stun_session_set_user_data(alloc->sess, alloc);
+
+ /* Create the relay resource */
+ status = pjturn_allocation_create_relay(srv, alloc, msg, req,
+ &alloc->relay);
+ if (status != PJ_SUCCESS) {
+ pjturn_allocation_destroy(alloc);
+ return status;
+ }
+
+ /* Register this allocation */
+ pjturn_srv_register_allocation(srv, alloc);
+
+ pj_sockaddr_print(&alloc->relay.hkey.addr, relay_info,
+ sizeof(relay_info), 3);
+ PJ_LOG(4,(alloc->obj_name, "Client %s created, relay addr=%s:%s",
+ alloc->info, pjturn_tp_type_name(req->tp_type), relay_info));
+
+ /* Success */
+ *p_alloc = alloc;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy allocation.
+ */
+PJ_DECL(void) pjturn_allocation_destroy(pjturn_allocation *alloc)
+{
+ pj_pool_t *pool;
+
+ /* Unregister this allocation */
+ pjturn_srv_unregister_allocation(alloc->listener->server, alloc);
+
+ /* Destroy relay */
+ destroy_relay(&alloc->relay);
+
+ /* Must lock only after destroying relay otherwise deadlock */
+ if (alloc->lock) {
+ pj_lock_acquire(alloc->lock);
+ }
+
+ /* Destroy STUN session */
+ if (alloc->sess) {
+ pj_stun_session_destroy(alloc->sess);
+ alloc->sess = NULL;
+ }
+
+ /* Destroy lock */
+ if (alloc->lock) {
+ pj_lock_release(alloc->lock);
+ pj_lock_destroy(alloc->lock);
+ alloc->lock = NULL;
+ }
+
+ /* Destroy pool */
+ pool = alloc->pool;
+ if (pool) {
+ alloc->pool = NULL;
+ pj_pool_release(pool);
+ }
+}
+
+
+/* Destroy relay resource */
+static void destroy_relay(pjturn_relay_res *relay)
+{
+ if (relay->timer.id) {
+ pj_timer_heap_cancel(relay->allocation->listener->server->core.timer_heap,
+ &relay->timer);
+ relay->timer.id = PJ_FALSE;
+ }
+
+ if (relay->tp.key) {
+ pj_ioqueue_unregister(relay->tp.key);
+ relay->tp.key = NULL;
+ relay->tp.sock = PJ_INVALID_SOCKET;
+ } else if (relay->tp.sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(relay->tp.sock);
+ relay->tp.sock = PJ_INVALID_SOCKET;
+ }
+
+ /* Mark as shutdown */
+ relay->lifetime = 0;
+}
+
+/* Initiate shutdown sequence for this allocation */
+static void alloc_shutdown(pjturn_allocation *alloc)
+{
+ pj_time_val destroy_delay = DESTROY_DELAY;
+
+ /* Work with existing schedule */
+ if (alloc->relay.timer.id == TIMER_ID_TIMEOUT) {
+ /* Cancel existing timer */
+ pj_timer_heap_cancel(alloc->listener->server->core.timer_heap,
+ &alloc->relay.timer);
+ alloc->relay.timer.id = TIMER_ID_NONE;
+
+ } else if (alloc->relay.timer.id == TIMER_ID_DESTROY) {
+ /* We've been scheduled to be destroyed, ignore this
+ * shutdown request.
+ */
+ return;
+ }
+
+ pj_assert(alloc->relay.timer.id == TIMER_ID_NONE);
+
+ /* Shutdown relay socket */
+ destroy_relay(&alloc->relay);
+
+ /* Don't unregister from hash table because we still need to
+ * handle REFRESH retransmission.
+ */
+
+ /* Schedule destroy timer */
+ alloc->relay.timer.id = TIMER_ID_DESTROY;
+ pj_timer_heap_schedule(alloc->listener->server->core.timer_heap,
+ &alloc->relay.timer, &destroy_delay);
+}
+
+/* Reschedule timeout using current lifetime setting */
+static pj_status_t resched_timeout(pjturn_allocation *alloc)
+{
+ pj_time_val delay;
+ pj_status_t status;
+
+ pj_gettimeofday(&alloc->relay.expiry);
+ alloc->relay.expiry.sec += alloc->relay.lifetime;
+
+ pj_assert(alloc->relay.timer.id != TIMER_ID_DESTROY);
+ if (alloc->relay.timer.id != 0) {
+ pj_timer_heap_cancel(alloc->listener->server->core.timer_heap,
+ &alloc->relay.timer);
+ alloc->relay.timer.id = TIMER_ID_NONE;
+ }
+
+ delay.sec = alloc->relay.lifetime;
+ delay.msec = 0;
+
+ alloc->relay.timer.id = TIMER_ID_TIMEOUT;
+ status = pj_timer_heap_schedule(alloc->listener->server->core.timer_heap,
+ &alloc->relay.timer, &delay);
+ if (status != PJ_SUCCESS) {
+ alloc->relay.timer.id = TIMER_ID_NONE;
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Timer timeout callback */
+static void relay_timeout_cb(pj_timer_heap_t *heap, pj_timer_entry *e)
+{
+ pjturn_relay_res *rel;
+ pjturn_allocation *alloc;
+
+ rel = (pjturn_relay_res*) e->user_data;
+ alloc = rel->allocation;
+
+ if (e->id == TIMER_ID_TIMEOUT) {
+
+ e->id = TIMER_ID_NONE;
+
+ PJ_LOG(4,(alloc->obj_name,
+ "Client %s refresh timed-out, shutting down..",
+ alloc->info));
+
+ alloc_shutdown(alloc);
+
+ } else if (e->id == TIMER_ID_DESTROY) {
+ e->id = TIMER_ID_NONE;
+
+ PJ_LOG(4,(alloc->obj_name, "Client %s destroying..",
+ alloc->info));
+
+ pjturn_allocation_destroy(alloc);
+ }
+}
+
+
+/*
+ * Create relay.
+ */
+PJ_DEF(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv,
+ pjturn_allocation *alloc,
+ const pj_stun_msg *msg,
+ const pjturn_allocation_req *req,
+ pjturn_relay_res *relay)
+{
+ enum { RETRY = 40 };
+ pj_pool_t *pool = alloc->pool;
+ int retry, retry_max, sock_type;
+ pj_ioqueue_callback icb;
+ int af, namelen;
+ pj_stun_string_attr *sa;
+ pj_status_t status;
+
+ pj_bzero(relay, sizeof(*relay));
+
+ relay->allocation = alloc;
+ relay->tp.sock = PJ_INVALID_SOCKET;
+
+ /* TODO: get the requested address family from somewhere */
+ af = alloc->listener->addr.addr.sa_family;
+
+ /* Save realm */
+ sa = (pj_stun_string_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
+ PJ_ASSERT_RETURN(sa, PJ_EINVALIDOP);
+ pj_strdup(pool, &relay->realm, &sa->value);
+
+ /* Save username */
+ sa = (pj_stun_string_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
+ PJ_ASSERT_RETURN(sa, PJ_EINVALIDOP);
+ pj_strdup(pool, &relay->user, &sa->value);
+
+ /* Lifetime and timeout */
+ relay->lifetime = req->lifetime;
+ pj_timer_entry_init(&relay->timer, TIMER_ID_NONE, relay,
+ &relay_timeout_cb);
+ resched_timeout(alloc);
+
+ /* Transport type */
+ relay->hkey.tp_type = req->tp_type;
+
+ /* Create the socket */
+ if (req->tp_type == PJTURN_TP_UDP) {
+ sock_type = pj_SOCK_DGRAM();
+ } else if (req->tp_type == PJTURN_TP_TCP) {
+ sock_type = pj_SOCK_STREAM();
+ } else {
+ pj_assert(!"Unknown transport");
+ return PJ_EINVALIDOP;
+ }
+
+ status = pj_sock_socket(af, sock_type, 0, &relay->tp.sock);
+ if (status != PJ_SUCCESS) {
+ pj_bzero(relay, sizeof(*relay));
+ return status;
+ }
+
+ /* Find suitable port for this allocation */
+ if (req->rpp_port) {
+ retry_max = 1;
+ } else {
+ retry_max = RETRY;
+ }
+
+ for (retry=0; retry<retry_max; ++retry) {
+ pj_uint16_t port;
+ pj_sockaddr bound_addr;
+
+ pj_lock_acquire(srv->core.lock);
+
+ if (req->rpp_port) {
+ port = (pj_uint16_t) req->rpp_port;
+ } else if (req->tp_type == PJTURN_TP_UDP) {
+ port = (pj_uint16_t) srv->ports.next_udp++;
+ if (srv->ports.next_udp > srv->ports.max_udp)
+ srv->ports.next_udp = srv->ports.min_udp;
+ } else if (req->tp_type == PJTURN_TP_TCP) {
+ port = (pj_uint16_t) srv->ports.next_tcp++;
+ if (srv->ports.next_tcp > srv->ports.max_tcp)
+ srv->ports.next_tcp = srv->ports.min_tcp;
+ } else {
+ pj_assert(!"Invalid transport");
+ }
+
+ pj_lock_release(srv->core.lock);
+
+ pj_sockaddr_init(af, &bound_addr, NULL, port);
+
+ status = pj_sock_bind(relay->tp.sock, &bound_addr,
+ pj_sockaddr_get_len(&bound_addr));
+ if (status == PJ_SUCCESS)
+ break;
+ }
+
+ if (status != PJ_SUCCESS) {
+ /* Unable to allocate port */
+ PJ_LOG(4,(THIS_FILE, "bind() failed: err %d",
+ status));
+ pj_sock_close(relay->tp.sock);
+ relay->tp.sock = PJ_INVALID_SOCKET;
+ return status;
+ }
+
+ /* Init relay key */
+ namelen = sizeof(relay->hkey.addr);
+ status = pj_sock_getsockname(relay->tp.sock, &relay->hkey.addr, &namelen);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4,(THIS_FILE, "pj_sock_getsockname() failed: err %d",
+ status));
+ pj_sock_close(relay->tp.sock);
+ relay->tp.sock = PJ_INVALID_SOCKET;
+ return status;
+ }
+ if (!pj_sockaddr_has_addr(&relay->hkey.addr)) {
+ pj_sockaddr_copy_addr(&relay->hkey.addr, &alloc->listener->addr);
+ }
+
+ /* Init ioqueue */
+ pj_bzero(&icb, sizeof(icb));
+ icb.on_read_complete = &on_rx_from_peer;
+
+ status = pj_ioqueue_register_sock(pool, srv->core.ioqueue, relay->tp.sock,
+ relay, &icb, &relay->tp.key);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4,(THIS_FILE, "pj_ioqueue_register_sock() failed: err %d",
+ status));
+ pj_sock_close(relay->tp.sock);
+ relay->tp.sock = PJ_INVALID_SOCKET;
+ return status;
+ }
+
+ /* Kick off pending read operation */
+ pj_ioqueue_op_key_init(&relay->tp.read_key, sizeof(relay->tp.read_key));
+ on_rx_from_peer(relay->tp.key, &relay->tp.read_key, 0);
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+/* Create and send error response */
+static void send_reply_err(pjturn_allocation *alloc,
+ const pj_stun_msg *req,
+ pj_bool_t cache,
+ int code, const char *errmsg)
+{
+ pj_status_t status;
+ pj_str_t reason;
+ pj_stun_tx_data *tdata;
+
+ status = pj_stun_session_create_res(alloc->sess, req,
+ code, (errmsg?pj_cstr(&reason,errmsg):NULL),
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ alloc_err(alloc, "Error creating STUN error response", status);
+ return;
+ }
+
+ status = pj_stun_session_send_msg(alloc->sess, cache,
+ &alloc->hkey.clt_addr,
+ pj_sockaddr_get_len(&alloc->hkey.clt_addr),
+ tdata);
+ if (status != PJ_SUCCESS) {
+ alloc_err(alloc, "Error sending STUN error response", status);
+ return;
+ }
+}
+
+/* Create and send successful response */
+static void send_reply_ok(pjturn_allocation *alloc,
+ const pj_stun_msg *req)
+{
+ pj_status_t status;
+ unsigned interval;
+ pj_stun_tx_data *tdata;
+
+ status = pj_stun_session_create_res(alloc->sess, req, 0, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ alloc_err(alloc, "Error creating STUN success response", status);
+ return;
+ }
+
+ /* Calculate time to expiration */
+ if (alloc->relay.lifetime != 0) {
+ pj_time_val now;
+ pj_gettimeofday(&now);
+ interval = alloc->relay.expiry.sec - now.sec;
+ } else {
+ interval = 0;
+ }
+
+ /* Add LIFETIME. */
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_LIFETIME, interval);
+
+ /* Add BANDWIDTH */
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_BANDWIDTH,
+ alloc->bandwidth);
+
+ status = pj_stun_session_send_msg(alloc->sess, PJ_TRUE,
+ &alloc->hkey.clt_addr,
+ pj_sockaddr_get_len(&alloc->hkey.clt_addr),
+ tdata);
+ if (status != PJ_SUCCESS) {
+ alloc_err(alloc, "Error sending STUN success response", status);
+ return;
+ }
+}
+
+
+/* Create new permission */
+static pjturn_permission *create_permission(pjturn_allocation *alloc,
+ const pj_sockaddr_t *peer_addr,
+ unsigned addr_len)
+{
+ pjturn_permission *perm;
+
+ perm = PJ_POOL_ZALLOC_T(alloc->pool, pjturn_permission);
+ pj_memcpy(&perm->hkey.peer_addr, peer_addr, addr_len);
+
+ if (alloc->listener->tp_type == PJTURN_TP_UDP) {
+ perm->sock = alloc->listener->sock;
+ } else {
+ pj_assert(!"TCP is not supported yet");
+ return NULL;
+ }
+
+ perm->allocation = alloc;
+ perm->channel = PJTURN_INVALID_CHANNEL;
+
+ pj_gettimeofday(&perm->expiry);
+ perm->expiry.sec += PJTURN_PERM_TIMEOUT;
+
+ return perm;
+}
+
+/* Check if a permission isn't expired. Return NULL if expired. */
+static pjturn_permission *check_permission_expiry(pjturn_permission *perm)
+{
+ pjturn_allocation *alloc = perm->allocation;
+ pj_time_val now;
+
+ pj_gettimeofday(&now);
+ if (PJ_TIME_VAL_LT(perm->expiry, now)) {
+ /* Permission has not expired */
+ return perm;
+ }
+
+ /* Remove from permission hash table */
+ pj_hash_set(NULL, alloc->peer_table, &perm->hkey, sizeof(perm->hkey),
+ 0, NULL);
+
+ /* Remove from channel hash table, if assigned a channel number */
+ if (perm->channel != PJTURN_INVALID_CHANNEL) {
+ pj_hash_set(NULL, alloc->ch_table, &perm->channel,
+ sizeof(perm->channel), 0, NULL);
+ }
+
+ return NULL;
+}
+
+/* Lookup permission in hash table by the peer address */
+static pjturn_permission*
+lookup_permission_by_addr(pjturn_allocation *alloc,
+ const pj_sockaddr_t *peer_addr,
+ unsigned addr_len)
+{
+ pjturn_permission_key key;
+ pjturn_permission *perm;
+
+ pj_bzero(&key, sizeof(key));
+ pj_memcpy(&key, peer_addr, addr_len);
+
+ /* Lookup in peer hash table */
+ perm = (pjturn_permission*) pj_hash_get(alloc->peer_table, &key,
+ sizeof(key), NULL);
+ return check_permission_expiry(perm);
+}
+
+/* Lookup permission in hash table by the channel number */
+static pjturn_permission*
+lookup_permission_by_chnum(pjturn_allocation *alloc,
+ unsigned chnum)
+{
+ pj_uint16_t chnum16 = (pj_uint16_t)chnum;
+ pjturn_permission *perm;
+
+ /* Lookup in peer hash table */
+ perm = (pjturn_permission*) pj_hash_get(alloc->peer_table, &chnum16,
+ sizeof(chnum16), NULL);
+ return check_permission_expiry(perm);
+}
+
+/* Update permission because of data from client to peer.
+ * Return PJ_TRUE is permission is found.
+ */
+static pj_bool_t refresh_permission(pjturn_permission *perm)
+{
+ pj_gettimeofday(&perm->expiry);
+ if (perm->channel == PJTURN_INVALID_CHANNEL)
+ perm->expiry.sec += PJTURN_PERM_TIMEOUT;
+ else
+ perm->expiry.sec += PJTURN_CHANNEL_TIMEOUT;
+ return PJ_TRUE;
+}
+
+/*
+ * Handle incoming packet from client.
+ */
+PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc,
+ pjturn_pkt *pkt)
+{
+ pj_bool_t is_stun;
+ pj_status_t status;
+
+ /* Quickly check if this is STUN message */
+ is_stun = ((*((pj_uint8_t*)pkt->pkt) & 0xC0) == 0);
+
+ if (is_stun) {
+ /*
+ * This could be an incoming STUN requests or indications.
+ * Pass this through to the STUN session, which will call
+ * our stun_on_rx_request() or stun_on_rx_indication()
+ * callbacks.
+ */
+ unsigned options = PJ_STUN_CHECK_PACKET;
+ if (pkt->listener->tp_type == PJTURN_TP_UDP)
+ options |= PJ_STUN_IS_DATAGRAM;
+
+ status = pj_stun_session_on_rx_pkt(alloc->sess, pkt->pkt, pkt->len,
+ options, NULL,
+ &pkt->src.clt_addr,
+ pkt->src_addr_len);
+ if (status != PJ_SUCCESS) {
+ alloc_err(alloc, "Error handling STUN packet", status);
+ return;
+ }
+
+ } else {
+ /*
+ * This is not a STUN packet, must be ChannelData packet.
+ */
+ channel_data_hdr *cd = (channel_data_hdr*)pkt->pkt;
+ pjturn_permission *perm;
+ pj_ssize_t len;
+
+ /* For UDP check the packet length */
+ if (alloc->listener->tp_type == PJTURN_TP_UDP) {
+ if (pkt->len < pj_ntohs(cd->length)+sizeof(*cd)) {
+ PJ_LOG(4,(alloc->obj_name,
+ "ChannelData from %s discarded: UDP size error",
+ alloc->info));
+ return;
+ }
+ } else {
+ pj_assert(!"Unsupported transport");
+ return;
+ }
+
+ perm = lookup_permission_by_chnum(alloc, pj_ntohs(cd->ch_number));
+ if (!perm) {
+ /* Discard */
+ PJ_LOG(4,(alloc->obj_name,
+ "ChannelData from %s discarded: not found",
+ alloc->info));
+ return;
+ }
+
+ /* Relay the data */
+ len = pj_ntohs(cd->length);
+ pj_sock_sendto(alloc->relay.tp.sock, cd+1, &len, 0,
+ &perm->hkey.peer_addr,
+ pj_sockaddr_get_len(&perm->hkey.peer_addr));
+
+ /* Refresh permission */
+ refresh_permission(perm);
+ }
+}
+
+/*
+ * Handle incoming packet from peer. This function is called by
+ * on_rx_from_peer().
+ */
+static void on_rx_peer_pkt(pjturn_allocation *alloc,
+ pjturn_relay_res *rel,
+ char *pkt, pj_size_t len,
+ const pj_sockaddr *src_addr)
+{
+ pjturn_permission *perm;
+
+ /* Lookup permission */
+ perm = lookup_permission_by_addr(alloc, src_addr,
+ pj_sockaddr_get_len(src_addr));
+ if (perm == NULL) {
+ /* No permission, discard data */
+ return;
+ }
+
+ /* Send Data Indication or ChannelData, depends on whether
+ * this permission is attached to a channel number.
+ */
+ if (perm->channel != PJTURN_INVALID_CHANNEL) {
+ /* Send ChannelData */
+ channel_data_hdr *cd = (channel_data_hdr*)rel->tp.tx_pkt;
+
+ if (len > PJTURN_MAX_PKT_LEN) {
+ char peer_addr[80];
+ pj_sockaddr_print(src_addr, peer_addr, sizeof(peer_addr), 3);
+ PJ_LOG(1,(alloc->obj_name, "Client %s: discarded data from %s "
+ "because it's too long (%d bytes)",
+ alloc->info, peer_addr, len));
+ return;
+ }
+
+ /* Init header */
+ cd->ch_number = pj_htons(perm->channel);
+ cd->length = pj_htons((pj_uint16_t)len);
+
+ /* Copy data */
+ pj_memcpy(rel->tp.rx_pkt+sizeof(channel_data_hdr), pkt, len);
+
+ /* Send to client */
+ pjturn_listener_sendto(alloc->listener, rel->tp.tx_pkt,
+ len+sizeof(channel_data_hdr), 0,
+ &alloc->hkey.clt_addr,
+ pj_sockaddr_get_len(&alloc->hkey.clt_addr));
+ } else {
+ /* Send Data Indication */
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+
+ status = pj_stun_session_create_ind(alloc->sess,
+ PJ_STUN_DATA_INDICATION, &tdata);
+ if (status != PJ_SUCCESS) {
+ alloc_err(alloc, "Error creating Data indication", status);
+ return;
+ }
+ }
+}
+
+/*
+ * ioqueue notification on RX packets from the relay socket.
+ */
+static void on_rx_from_peer(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ pjturn_relay_res *rel;
+ pj_status_t status;
+
+ rel = (pjturn_relay_res*) pj_ioqueue_get_user_data(key);
+
+ do {
+ if (bytes_read > 0) {
+ on_rx_peer_pkt(rel->allocation, rel, rel->tp.rx_pkt,
+ bytes_read, &rel->tp.src_addr);
+ }
+
+ /* Read next packet */
+ bytes_read = sizeof(rel->tp.rx_pkt);
+ rel->tp.src_addr_len = sizeof(rel->tp.src_addr);
+ status = pj_ioqueue_recvfrom(key, op_key,
+ rel->tp.rx_pkt, &bytes_read, 0,
+ &rel->tp.src_addr,
+ &rel->tp.src_addr_len);
+
+ if (status != PJ_EPENDING && status != PJ_SUCCESS)
+ bytes_read = -status;
+
+ } while (status != PJ_EPENDING && status != PJ_ECANCELLED);
+
+}
+
+/*
+ * Callback notification from STUN session when it wants to send
+ * a STUN message towards the client.
+ */
+static pj_status_t stun_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ pjturn_allocation *alloc;
+
+ alloc = (pjturn_allocation*) pj_stun_session_get_user_data(sess);
+
+ return pjturn_listener_sendto(alloc->listener, pkt, pkt_size, 0,
+ dst_addr, addr_len);
+}
+
+/*
+ * Callback notification from STUN session when it receives STUN
+ * requests. This callback was trigger by STUN incoming message
+ * processing in pjturn_allocation_on_rx_client_pkt().
+ */
+static pj_status_t stun_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pjturn_allocation *alloc;
+
+ alloc = (pjturn_allocation*) pj_stun_session_get_user_data(sess);
+
+ /* Refuse to serve any request if we've been shutdown */
+ if (alloc->relay.lifetime == 0) {
+ send_reply_err(alloc, msg, PJ_TRUE,
+ PJ_STUN_SC_ALLOCATION_MISMATCH, NULL);
+ return PJ_SUCCESS;
+ }
+
+ if (msg->hdr.type == PJ_STUN_REFRESH_REQUEST) {
+ /*
+ * Handle REFRESH request
+ */
+ pj_stun_lifetime_attr *lifetime;
+ pj_stun_bandwidth_attr *bandwidth;
+
+ /* Get LIFETIME attribute */
+ lifetime = (pj_stun_lifetime_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0);
+
+ /* Get BANDWIDTH attribute */
+ bandwidth = (pj_stun_bandwidth_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_BANDWIDTH, 0);
+
+ if (lifetime && lifetime->value==0) {
+ /*
+ * This is deallocation request.
+ */
+ alloc->relay.lifetime = 0;
+
+ /* Respond first */
+ send_reply_ok(alloc, msg);
+
+ /* Shutdown allocation */
+ PJ_LOG(4,(alloc->obj_name,
+ "Client %s request to dealloc, shutting down",
+ alloc->info));
+
+ alloc_shutdown(alloc);
+
+ } else {
+ /*
+ * This is a refresh request.
+ */
+
+ /* Update lifetime */
+ if (lifetime) {
+ alloc->relay.lifetime = lifetime->value;
+ }
+
+ /* Update bandwidth */
+ // TODO:
+
+ /* Update expiration timer */
+ resched_timeout(alloc);
+
+ /* Send reply */
+ send_reply_ok(alloc, msg);
+ }
+
+ } else if (msg->hdr.type == PJ_STUN_CHANNEL_BIND_REQUEST) {
+ /*
+ * ChannelBind request.
+ */
+ pj_stun_channel_number_attr *ch_attr;
+ pj_stun_peer_addr_attr *peer_attr;
+ pjturn_permission *p1, *p2;
+
+ ch_attr = (pj_stun_channel_number_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_CHANNEL_NUMBER, 0);
+ peer_attr = (pj_stun_peer_addr_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PEER_ADDR, 0);
+
+ if (!ch_attr || !peer_attr) {
+ send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, NULL);
+ return PJ_SUCCESS;
+ }
+
+ /* Find permission with the channel number */
+ p1 = lookup_permission_by_chnum(alloc, PJ_STUN_GET_CH_NB(ch_attr->value));
+
+ /* If permission is found, this is supposed to be a channel bind
+ * refresh. Make sure it's for the same peer.
+ */
+ if (p1) {
+ if (pj_sockaddr_cmp(&p1->hkey.peer_addr, &peer_attr->sockaddr)) {
+ /* Address mismatch. Send 400 */
+ send_reply_err(alloc, msg, PJ_TRUE,
+ PJ_STUN_SC_BAD_REQUEST,
+ "Peer address mismatch");
+ return PJ_SUCCESS;
+ }
+
+ /* Refresh permission */
+ refresh_permission(p1);
+
+ /* Done */
+ return PJ_SUCCESS;
+ }
+
+ /* If permission is not found, create a new one. Make sure the peer
+ * has not alreadyy assigned with a channel number.
+ */
+ p2 = lookup_permission_by_addr(alloc, &peer_attr->sockaddr,
+ pj_sockaddr_get_len(&peer_attr->sockaddr));
+ if (p2 && p2->channel != PJTURN_INVALID_CHANNEL) {
+ send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST,
+ "Peer address already assigned a channel number");
+ return PJ_SUCCESS;
+ }
+
+ /* Create permission if it doesn't exist */
+ if (!p2) {
+ p2 = create_permission(alloc, &peer_attr->sockaddr,
+ pj_sockaddr_get_len(&peer_attr->sockaddr));
+ if (!p2)
+ return PJ_SUCCESS;
+ }
+
+ /* Assign channel number to permission */
+ p2->channel = PJ_STUN_GET_CH_NB(ch_attr->value);
+
+ /* Update */
+ refresh_permission(p2);
+
+ /* Reply */
+ send_reply_ok(alloc, msg);
+
+ return PJ_SUCCESS;
+
+ } else if (msg->hdr.type == PJ_STUN_ALLOCATE_REQUEST) {
+
+ /* Respond with 437 (section 6.3 turn-07) */
+ send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_ALLOCATION_MISMATCH, NULL);
+
+ } else {
+
+ /* Respond with Bad Request? */
+ send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, NULL);
+
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Callback notification from STUN session when it receives STUN
+ * indications. This callback was trigger by STUN incoming message
+ * processing in pjturn_allocation_on_rx_client_pkt().
+ */
+static pj_status_t stun_on_rx_indication(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_peer_addr_attr *peer_attr;
+ pj_stun_data_attr *data_attr;
+ pjturn_allocation *alloc;
+ pjturn_permission *perm;
+
+ alloc = (pjturn_allocation*) pj_stun_session_get_user_data(sess);
+
+ /* Only expect Send Indication */
+ if (msg->hdr.type != PJ_STUN_SEND_INDICATION) {
+ /* Ignore */
+ return PJ_SUCCESS;
+ }
+
+ /* Get PEER-ADDRESS attribute */
+ peer_attr = (pj_stun_peer_addr_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_PEER_ADDR, 0);
+
+ /* MUST have PEER-ADDRESS attribute */
+ if (!peer_attr)
+ return PJ_SUCCESS;
+
+ /* Get DATA attribute */
+ data_attr = (pj_stun_data_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_DATA, 0);
+
+ /* Create/update/refresh the permission */
+ perm = lookup_permission_by_addr(alloc, &peer_attr->sockaddr,
+ pj_sockaddr_get_len(&peer_attr->sockaddr));
+ if (perm == NULL) {
+ perm = create_permission(alloc, &peer_attr->sockaddr,
+ pj_sockaddr_get_len(&peer_attr->sockaddr));
+ }
+ refresh_permission(perm);
+
+ /* Return if we don't have data */
+ if (data_attr == NULL)
+ return PJ_SUCCESS;
+
+ /* Relay the data to client */
+ if (alloc->hkey.tp_type == PJTURN_TP_UDP) {
+ pj_ssize_t len = data_attr->length;
+ pj_sock_sendto(alloc->listener->sock, data_attr->data,
+ &len, 0, &peer_attr->sockaddr,
+ pj_sockaddr_get_len(&peer_attr->sockaddr));
+ } else {
+ pj_assert(!"TCP is not supported");
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjnath/src/pjturn-srv/server.c b/pjnath/src/pjturn-srv/server.c
index 15268348..c9fc40cf 100644
--- a/pjnath/src/pjturn-srv/server.c
+++ b/pjnath/src/pjturn-srv/server.c
@@ -35,11 +35,6 @@
#define DEF_LIFETIME 300
-/* Globals */
-PJ_DEF_DATA(int) PJTURN_TP_UDP = 1;
-PJ_DEF_DATA(int) PJTURN_TP_TCP = 2;
-PJ_DEF_DATA(int) PJTURN_TP_TLS = 3;
-
/* Prototypes */
static pj_status_t on_tx_stun_msg( pj_stun_session *sess,
const void *pkt,
@@ -53,6 +48,19 @@ static pj_status_t on_rx_stun_request(pj_stun_session *sess,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len);
+/*
+ * Get transport type name.
+ */
+PJ_DEF(const char*) pjturn_tp_type_name(int tp_type)
+{
+ /* Must be 3 characters long! */
+ if (tp_type == PJTURN_TP_UDP)
+ return "UDP";
+ else if (tp_type == PJTURN_TP_TCP)
+ return "TCP";
+ else
+ return "???";
+}
/*
* Create server.
@@ -97,7 +105,6 @@ PJ_DEF(pj_status_t) pjturn_srv_create( pj_pool_factory *pf,
/* Create hash tables */
srv->tables.alloc = pj_hash_create(pool, MAX_CLIENTS);
srv->tables.res = pj_hash_create(pool, MAX_CLIENTS);
- srv->tables.peer = pj_hash_create(pool, MAX_CLIENTS*MAX_PEERS_PER_CLIENT);
/* Init ports settings */
srv->ports.min_udp = srv->ports.next_udp = MIN_PORT;
@@ -165,6 +172,41 @@ PJ_DEF(pj_status_t) pjturn_srv_add_listener(pjturn_srv *srv,
return PJ_SUCCESS;
}
+/**
+ * Register an allocation.
+ */
+PJ_DEF(pj_status_t) pjturn_srv_register_allocation(pjturn_srv *srv,
+ pjturn_allocation *alloc)
+{
+ /* Add to hash tables */
+ pj_lock_acquire(srv->core.lock);
+ pj_hash_set(alloc->pool, srv->tables.alloc,
+ &alloc->hkey, sizeof(alloc->hkey), 0, alloc);
+ pj_hash_set(alloc->pool, srv->tables.res,
+ &alloc->relay.hkey, sizeof(alloc->relay.hkey), 0,
+ &alloc->relay);
+ pj_lock_release(srv->core.lock);
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Unregister an allocation.
+ */
+PJ_DEF(pj_status_t) pjturn_srv_unregister_allocation(pjturn_srv *srv,
+ pjturn_allocation *alloc)
+{
+ /* Unregister from hash tables */
+ pj_lock_acquire(srv->core.lock);
+ pj_hash_set(alloc->pool, srv->tables.alloc,
+ &alloc->hkey, sizeof(alloc->hkey), 0, NULL);
+ pj_hash_set(alloc->pool, srv->tables.res,
+ &alloc->relay.hkey, sizeof(alloc->relay.hkey), 0, NULL);
+ pj_lock_release(srv->core.lock);
+
+ return PJ_SUCCESS;
+}
+
/* Callback from our own STUN session to send packet */
static pj_status_t on_tx_stun_msg( pj_stun_session *sess,
@@ -184,19 +226,20 @@ static pj_status_t on_tx_stun_msg( pj_stun_session *sess,
}
/* Create and send error response */
-static pj_status_t respond_error(pj_stun_sess *sess, const pj_stun_msg *req,
- pj_bool_t cache, int code, const char *err_msg,
- const pj_sockaddr_t *addr, unsigned addr_len)
+static pj_status_t respond_error(pj_stun_session *sess, const pj_stun_msg *req,
+ pj_bool_t cache, int code, const char *errmsg,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
{
pj_status_t status;
pj_str_t reason;
pj_stun_tx_data *tdata;
status = pj_stun_session_create_res(sess, req,
- code, (err_msg?pj_cstr(&reason,err_msg):NULL),
+ code, (errmsg?pj_cstr(&reason,errmsg):NULL),
&tdata);
if (status != PJ_SUCCESS)
- return statys;
+ return status;
status = pj_stun_session_send_msg(sess, cache, dst_addr, addr_len, tdata);
return status;
@@ -220,7 +263,8 @@ static pj_status_t parse_allocate_req(pjturn_allocation_req *cfg,
pj_bzero(cfg, sizeof(*cfg));
/* Get BANDWIDTH attribute, if any. */
- attr_bw = pj_stun_msg_find_attr(msg, PJ_STUN_BANDWIDTH_ATTR, 0);
+ attr_bw = (pj_stun_uint_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_BANDWIDTH, 0);
if (attr_bw) {
cfg->bandwidth = attr_bw->value;
} else {
@@ -229,14 +273,15 @@ static pj_status_t parse_allocate_req(pjturn_allocation_req *cfg,
/* Check if we can satisfy the bandwidth */
if (cfg->bandwidth > MAX_CLIENT_BANDWIDTH) {
- respond_error(sess, msg, PJ_FALSE,
+ respond_error(sess, req, PJ_FALSE,
PJ_STUN_SC_ALLOCATION_QUOTA_REACHED,
"Invalid bandwidth", src_addr, src_addr_len);
return -1;
}
/* Get REQUESTED-TRANSPORT attribute, is any */
- attr_req_tp = pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REQ_TRANSPORT, 0);
+ attr_req_tp = (pj_stun_uint_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_TRANSPORT, 0);
if (attr_req_tp) {
cfg->tp_type = PJ_STUN_GET_RT_PROTO(attr_req_tp->value);
} else {
@@ -245,21 +290,23 @@ static pj_status_t parse_allocate_req(pjturn_allocation_req *cfg,
/* Can only support UDP for now */
if (cfg->tp_type != PJTURN_TP_UDP) {
- respond_error(sess, msg, PJ_FALSE,
+ respond_error(sess, req, PJ_FALSE,
PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO,
NULL, src_addr, src_addr_len);
return -1;
}
/* Get REQUESTED-IP attribute, if any */
- attr_req_ip = pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REQ_IP, 0);
+ attr_req_ip = (pj_stun_sockaddr_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_IP, 0);
if (attr_req_ip) {
- pj_memcpy(&cfg->addr, &attr_req_ip->sockaddr,
- sizeof(attr_req_ip->sockaddr));
+ pj_sockaddr_print(&attr_req_ip->sockaddr, cfg->addr,
+ sizeof(cfg->addr), 0);
}
/* Get REQUESTED-PORT-PROPS attribute, if any */
- attr_rpp = pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REQ_PORT_PROPS, 0);
+ attr_rpp = (pj_stun_uint_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_PORT_PROPS, 0);
if (attr_rpp) {
cfg->rpp_bits = PJ_STUN_GET_RPP_BITS(attr_rpp->value);
cfg->rpp_port = PJ_STUN_GET_RPP_PORT(attr_rpp->value);
@@ -269,11 +316,12 @@ static pj_status_t parse_allocate_req(pjturn_allocation_req *cfg,
}
/* Get LIFETIME attribute */
- attr_lifetime = pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0);
+ attr_lifetime = (pj_stun_uint_attr*)
+ pj_stun_msg_find_attr(req, PJ_STUN_ATTR_LIFETIME, 0);
if (attr_lifetime) {
cfg->lifetime = attr_lifetime->value;
if (cfg->lifetime < MIN_LIFETIME || cfg->lifetime > MAX_LIFETIME) {
- respond_error(sess, msg, PJ_FALSE,
+ respond_error(sess, req, PJ_FALSE,
PJ_STUN_SC_BAD_REQUEST,
"Invalid LIFETIME value", src_addr,
src_addr_len);
@@ -295,10 +343,14 @@ static pj_status_t on_rx_stun_request(pj_stun_session *sess,
unsigned src_addr_len)
{
pjturn_listener *listener;
+ pjturn_srv *srv;
pjturn_allocation_req req;
+ pjturn_allocation *alloc;
+ pj_stun_tx_data *tdata;
pj_status_t status;
listener = (pjturn_listener*) pj_stun_session_get_user_data(sess);
+ srv = listener->server;
/* Handle strayed REFRESH request */
if (msg->hdr.type == PJ_STUN_REFRESH_REQUEST) {
@@ -321,8 +373,63 @@ static pj_status_t on_rx_stun_request(pj_stun_session *sess,
if (status != PJ_SUCCESS)
return status;
- /* Ready to allocate now */
+ /* Create new allocation. The relay resource will be allocated
+ * in this function.
+ */
+ status = pjturn_allocation_create(listener, src_addr, src_addr_len,
+ msg, &req, &alloc);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ return respond_error(sess, msg, PJ_FALSE, PJ_STUN_SC_SERVER_ERROR,
+ errmsg, src_addr, src_addr_len);
+ }
+
+ /* Respond the original ALLOCATE request */
+ status = pj_stun_session_create_res(srv->core.stun_sess[listener->id],
+ msg, 0, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pjturn_allocation_destroy(alloc);
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ return respond_error(sess, msg, PJ_FALSE, PJ_STUN_SC_SERVER_ERROR,
+ errmsg, src_addr, src_addr_len);
+ }
+ /* Add RELAYED-ADDRESS attribute */
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_RELAY_ADDR, PJ_TRUE,
+ &alloc->relay.hkey.addr,
+ pj_sockaddr_get_len(&alloc->relay.hkey.addr));
+
+ /* Add LIFETIME. */
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_LIFETIME,
+ (unsigned)alloc->relay.lifetime);
+
+ /* Add BANDWIDTH */
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_BANDWIDTH,
+ alloc->bandwidth);
+
+ /* Add RESERVATION-TOKEN */
+ PJ_TODO(ADD_RESERVATION_TOKEN);
+
+ /* Add XOR-MAPPED-ADDRESS */
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE,
+ &alloc->hkey.clt_addr,
+ pj_sockaddr_get_len(&alloc->hkey.clt_addr));
+
+ /* Send the response */
+ pj_stun_session_send_msg(srv->core.stun_sess[listener->id], PJ_TRUE,
+ src_addr, src_addr_len, tdata);
+
+ /* Done. */
+ return PJ_SUCCESS;
}
@@ -330,7 +437,6 @@ static pj_status_t on_rx_stun_request(pj_stun_session *sess,
static void handle_new_client( pjturn_srv *srv,
pjturn_pkt *pkt)
{
- pj_stun_msg *req, *res;
unsigned options, lis_id;
pj_status_t status;
@@ -391,7 +497,7 @@ PJ_DEF(void) pjturn_srv_on_rx_pkt( pjturn_srv *srv,
* allocation.
*/
if (alloc) {
- pjturn_allocation_on_rx_pkt(alloc, pkt);
+ pjturn_allocation_on_rx_client_pkt(alloc, pkt);
} else {
/* Otherwise this is a new client */
handle_new_client(srv, pkt);
diff --git a/pjnath/src/pjturn-srv/turn.h b/pjnath/src/pjturn-srv/turn.h
index 39a17fae..a53cabbf 100644
--- a/pjnath/src/pjturn-srv/turn.h
+++ b/pjnath/src/pjturn-srv/turn.h
@@ -33,6 +33,8 @@ typedef struct pjturn_pkt pjturn_pkt;
#define PJTURN_INVALID_CHANNEL 0xFFFF
#define PJTURN_NO_TIMEOUT ((long)0x7FFFFFFF)
#define PJTURN_MAX_PKT_LEN 3000
+#define PJTURN_PERM_TIMEOUT 300
+#define PJTURN_CHANNEL_TIMEOUT 600
/** Transport types */
enum {
@@ -40,6 +42,10 @@ enum {
PJTURN_TP_TCP = 6 /**< TCP. */
};
+/**
+ * Get transport type name string.
+ */
+PJ_DECL(const char*) pjturn_tp_type_name(int tp_type);
/**
* This structure describes TURN relay resource. An allocation allocates
@@ -54,28 +60,49 @@ struct pjturn_relay_res
/** Transport/relay address */
pj_sockaddr addr;
- } key;
-
- /** Pool for this resource. */
- pj_pool_t *pool;
-
- /** Mutex */
- pj_lock_t *lock;
+ } hkey;
/** Allocation who requested or reserved this resource. */
pjturn_allocation *allocation;
- /** Time when this resource times out */
- pj_time_val timeout;
-
/** Username used in credential */
pj_str_t user;
/** Realm used in credential. */
pj_str_t realm;
- /** Transport/relay socket */
- pj_sock_t sock;
+ /** Lifetime, in seconds. */
+ unsigned lifetime;
+
+ /** Relay/allocation expiration time */
+ pj_time_val expiry;
+
+ /** Timeout timer entry */
+ pj_timer_entry timer;
+
+ /** Transport. */
+ struct {
+ /** Transport/relay socket */
+ pj_sock_t sock;
+
+ /** Transport/relay ioqueue */
+ pj_ioqueue_key_t *key;
+
+ /** Read operation key. */
+ pj_ioqueue_op_key_t read_key;
+
+ /** The incoming packet buffer */
+ char rx_pkt[PJTURN_MAX_PKT_LEN];
+
+ /** Source address of the packet. */
+ pj_sockaddr src_addr;
+
+ /** Source address length */
+ int src_addr_len;
+
+ /** The outgoing packet buffer. This must be 3wbit aligned. */
+ char tx_pkt[PJTURN_MAX_PKT_LEN+4];
+ } tp;
};
@@ -104,7 +131,7 @@ typedef struct pjturn_allocation_req
unsigned tp_type;
/** Requested IP */
- pj_sockaddr addr;
+ char addr[PJ_INET6_ADDRSTRLEN];
/** Requested bandwidth */
unsigned bandwidth;
@@ -127,11 +154,17 @@ typedef struct pjturn_allocation_req
struct pjturn_allocation
{
/** Hash table key to identify client. */
- pjturn_allocation_key key;
+ pjturn_allocation_key hkey;
/** Pool for this allocation. */
pj_pool_t *pool;
+ /** Object name for logging identification */
+ char *obj_name;
+
+ /** Client info (IP address and port) */
+ char info[80];
+
/** Mutex */
pj_lock_t *lock;
@@ -147,33 +180,44 @@ struct pjturn_allocation
/** Relay resource reserved by this allocation, if any */
pjturn_relay_res *resv;
+ /** Requested bandwidth */
+ unsigned bandwidth;
+
+ /** STUN session for this client */
+ pj_stun_session *sess;
+
+ /** Peer hash table (keyed by peer address) */
+ pj_hash_table_t *peer_table;
+
+ /** Channel hash table (keyed by channel number) */
+ pj_hash_table_t *ch_table;
};
/**
- * This structure describes TURN pjturn_permission or channel.
+ * This structure describes the hash table key to lookup TURN
+ * permission.
*/
-struct pjturn_permission
+typedef struct pjturn_permission_key
{
- /** Hash table key */
- struct {
- /** Transport type. */
- pj_uint16_t tp_type;
+ /** Peer address. */
+ pj_sockaddr peer_addr;
- /** Transport socket. If TCP is used, the value will be the actual
- * TCP socket. If UDP is used, the value will be the relay address
- */
- pj_sock_t sock;
+} pjturn_permission_key;
- /** Peer address. */
- pj_sockaddr peer_addr;
- } key;
- /** Pool for this permission. */
- pj_pool_t *pool;
+/**
+ * This structure describes TURN pjturn_permission or channel.
+ */
+struct pjturn_permission
+{
+ /** Hash table key */
+ pjturn_permission_key hkey;
- /** Mutex */
- pj_lock_t *lock;
+ /** Transport socket. If TCP is used, the value will be the actual
+ * TCP socket. If UDP is used, the value will be the relay address
+ */
+ pj_sock_t sock;
/** TURN allocation that owns this permission/channel */
pjturn_allocation *allocation;
@@ -183,16 +227,38 @@ struct pjturn_permission
*/
pj_uint16_t channel;
- /** Permission timeout. */
- pj_time_val timeout;
+ /** Permission expiration time. */
+ pj_time_val expiry;
};
/**
- * Handle incoming packet.
+ * Create new allocation.
+ */
+PJ_DECL(pj_status_t) pjturn_allocation_create(pjturn_listener *listener,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len,
+ const pj_stun_msg *msg,
+ const pjturn_allocation_req *req,
+ pjturn_allocation **p_alloc);
+/**
+ * Destroy allocation.
*/
-PJ_DECL(void) pjturn_allocation_on_rx_pkt(pjturn_allocation *alloc,
- pjturn_pkt *pkt);
+PJ_DECL(void) pjturn_allocation_destroy(pjturn_allocation *alloc);
+/**
+ * Create relay.
+ */
+PJ_DECL(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv,
+ pjturn_allocation *alloc,
+ const pj_stun_msg *msg,
+ const pjturn_allocation_req *req,
+ pjturn_relay_res *relay);
+
+/**
+ * Handle incoming packet from client.
+ */
+PJ_DECL(void) pjturn_allocation_on_rx_client_pkt(pjturn_allocation *alloc,
+ pjturn_pkt *pkt);
/****************************************************************************/
/*
@@ -250,7 +316,7 @@ struct pjturn_pkt
/** Listener that owns this. */
pjturn_listener *listener;
- /** Packet buffer. */
+ /** Packet buffer (must be 32bit aligned). */
pj_uint8_t pkt[PJTURN_MAX_PKT_LEN];
/** Size of the packet */
@@ -357,11 +423,6 @@ struct pjturn_srv
*/
pj_hash_table_t *res;
- /** Permission hash table, indexed by transport type, socket handle,
- * and peer address.
- */
- pj_hash_table_t *peer;
-
} tables;
/** Ports settings */
@@ -407,6 +468,18 @@ PJ_DECL(pj_status_t) pjturn_srv_add_listener(pjturn_srv *srv,
pjturn_listener *lis);
/**
+ * Register an allocation.
+ */
+PJ_DECL(pj_status_t) pjturn_srv_register_allocation(pjturn_srv *srv,
+ pjturn_allocation *alloc);
+
+/**
+ * Unregister an allocation.
+ */
+PJ_DECL(pj_status_t) pjturn_srv_unregister_allocation(pjturn_srv *srv,
+ pjturn_allocation *alloc);
+
+/**
* This callback is called by UDP listener on incoming packet.
*/
PJ_DECL(void) pjturn_srv_on_rx_pkt(pjturn_srv *srv,