diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-03-08 00:54:04 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-03-08 00:54:04 +0000 |
commit | e30de41312b8f3552dea56db5eb73d99889f6941 (patch) | |
tree | 94af9773e03bffd53f58620a87c26da737db62df /pjnath/src/pjturn-srv | |
parent | 2eeebb98f8e4fc3f84fe8d16548035b605ff0929 (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/src/pjturn-srv')
-rw-r--r-- | pjnath/src/pjturn-srv/allocation.c | 1039 | ||||
-rw-r--r-- | pjnath/src/pjturn-srv/server.c | 154 | ||||
-rw-r--r-- | pjnath/src/pjturn-srv/turn.h | 157 |
3 files changed, 1284 insertions, 66 deletions
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, |