| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| #include <pjnath/turn_session.h> |
| #include <pjnath/errno.h> |
| #include <pjlib-util/srv_resolver.h> |
| #include <pj/addr_resolv.h> |
| #include <pj/assert.h> |
| #include <pj/errno.h> |
| #include <pj/hash.h> |
| #include <pj/lock.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/pool.h> |
| #include <pj/rand.h> |
| #include <pj/sock.h> |
| |
| #define PJ_TURN_CHANNEL_MIN 0x4000 |
| #define PJ_TURN_CHANNEL_MAX 0x7FFF /* inclusive */ |
| #define PJ_TURN_CHANNEL_HTABLE_SIZE 8 |
| #define PJ_TURN_PERM_HTABLE_SIZE 8 |
| |
| static const char *state_names[] = |
| { |
| "Null", |
| "Resolving", |
| "Resolved", |
| "Allocating", |
| "Ready", |
| "Deallocating", |
| "Deallocated", |
| "Destroying" |
| }; |
| |
| enum timer_id_t |
| { |
| TIMER_NONE, |
| TIMER_KEEP_ALIVE, |
| TIMER_DESTROY |
| }; |
| |
| /* This structure describes a channel binding. A channel binding is index by |
| * the channel number or IP address and port number of the peer. |
| */ |
| struct ch_t |
| { |
| /* The channel number */ |
| pj_uint16_t num; |
| |
| /* PJ_TRUE if we've received successful response to ChannelBind request |
| * for this channel. |
| */ |
| pj_bool_t bound; |
| |
| /* The peer IP address and port */ |
| pj_sockaddr addr; |
| |
| /* The channel binding expiration */ |
| pj_time_val expiry; |
| }; |
| |
| |
| /* This structure describes a permission. A permission is identified by the |
| * IP address only. |
| */ |
| struct perm_t |
| { |
| /* Cache of hash value to speed-up lookup */ |
| pj_uint32_t hval; |
| |
| /* The permission IP address. The port number MUST be zero */ |
| pj_sockaddr addr; |
| |
| /* Number of peers that uses this permission. */ |
| unsigned peer_cnt; |
| |
| /* Automatically renew this permission once it expires? */ |
| pj_bool_t renew; |
| |
| /* The permission expiration */ |
| pj_time_val expiry; |
| |
| /* Arbitrary/random pointer value (token) to map this perm with the |
| * request to create it. It is used to invalidate this perm when the |
| * request fails. |
| */ |
| void *req_token; |
| }; |
| |
| |
| /* The TURN client session structure */ |
| struct pj_turn_session |
| { |
| pj_pool_t *pool; |
| const char *obj_name; |
| pj_turn_session_cb cb; |
| void *user_data; |
| pj_stun_config stun_cfg; |
| pj_bool_t is_destroying; |
| |
| pj_grp_lock_t *grp_lock; |
| int busy; |
| |
| pj_turn_state_t state; |
| pj_status_t last_status; |
| pj_bool_t pending_destroy; |
| |
| pj_stun_session *stun; |
| |
| unsigned lifetime; |
| int ka_interval; |
| pj_time_val expiry; |
| |
| pj_timer_heap_t *timer_heap; |
| pj_timer_entry timer; |
| |
| pj_uint16_t default_port; |
| |
| pj_uint16_t af; |
| pj_turn_tp_type conn_type; |
| pj_uint16_t srv_addr_cnt; |
| pj_sockaddr *srv_addr_list; |
| pj_sockaddr *srv_addr; |
| |
| pj_bool_t pending_alloc; |
| pj_turn_alloc_param alloc_param; |
| |
| pj_sockaddr mapped_addr; |
| pj_sockaddr relay_addr; |
| |
| pj_hash_table_t *ch_table; |
| pj_hash_table_t *perm_table; |
| |
| pj_uint32_t send_ind_tsx_id[3]; |
| /* tx_pkt must be 16bit aligned */ |
| pj_uint8_t tx_pkt[PJ_TURN_MAX_PKT_LEN]; |
| |
| pj_uint16_t next_ch; |
| }; |
| |
| |
| /* |
| * Prototypes. |
| */ |
| static void sess_shutdown(pj_turn_session *sess, |
| pj_status_t status); |
| static void turn_sess_on_destroy(void *comp); |
| static void do_destroy(pj_turn_session *sess); |
| static void send_refresh(pj_turn_session *sess, int lifetime); |
| static pj_status_t stun_on_send_msg(pj_stun_session *sess, |
| void *token, |
| const void *pkt, |
| pj_size_t pkt_size, |
| const pj_sockaddr_t *dst_addr, |
| unsigned addr_len); |
| static void stun_on_request_complete(pj_stun_session *sess, |
| pj_status_t status, |
| void *token, |
| pj_stun_tx_data *tdata, |
| const pj_stun_msg *response, |
| 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, |
| void *token, |
| const pj_sockaddr_t *src_addr, |
| unsigned src_addr_len); |
| static void dns_srv_resolver_cb(void *user_data, |
| pj_status_t status, |
| const pj_dns_srv_record *rec); |
| static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len, |
| pj_bool_t update, |
| pj_bool_t bind_channel); |
| static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess, |
| pj_uint16_t chnum); |
| static struct perm_t *lookup_perm(pj_turn_session *sess, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len, |
| pj_bool_t update); |
| static void invalidate_perm(pj_turn_session *sess, |
| struct perm_t *perm); |
| static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e); |
| |
| |
| /* |
| * Create default pj_turn_alloc_param. |
| */ |
| PJ_DEF(void) pj_turn_alloc_param_default(pj_turn_alloc_param *prm) |
| { |
| pj_bzero(prm, sizeof(*prm)); |
| } |
| |
| /* |
| * Duplicate pj_turn_alloc_param. |
| */ |
| PJ_DEF(void) pj_turn_alloc_param_copy( pj_pool_t *pool, |
| pj_turn_alloc_param *dst, |
| const pj_turn_alloc_param *src) |
| { |
| PJ_UNUSED_ARG(pool); |
| pj_memcpy(dst, src, sizeof(*dst)); |
| } |
| |
| /* |
| * Get TURN state name. |
| */ |
| PJ_DEF(const char*) pj_turn_state_name(pj_turn_state_t state) |
| { |
| return state_names[state]; |
| } |
| |
| /* |
| * Create TURN client session. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_create( const pj_stun_config *cfg, |
| const char *name, |
| int af, |
| pj_turn_tp_type conn_type, |
| pj_grp_lock_t *grp_lock, |
| const pj_turn_session_cb *cb, |
| unsigned options, |
| void *user_data, |
| pj_turn_session **p_sess) |
| { |
| pj_pool_t *pool; |
| pj_turn_session *sess; |
| pj_stun_session_cb stun_cb; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(cfg && cfg->pf && cb && p_sess, PJ_EINVAL); |
| PJ_ASSERT_RETURN(cb->on_send_pkt, PJ_EINVAL); |
| |
| PJ_UNUSED_ARG(options); |
| |
| if (name == NULL) |
| name = "turn%p"; |
| |
| /* Allocate and create TURN session */ |
| pool = pj_pool_create(cfg->pf, name, PJNATH_POOL_LEN_TURN_SESS, |
| PJNATH_POOL_INC_TURN_SESS, NULL); |
| sess = PJ_POOL_ZALLOC_T(pool, pj_turn_session); |
| sess->pool = pool; |
| sess->obj_name = pool->obj_name; |
| sess->timer_heap = cfg->timer_heap; |
| sess->af = (pj_uint16_t)af; |
| sess->conn_type = conn_type; |
| sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; |
| sess->user_data = user_data; |
| sess->next_ch = PJ_TURN_CHANNEL_MIN; |
| |
| /* Copy STUN session */ |
| pj_memcpy(&sess->stun_cfg, cfg, sizeof(pj_stun_config)); |
| |
| /* Copy callback */ |
| pj_memcpy(&sess->cb, cb, sizeof(*cb)); |
| |
| /* Peer hash table */ |
| sess->ch_table = pj_hash_create(pool, PJ_TURN_CHANNEL_HTABLE_SIZE); |
| |
| /* Permission hash table */ |
| sess->perm_table = pj_hash_create(pool, PJ_TURN_PERM_HTABLE_SIZE); |
| |
| /* Session lock */ |
| if (grp_lock) { |
| sess->grp_lock = grp_lock; |
| } else { |
| status = pj_grp_lock_create(pool, NULL, &sess->grp_lock); |
| if (status != PJ_SUCCESS) { |
| pj_pool_release(pool); |
| return status; |
| } |
| } |
| |
| pj_grp_lock_add_ref(sess->grp_lock); |
| pj_grp_lock_add_handler(sess->grp_lock, pool, sess, |
| &turn_sess_on_destroy); |
| |
| /* Timer */ |
| pj_timer_entry_init(&sess->timer, TIMER_NONE, sess, &on_timer_event); |
| |
| /* Create STUN session */ |
| pj_bzero(&stun_cb, sizeof(stun_cb)); |
| stun_cb.on_send_msg = &stun_on_send_msg; |
| stun_cb.on_request_complete = &stun_on_request_complete; |
| stun_cb.on_rx_indication = &stun_on_rx_indication; |
| status = pj_stun_session_create(&sess->stun_cfg, sess->obj_name, &stun_cb, |
| PJ_FALSE, sess->grp_lock, &sess->stun); |
| if (status != PJ_SUCCESS) { |
| do_destroy(sess); |
| return status; |
| } |
| |
| /* Attach ourself to STUN session */ |
| pj_stun_session_set_user_data(sess->stun, sess); |
| |
| /* Done */ |
| |
| PJ_LOG(4,(sess->obj_name, "TURN client session created")); |
| |
| *p_sess = sess; |
| return PJ_SUCCESS; |
| } |
| |
| |
| static void turn_sess_on_destroy(void *comp) |
| { |
| pj_turn_session *sess = (pj_turn_session*) comp; |
| |
| /* Destroy pool */ |
| if (sess->pool) { |
| pj_pool_t *pool = sess->pool; |
| |
| PJ_LOG(4,(sess->obj_name, "TURN client session destroyed")); |
| |
| sess->pool = NULL; |
| pj_pool_release(pool); |
| } |
| } |
| |
| /* Destroy */ |
| static void do_destroy(pj_turn_session *sess) |
| { |
| PJ_LOG(4,(sess->obj_name, "TURN session destroy request, ref_cnt=%d", |
| pj_grp_lock_get_ref(sess->grp_lock))); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| if (sess->is_destroying) { |
| pj_grp_lock_release(sess->grp_lock); |
| return; |
| } |
| |
| sess->is_destroying = PJ_TRUE; |
| pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, TIMER_NONE); |
| pj_stun_session_destroy(sess->stun); |
| |
| pj_grp_lock_dec_ref(sess->grp_lock); |
| pj_grp_lock_release(sess->grp_lock); |
| } |
| |
| |
| /* Set session state */ |
| static void set_state(pj_turn_session *sess, enum pj_turn_state_t state) |
| { |
| pj_turn_state_t old_state = sess->state; |
| |
| if (state==sess->state) |
| return; |
| |
| PJ_LOG(4,(sess->obj_name, "State changed %s --> %s", |
| state_names[old_state], state_names[state])); |
| sess->state = state; |
| |
| if (sess->cb.on_state) { |
| (*sess->cb.on_state)(sess, old_state, state); |
| } |
| } |
| |
| /* |
| * Notify application and shutdown the TURN session. |
| */ |
| static void sess_shutdown(pj_turn_session *sess, |
| pj_status_t status) |
| { |
| pj_bool_t can_destroy = PJ_TRUE; |
| |
| PJ_LOG(4,(sess->obj_name, "Request to shutdown in state %s, cause:%d", |
| state_names[sess->state], status)); |
| |
| if (sess->last_status == PJ_SUCCESS && status != PJ_SUCCESS) |
| sess->last_status = status; |
| |
| switch (sess->state) { |
| case PJ_TURN_STATE_NULL: |
| break; |
| case PJ_TURN_STATE_RESOLVING: |
| /* Wait for DNS callback invoked, it will call the this function |
| * again. If the callback happens to get pending_destroy==FALSE, |
| * the TURN allocation will call this function again. |
| */ |
| sess->pending_destroy = PJ_TRUE; |
| can_destroy = PJ_FALSE; |
| break; |
| case PJ_TURN_STATE_RESOLVED: |
| break; |
| case PJ_TURN_STATE_ALLOCATING: |
| /* We need to wait until allocation complete */ |
| sess->pending_destroy = PJ_TRUE; |
| can_destroy = PJ_FALSE; |
| break; |
| case PJ_TURN_STATE_READY: |
| /* Send REFRESH with LIFETIME=0 */ |
| can_destroy = PJ_FALSE; |
| send_refresh(sess, 0); |
| break; |
| case PJ_TURN_STATE_DEALLOCATING: |
| can_destroy = PJ_FALSE; |
| /* This may recursively call this function again with |
| * state==PJ_TURN_STATE_DEALLOCATED. |
| */ |
| /* No need to deallocate as we're already deallocating! |
| * See https://trac.pjsip.org/repos/ticket/1551 |
| send_refresh(sess, 0); |
| */ |
| break; |
| case PJ_TURN_STATE_DEALLOCATED: |
| case PJ_TURN_STATE_DESTROYING: |
| break; |
| } |
| |
| if (can_destroy) { |
| /* Schedule destroy */ |
| pj_time_val delay = {0, 0}; |
| |
| set_state(sess, PJ_TURN_STATE_DESTROYING); |
| |
| pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, |
| TIMER_NONE); |
| pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, |
| &delay, TIMER_DESTROY, |
| sess->grp_lock); |
| } |
| } |
| |
| |
| /* |
| * Public API to destroy TURN client session. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_shutdown(pj_turn_session *sess) |
| { |
| PJ_ASSERT_RETURN(sess, PJ_EINVAL); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| sess_shutdown(sess, PJ_SUCCESS); |
| |
| pj_grp_lock_release(sess->grp_lock); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /** |
| * Forcefully destroy the TURN session. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_destroy( pj_turn_session *sess, |
| pj_status_t last_err) |
| { |
| PJ_ASSERT_RETURN(sess, PJ_EINVAL); |
| |
| if (last_err != PJ_SUCCESS && sess->last_status == PJ_SUCCESS) |
| sess->last_status = last_err; |
| set_state(sess, PJ_TURN_STATE_DEALLOCATED); |
| sess_shutdown(sess, PJ_SUCCESS); |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Get TURN session info. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_get_info( pj_turn_session *sess, |
| pj_turn_session_info *info) |
| { |
| pj_time_val now; |
| |
| PJ_ASSERT_RETURN(sess && info, PJ_EINVAL); |
| |
| pj_gettimeofday(&now); |
| |
| info->state = sess->state; |
| info->conn_type = sess->conn_type; |
| info->lifetime = sess->expiry.sec - now.sec; |
| info->last_status = sess->last_status; |
| |
| if (sess->srv_addr) |
| pj_memcpy(&info->server, sess->srv_addr, sizeof(info->server)); |
| else |
| pj_bzero(&info->server, sizeof(info->server)); |
| |
| pj_memcpy(&info->mapped_addr, &sess->mapped_addr, |
| sizeof(sess->mapped_addr)); |
| pj_memcpy(&info->relay_addr, &sess->relay_addr, |
| sizeof(sess->relay_addr)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Re-assign user data. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_set_user_data( pj_turn_session *sess, |
| void *user_data) |
| { |
| sess->user_data = user_data; |
| return PJ_SUCCESS; |
| } |
| |
| |
| /** |
| * Retrieve user data. |
| */ |
| PJ_DEF(void*) pj_turn_session_get_user_data(pj_turn_session *sess) |
| { |
| return sess->user_data; |
| } |
| |
| /** |
| * Get group lock. |
| */ |
| PJ_DEF(pj_grp_lock_t *) pj_turn_session_get_grp_lock(pj_turn_session *sess) |
| { |
| PJ_ASSERT_RETURN(sess, NULL); |
| return sess->grp_lock; |
| } |
| |
| /* |
| * Configure message logging. By default all flags are enabled. |
| * |
| * @param sess The TURN client session. |
| * @param flags Bitmask combination of #pj_stun_sess_msg_log_flag |
| */ |
| PJ_DEF(void) pj_turn_session_set_log( pj_turn_session *sess, |
| unsigned flags) |
| { |
| pj_stun_session_set_log(sess->stun, flags); |
| } |
| |
| |
| /* |
| * Set software name |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_set_software_name( pj_turn_session *sess, |
| const pj_str_t *sw) |
| { |
| pj_status_t status; |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| status = pj_stun_session_set_software_name(sess->stun, sw); |
| pj_grp_lock_release(sess->grp_lock); |
| |
| return status; |
| } |
| |
| |
| /** |
| * Set the server or domain name of the server. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_set_server( pj_turn_session *sess, |
| const pj_str_t *domain, |
| int default_port, |
| pj_dns_resolver *resolver) |
| { |
| pj_sockaddr tmp_addr; |
| pj_bool_t is_ip_addr; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && domain, PJ_EINVAL); |
| PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_NULL, PJ_EINVALIDOP); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| /* See if "domain" contains just IP address */ |
| tmp_addr.addr.sa_family = sess->af; |
| status = pj_inet_pton(sess->af, domain, |
| pj_sockaddr_get_addr(&tmp_addr)); |
| is_ip_addr = (status == PJ_SUCCESS); |
| |
| if (!is_ip_addr && resolver) { |
| /* Resolve with DNS SRV resolution, and fallback to DNS A resolution |
| * if default_port is specified. |
| */ |
| unsigned opt = 0; |
| pj_str_t res_name; |
| |
| switch (sess->conn_type) { |
| case PJ_TURN_TP_UDP: |
| res_name = pj_str("_turn._udp."); |
| break; |
| case PJ_TURN_TP_TCP: |
| res_name = pj_str("_turn._tcp."); |
| break; |
| case PJ_TURN_TP_TLS: |
| res_name = pj_str("_turns._tcp."); |
| break; |
| default: |
| status = PJNATH_ETURNINTP; |
| goto on_return; |
| } |
| |
| /* Fallback to DNS A only if default port is specified */ |
| if (default_port>0 && default_port<65536) { |
| opt = PJ_DNS_SRV_FALLBACK_A; |
| sess->default_port = (pj_uint16_t)default_port; |
| } |
| |
| PJ_LOG(5,(sess->obj_name, "Resolving %.*s%.*s with DNS SRV", |
| (int)res_name.slen, res_name.ptr, |
| (int)domain->slen, domain->ptr)); |
| set_state(sess, PJ_TURN_STATE_RESOLVING); |
| |
| /* User may have destroyed us in the callback */ |
| if (sess->state != PJ_TURN_STATE_RESOLVING) { |
| status = PJ_ECANCELLED; |
| goto on_return; |
| } |
| |
| status = pj_dns_srv_resolve(domain, &res_name, default_port, |
| sess->pool, resolver, opt, sess, |
| &dns_srv_resolver_cb, NULL); |
| if (status != PJ_SUCCESS) { |
| set_state(sess, PJ_TURN_STATE_NULL); |
| goto on_return; |
| } |
| |
| } else { |
| /* Resolver is not specified, resolve with standard gethostbyname(). |
| * The default_port MUST be specified in this case. |
| */ |
| pj_addrinfo *ai; |
| unsigned i, cnt; |
| |
| /* Default port must be specified */ |
| PJ_ASSERT_RETURN(default_port>0 && default_port<65536, PJ_EINVAL); |
| sess->default_port = (pj_uint16_t)default_port; |
| |
| cnt = PJ_TURN_MAX_DNS_SRV_CNT; |
| ai = (pj_addrinfo*) |
| pj_pool_calloc(sess->pool, cnt, sizeof(pj_addrinfo)); |
| |
| PJ_LOG(5,(sess->obj_name, "Resolving %.*s with DNS A", |
| (int)domain->slen, domain->ptr)); |
| set_state(sess, PJ_TURN_STATE_RESOLVING); |
| |
| /* User may have destroyed us in the callback */ |
| if (sess->state != PJ_TURN_STATE_RESOLVING) { |
| status = PJ_ECANCELLED; |
| goto on_return; |
| } |
| |
| status = pj_getaddrinfo(sess->af, domain, &cnt, ai); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| sess->srv_addr_cnt = (pj_uint16_t)cnt; |
| sess->srv_addr_list = (pj_sockaddr*) |
| pj_pool_calloc(sess->pool, cnt, |
| sizeof(pj_sockaddr)); |
| for (i=0; i<cnt; ++i) { |
| pj_sockaddr *addr = &sess->srv_addr_list[i]; |
| pj_memcpy(addr, &ai[i].ai_addr, sizeof(pj_sockaddr)); |
| addr->addr.sa_family = sess->af; |
| addr->ipv4.sin_port = pj_htons(sess->default_port); |
| } |
| |
| sess->srv_addr = &sess->srv_addr_list[0]; |
| set_state(sess, PJ_TURN_STATE_RESOLVED); |
| } |
| |
| on_return: |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| |
| /** |
| * Set credential to be used by the session. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_set_credential(pj_turn_session *sess, |
| const pj_stun_auth_cred *cred) |
| { |
| PJ_ASSERT_RETURN(sess && cred, PJ_EINVAL); |
| PJ_ASSERT_RETURN(sess->stun, PJ_EINVALIDOP); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| pj_stun_session_set_credential(sess->stun, PJ_STUN_AUTH_LONG_TERM, cred); |
| |
| pj_grp_lock_release(sess->grp_lock); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /** |
| * Create TURN allocation. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_alloc(pj_turn_session *sess, |
| const pj_turn_alloc_param *param) |
| { |
| pj_stun_tx_data *tdata; |
| pj_bool_t retransmit; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess, PJ_EINVAL); |
| PJ_ASSERT_RETURN(sess->state>PJ_TURN_STATE_NULL && |
| sess->state<=PJ_TURN_STATE_RESOLVED, |
| PJ_EINVALIDOP); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| if (param && param != &sess->alloc_param) |
| pj_turn_alloc_param_copy(sess->pool, &sess->alloc_param, param); |
| |
| if (sess->state < PJ_TURN_STATE_RESOLVED) { |
| sess->pending_alloc = PJ_TRUE; |
| |
| PJ_LOG(4,(sess->obj_name, "Pending ALLOCATE in state %s", |
| state_names[sess->state])); |
| |
| pj_grp_lock_release(sess->grp_lock); |
| return PJ_SUCCESS; |
| |
| } |
| |
| /* Ready to allocate */ |
| pj_assert(sess->state == PJ_TURN_STATE_RESOLVED); |
| |
| /* Create a bare request */ |
| status = pj_stun_session_create_req(sess->stun, PJ_STUN_ALLOCATE_REQUEST, |
| PJ_STUN_MAGIC, NULL, &tdata); |
| if (status != PJ_SUCCESS) { |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| /* MUST include REQUESTED-TRANSPORT attribute */ |
| pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_REQ_TRANSPORT, |
| PJ_STUN_SET_RT_PROTO(PJ_TURN_TP_UDP)); |
| |
| /* Include BANDWIDTH if requested */ |
| if (sess->alloc_param.bandwidth > 0) { |
| pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_BANDWIDTH, |
| sess->alloc_param.bandwidth); |
| } |
| |
| /* Include LIFETIME if requested */ |
| if (sess->alloc_param.lifetime > 0) { |
| pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_LIFETIME, |
| sess->alloc_param.lifetime); |
| } |
| |
| /* Server address must be set */ |
| pj_assert(sess->srv_addr != NULL); |
| |
| /* Send request */ |
| set_state(sess, PJ_TURN_STATE_ALLOCATING); |
| retransmit = (sess->conn_type == PJ_TURN_TP_UDP); |
| status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, |
| retransmit, sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr), |
| tdata); |
| if (status != PJ_SUCCESS) { |
| /* Set state back to RESOLVED. We don't want to destroy session now, |
| * let the application do it if it wants to. |
| */ |
| set_state(sess, PJ_TURN_STATE_RESOLVED); |
| } |
| |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| |
| /* |
| * Install or renew permissions |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_set_perm( pj_turn_session *sess, |
| unsigned addr_cnt, |
| const pj_sockaddr addr[], |
| unsigned options) |
| { |
| pj_stun_tx_data *tdata; |
| pj_hash_iterator_t it_buf, *it; |
| void *req_token; |
| unsigned i, attr_added=0; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && addr_cnt && addr, PJ_EINVAL); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| /* Create a bare CreatePermission request */ |
| status = pj_stun_session_create_req(sess->stun, |
| PJ_STUN_CREATE_PERM_REQUEST, |
| PJ_STUN_MAGIC, NULL, &tdata); |
| if (status != PJ_SUCCESS) { |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| /* Create request token to map the request to the perm structures |
| * which the request belongs. |
| */ |
| req_token = (void*)(pj_ssize_t)pj_rand(); |
| |
| /* Process the addresses */ |
| for (i=0; i<addr_cnt; ++i) { |
| struct perm_t *perm; |
| |
| /* Lookup the perm structure and create if it doesn't exist */ |
| perm = lookup_perm(sess, &addr[i], pj_sockaddr_get_len(&addr[i]), |
| PJ_TRUE); |
| perm->renew = (options & 0x01); |
| |
| /* Only add to the request if the request doesn't contain this |
| * address yet. |
| */ |
| if (perm->req_token != req_token) { |
| perm->req_token = req_token; |
| |
| /* Add XOR-PEER-ADDRESS */ |
| status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_XOR_PEER_ADDR, |
| PJ_TRUE, |
| &addr[i], |
| sizeof(addr[i])); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| ++attr_added; |
| } |
| } |
| |
| pj_assert(attr_added != 0); |
| |
| /* Send the request */ |
| status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, |
| (sess->conn_type==PJ_TURN_TP_UDP), |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr), |
| tdata); |
| if (status != PJ_SUCCESS) { |
| /* tdata is already destroyed */ |
| tdata = NULL; |
| goto on_error; |
| } |
| |
| pj_grp_lock_release(sess->grp_lock); |
| return PJ_SUCCESS; |
| |
| on_error: |
| /* destroy tdata */ |
| if (tdata) { |
| pj_stun_msg_destroy_tdata(sess->stun, tdata); |
| } |
| /* invalidate perm structures associated with this request */ |
| it = pj_hash_first(sess->perm_table, &it_buf); |
| while (it) { |
| struct perm_t *perm = (struct perm_t*) |
| pj_hash_this(sess->perm_table, it); |
| it = pj_hash_next(sess->perm_table, it); |
| if (perm->req_token == req_token) |
| invalidate_perm(sess, perm); |
| } |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| /* |
| * Send REFRESH |
| */ |
| static void send_refresh(pj_turn_session *sess, int lifetime) |
| { |
| pj_stun_tx_data *tdata; |
| pj_status_t status; |
| |
| PJ_ASSERT_ON_FAIL(sess->state==PJ_TURN_STATE_READY, return); |
| |
| /* Create a bare REFRESH request */ |
| status = pj_stun_session_create_req(sess->stun, PJ_STUN_REFRESH_REQUEST, |
| PJ_STUN_MAGIC, NULL, &tdata); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| /* Add LIFETIME */ |
| if (lifetime >= 0) { |
| pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_LIFETIME, lifetime); |
| } |
| |
| /* Send request */ |
| if (lifetime == 0) { |
| set_state(sess, PJ_TURN_STATE_DEALLOCATING); |
| } |
| |
| status = pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, |
| (sess->conn_type==PJ_TURN_TP_UDP), |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr), |
| tdata); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| return; |
| |
| on_error: |
| if (lifetime == 0) { |
| set_state(sess, PJ_TURN_STATE_DEALLOCATED); |
| sess_shutdown(sess, status); |
| } |
| } |
| |
| |
| /** |
| * Relay data to the specified peer through the session. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_sendto( pj_turn_session *sess, |
| const pj_uint8_t *pkt, |
| unsigned pkt_len, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len) |
| { |
| struct ch_t *ch; |
| struct perm_t *perm; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && pkt && pkt_len && addr && addr_len, |
| PJ_EINVAL); |
| |
| /* Return error if we're not ready */ |
| if (sess->state != PJ_TURN_STATE_READY) { |
| return PJ_EIGNORED; |
| } |
| |
| /* Lock session now */ |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| /* Lookup permission first */ |
| perm = lookup_perm(sess, addr, pj_sockaddr_get_len(addr), PJ_FALSE); |
| if (perm == NULL) { |
| /* Permission doesn't exist, install it first */ |
| char ipstr[PJ_INET6_ADDRSTRLEN+2]; |
| |
| PJ_LOG(4,(sess->obj_name, |
| "sendto(): IP %s has no permission, requesting it first..", |
| pj_sockaddr_print(addr, ipstr, sizeof(ipstr), 2))); |
| |
| status = pj_turn_session_set_perm(sess, 1, (const pj_sockaddr*)addr, |
| 0); |
| if (status != PJ_SUCCESS) { |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| } |
| |
| /* See if the peer is bound to a channel number */ |
| ch = lookup_ch_by_addr(sess, addr, pj_sockaddr_get_len(addr), |
| PJ_FALSE, PJ_FALSE); |
| if (ch && ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound) { |
| unsigned total_len; |
| |
| /* Peer is assigned a channel number, we can use ChannelData */ |
| pj_turn_channel_data *cd = (pj_turn_channel_data*)sess->tx_pkt; |
| |
| pj_assert(sizeof(*cd)==4); |
| |
| /* Calculate total length, including paddings */ |
| total_len = (pkt_len + sizeof(*cd) + 3) & (~3); |
| if (total_len > sizeof(sess->tx_pkt)) { |
| status = PJ_ETOOBIG; |
| goto on_return; |
| } |
| |
| cd->ch_number = pj_htons((pj_uint16_t)ch->num); |
| cd->length = pj_htons((pj_uint16_t)pkt_len); |
| pj_memcpy(cd+1, pkt, pkt_len); |
| |
| pj_assert(sess->srv_addr != NULL); |
| |
| status = sess->cb.on_send_pkt(sess, sess->tx_pkt, total_len, |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr)); |
| |
| } else { |
| /* Use Send Indication. */ |
| pj_stun_sockaddr_attr peer_attr; |
| pj_stun_binary_attr data_attr; |
| pj_stun_msg send_ind; |
| pj_size_t send_ind_len; |
| |
| /* Increment counter */ |
| ++sess->send_ind_tsx_id[2]; |
| |
| /* Create blank SEND-INDICATION */ |
| status = pj_stun_msg_init(&send_ind, PJ_STUN_SEND_INDICATION, |
| PJ_STUN_MAGIC, |
| (const pj_uint8_t*)sess->send_ind_tsx_id); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| /* Add XOR-PEER-ADDRESS */ |
| pj_stun_sockaddr_attr_init(&peer_attr, PJ_STUN_ATTR_XOR_PEER_ADDR, |
| PJ_TRUE, addr, addr_len); |
| pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr*)&peer_attr); |
| |
| /* Add DATA attribute */ |
| pj_stun_binary_attr_init(&data_attr, NULL, PJ_STUN_ATTR_DATA, NULL, 0); |
| data_attr.data = (pj_uint8_t*)pkt; |
| data_attr.length = pkt_len; |
| pj_stun_msg_add_attr(&send_ind, (pj_stun_attr_hdr*)&data_attr); |
| |
| /* Encode the message */ |
| status = pj_stun_msg_encode(&send_ind, sess->tx_pkt, |
| sizeof(sess->tx_pkt), 0, |
| NULL, &send_ind_len); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| /* Send the Send Indication */ |
| status = sess->cb.on_send_pkt(sess, sess->tx_pkt, |
| (unsigned)send_ind_len, |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr)); |
| } |
| |
| on_return: |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| |
| /** |
| * Bind a peer address to a channel number. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_bind_channel(pj_turn_session *sess, |
| const pj_sockaddr_t *peer_adr, |
| unsigned addr_len) |
| { |
| struct ch_t *ch; |
| pj_stun_tx_data *tdata; |
| pj_uint16_t ch_num; |
| pj_status_t status; |
| |
| PJ_ASSERT_RETURN(sess && peer_adr && addr_len, PJ_EINVAL); |
| PJ_ASSERT_RETURN(sess->state == PJ_TURN_STATE_READY, PJ_EINVALIDOP); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| /* Create blank ChannelBind request */ |
| status = pj_stun_session_create_req(sess->stun, |
| PJ_STUN_CHANNEL_BIND_REQUEST, |
| PJ_STUN_MAGIC, NULL, &tdata); |
| if (status != PJ_SUCCESS) |
| goto on_return; |
| |
| /* Lookup if this peer has already been assigned a number */ |
| ch = lookup_ch_by_addr(sess, peer_adr, pj_sockaddr_get_len(peer_adr), |
| PJ_TRUE, PJ_FALSE); |
| pj_assert(ch); |
| |
| if (ch->num != PJ_TURN_INVALID_CHANNEL) { |
| /* Channel is already bound. This is a refresh request. */ |
| ch_num = ch->num; |
| } else { |
| PJ_ASSERT_ON_FAIL(sess->next_ch <= PJ_TURN_CHANNEL_MAX, |
| {status=PJ_ETOOMANY; goto on_return;}); |
| ch->num = ch_num = sess->next_ch++; |
| } |
| |
| /* Add CHANNEL-NUMBER attribute */ |
| pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_CHANNEL_NUMBER, |
| PJ_STUN_SET_CH_NB(ch_num)); |
| |
| /* Add XOR-PEER-ADDRESS attribute */ |
| pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_XOR_PEER_ADDR, PJ_TRUE, |
| peer_adr, addr_len); |
| |
| /* Send the request, associate peer data structure with tdata |
| * for future reference when we receive the ChannelBind response. |
| */ |
| status = pj_stun_session_send_msg(sess->stun, ch, PJ_FALSE, |
| (sess->conn_type==PJ_TURN_TP_UDP), |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr), |
| tdata); |
| |
| on_return: |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| |
| /** |
| * Notify TURN client session upon receiving a packet from server. |
| * The packet maybe a STUN packet or ChannelData packet. |
| */ |
| PJ_DEF(pj_status_t) pj_turn_session_on_rx_pkt(pj_turn_session *sess, |
| void *pkt, |
| pj_size_t pkt_len, |
| pj_size_t *parsed_len) |
| { |
| pj_bool_t is_stun; |
| pj_status_t status; |
| pj_bool_t is_datagram; |
| |
| /* Packet could be ChannelData or STUN message (response or |
| * indication). |
| */ |
| |
| /* Start locking the session */ |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| is_datagram = (sess->conn_type==PJ_TURN_TP_UDP); |
| |
| /* Quickly check if this is STUN message */ |
| is_stun = ((((pj_uint8_t*)pkt)[0] & 0xC0) == 0); |
| |
| if (is_stun) { |
| /* This looks like STUN, give it to the STUN session */ |
| unsigned options; |
| |
| options = PJ_STUN_CHECK_PACKET | PJ_STUN_NO_FINGERPRINT_CHECK; |
| if (is_datagram) |
| options |= PJ_STUN_IS_DATAGRAM; |
| status=pj_stun_session_on_rx_pkt(sess->stun, pkt, pkt_len, |
| options, NULL, parsed_len, |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr)); |
| |
| } else { |
| /* This must be ChannelData. */ |
| pj_turn_channel_data cd; |
| struct ch_t *ch; |
| |
| if (pkt_len < 4) { |
| if (parsed_len) *parsed_len = 0; |
| return PJ_ETOOSMALL; |
| } |
| |
| /* Decode ChannelData packet */ |
| pj_memcpy(&cd, pkt, sizeof(pj_turn_channel_data)); |
| cd.ch_number = pj_ntohs(cd.ch_number); |
| cd.length = pj_ntohs(cd.length); |
| |
| /* Check that size is sane */ |
| if (pkt_len < cd.length+sizeof(cd)) { |
| if (parsed_len) { |
| if (is_datagram) { |
| /* Discard the datagram */ |
| *parsed_len = pkt_len; |
| } else { |
| /* Insufficient fragment */ |
| *parsed_len = 0; |
| } |
| } |
| status = PJ_ETOOSMALL; |
| goto on_return; |
| } else { |
| if (parsed_len) { |
| /* Apply padding too */ |
| *parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd); |
| } |
| } |
| |
| /* Lookup channel */ |
| ch = lookup_ch_by_chnum(sess, cd.ch_number); |
| if (!ch || !ch->bound) { |
| status = PJ_ENOTFOUND; |
| goto on_return; |
| } |
| |
| /* Notify application */ |
| if (sess->cb.on_rx_data) { |
| (*sess->cb.on_rx_data)(sess, ((pj_uint8_t*)pkt)+sizeof(cd), |
| cd.length, &ch->addr, |
| pj_sockaddr_get_len(&ch->addr)); |
| } |
| |
| status = PJ_SUCCESS; |
| } |
| |
| on_return: |
| pj_grp_lock_release(sess->grp_lock); |
| return status; |
| } |
| |
| |
| /* |
| * This is a callback from STUN session to send outgoing packet. |
| */ |
| static pj_status_t stun_on_send_msg(pj_stun_session *stun, |
| void *token, |
| const void *pkt, |
| pj_size_t pkt_size, |
| const pj_sockaddr_t *dst_addr, |
| unsigned addr_len) |
| { |
| pj_turn_session *sess; |
| |
| PJ_UNUSED_ARG(token); |
| |
| sess = (pj_turn_session*) pj_stun_session_get_user_data(stun); |
| return (*sess->cb.on_send_pkt)(sess, (const pj_uint8_t*)pkt, |
| (unsigned)pkt_size, |
| dst_addr, addr_len); |
| } |
| |
| |
| /* |
| * Handle failed ALLOCATE or REFRESH request. This may switch to alternate |
| * server if we have one. |
| */ |
| static void on_session_fail( pj_turn_session *sess, |
| enum pj_stun_method_e method, |
| pj_status_t status, |
| const pj_str_t *reason) |
| { |
| sess->last_status = status; |
| |
| do { |
| pj_str_t reason1; |
| char err_msg[PJ_ERR_MSG_SIZE]; |
| |
| if (reason == NULL) { |
| pj_strerror(status, err_msg, sizeof(err_msg)); |
| reason1 = pj_str(err_msg); |
| reason = &reason1; |
| } |
| |
| PJ_LOG(4,(sess->obj_name, "%s error: %.*s", |
| pj_stun_get_method_name(method), |
| (int)reason->slen, reason->ptr)); |
| |
| /* If this is ALLOCATE response and we don't have more server |
| * addresses to try, notify application and destroy the TURN |
| * session. |
| */ |
| if (method==PJ_STUN_ALLOCATE_METHOD && |
| sess->srv_addr == &sess->srv_addr_list[sess->srv_addr_cnt-1]) |
| { |
| |
| set_state(sess, PJ_TURN_STATE_DEALLOCATED); |
| sess_shutdown(sess, status); |
| return; |
| } |
| |
| /* Otherwise if this is not ALLOCATE response, notify application |
| * that session has been TERMINATED. |
| */ |
| if (method!=PJ_STUN_ALLOCATE_METHOD) { |
| set_state(sess, PJ_TURN_STATE_DEALLOCATED); |
| sess_shutdown(sess, status); |
| return; |
| } |
| |
| /* Try next server */ |
| ++sess->srv_addr; |
| reason = NULL; |
| |
| PJ_LOG(4,(sess->obj_name, "Trying next server")); |
| set_state(sess, PJ_TURN_STATE_RESOLVED); |
| |
| } while (0); |
| } |
| |
| |
| /* |
| * Handle successful response to ALLOCATE or REFRESH request. |
| */ |
| static void on_allocate_success(pj_turn_session *sess, |
| enum pj_stun_method_e method, |
| const pj_stun_msg *msg) |
| { |
| const pj_stun_lifetime_attr *lf_attr; |
| const pj_stun_xor_relayed_addr_attr *raddr_attr; |
| const pj_stun_sockaddr_attr *mapped_attr; |
| pj_str_t s; |
| pj_time_val timeout; |
| |
| /* Must have LIFETIME attribute */ |
| lf_attr = (const pj_stun_lifetime_attr*) |
| pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0); |
| if (lf_attr == NULL) { |
| on_session_fail(sess, method, PJNATH_EINSTUNMSG, |
| pj_cstr(&s, "Error: Missing LIFETIME attribute")); |
| return; |
| } |
| |
| /* If LIFETIME is zero, this is a deallocation */ |
| if (lf_attr->value == 0) { |
| set_state(sess, PJ_TURN_STATE_DEALLOCATED); |
| sess_shutdown(sess, PJ_SUCCESS); |
| return; |
| } |
| |
| /* Update lifetime and keep-alive interval */ |
| sess->lifetime = lf_attr->value; |
| pj_gettimeofday(&sess->expiry); |
| |
| if (sess->lifetime < PJ_TURN_KEEP_ALIVE_SEC) { |
| if (sess->lifetime <= 2) { |
| on_session_fail(sess, method, PJ_ETOOSMALL, |
| pj_cstr(&s, "Error: LIFETIME too small")); |
| return; |
| } |
| sess->ka_interval = sess->lifetime - 2; |
| sess->expiry.sec += (sess->ka_interval-1); |
| } else { |
| int timeout; |
| |
| sess->ka_interval = PJ_TURN_KEEP_ALIVE_SEC; |
| |
| timeout = sess->lifetime - PJ_TURN_REFRESH_SEC_BEFORE; |
| if (timeout < sess->ka_interval) |
| timeout = sess->ka_interval - 1; |
| |
| sess->expiry.sec += timeout; |
| } |
| |
| /* Check that relayed transport address contains correct |
| * address family. |
| */ |
| raddr_attr = (const pj_stun_xor_relayed_addr_attr*) |
| pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_RELAYED_ADDR, 0); |
| if (raddr_attr == NULL && method==PJ_STUN_ALLOCATE_METHOD) { |
| on_session_fail(sess, method, PJNATH_EINSTUNMSG, |
| pj_cstr(&s, "Error: Received ALLOCATE without " |
| "RELAY-ADDRESS attribute")); |
| return; |
| } |
| if (raddr_attr && raddr_attr->sockaddr.addr.sa_family != sess->af) { |
| on_session_fail(sess, method, PJNATH_EINSTUNMSG, |
| pj_cstr(&s, "Error: RELAY-ADDRESS with non IPv4" |
| " address family is not supported " |
| "for now")); |
| return; |
| } |
| if (raddr_attr && !pj_sockaddr_has_addr(&raddr_attr->sockaddr)) { |
| on_session_fail(sess, method, PJNATH_EINSTUNMSG, |
| pj_cstr(&s, "Error: Invalid IP address in " |
| "RELAY-ADDRESS attribute")); |
| return; |
| } |
| |
| /* Save relayed address */ |
| if (raddr_attr) { |
| /* If we already have relay address, check if the relay address |
| * in the response matches our relay address. |
| */ |
| if (pj_sockaddr_has_addr(&sess->relay_addr)) { |
| if (pj_sockaddr_cmp(&sess->relay_addr, &raddr_attr->sockaddr)) { |
| on_session_fail(sess, method, PJNATH_EINSTUNMSG, |
| pj_cstr(&s, "Error: different RELAY-ADDRESS is" |
| "returned by server")); |
| return; |
| } |
| } else { |
| /* Otherwise save the relayed address */ |
| pj_memcpy(&sess->relay_addr, &raddr_attr->sockaddr, |
| sizeof(pj_sockaddr)); |
| } |
| } |
| |
| /* Get mapped address */ |
| mapped_attr = (const pj_stun_sockaddr_attr*) |
| pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); |
| if (mapped_attr) { |
| pj_memcpy(&sess->mapped_addr, &mapped_attr->sockaddr, |
| sizeof(mapped_attr->sockaddr)); |
| } |
| |
| /* Success */ |
| |
| /* Cancel existing keep-alive timer, if any */ |
| pj_assert(sess->timer.id != TIMER_DESTROY); |
| if (sess->timer.id == TIMER_KEEP_ALIVE) { |
| pj_timer_heap_cancel_if_active(sess->timer_heap, &sess->timer, |
| TIMER_NONE); |
| } |
| |
| /* Start keep-alive timer once allocation succeeds */ |
| if (sess->state < PJ_TURN_STATE_DEALLOCATING) { |
| timeout.sec = sess->ka_interval; |
| timeout.msec = 0; |
| |
| pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, |
| &timeout, TIMER_KEEP_ALIVE, |
| sess->grp_lock); |
| |
| set_state(sess, PJ_TURN_STATE_READY); |
| } |
| } |
| |
| /* |
| * Notification from STUN session on request completion. |
| */ |
| static void stun_on_request_complete(pj_stun_session *stun, |
| pj_status_t status, |
| void *token, |
| pj_stun_tx_data *tdata, |
| const pj_stun_msg *response, |
| const pj_sockaddr_t *src_addr, |
| unsigned src_addr_len) |
| { |
| pj_turn_session *sess; |
| enum pj_stun_method_e method = (enum pj_stun_method_e) |
| PJ_STUN_GET_METHOD(tdata->msg->hdr.type); |
| |
| PJ_UNUSED_ARG(src_addr); |
| PJ_UNUSED_ARG(src_addr_len); |
| |
| sess = (pj_turn_session*)pj_stun_session_get_user_data(stun); |
| |
| if (method == PJ_STUN_ALLOCATE_METHOD) { |
| |
| /* Destroy if we have pending destroy request */ |
| if (sess->pending_destroy) { |
| if (status == PJ_SUCCESS) |
| sess->state = PJ_TURN_STATE_READY; |
| else |
| sess->state = PJ_TURN_STATE_DEALLOCATED; |
| sess_shutdown(sess, PJ_SUCCESS); |
| return; |
| } |
| |
| /* Handle ALLOCATE response */ |
| if (status==PJ_SUCCESS && |
| PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) |
| { |
| |
| /* Successful Allocate response */ |
| on_allocate_success(sess, method, response); |
| |
| } else { |
| /* Failed Allocate request */ |
| const pj_str_t *err_msg = NULL; |
| |
| if (status == PJ_SUCCESS) { |
| const pj_stun_errcode_attr *err_attr; |
| err_attr = (const pj_stun_errcode_attr*) |
| pj_stun_msg_find_attr(response, |
| PJ_STUN_ATTR_ERROR_CODE, 0); |
| if (err_attr) { |
| status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); |
| err_msg = &err_attr->reason; |
| } else { |
| status = PJNATH_EINSTUNMSG; |
| } |
| } |
| |
| on_session_fail(sess, method, status, err_msg); |
| } |
| |
| } else if (method == PJ_STUN_REFRESH_METHOD) { |
| /* Handle Refresh response */ |
| if (status==PJ_SUCCESS && |
| PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) |
| { |
| /* Success, schedule next refresh. */ |
| on_allocate_success(sess, method, response); |
| |
| } else { |
| /* Failed Refresh request */ |
| const pj_str_t *err_msg = NULL; |
| |
| pj_assert(status != PJ_SUCCESS); |
| |
| if (response) { |
| const pj_stun_errcode_attr *err_attr; |
| err_attr = (const pj_stun_errcode_attr*) |
| pj_stun_msg_find_attr(response, |
| PJ_STUN_ATTR_ERROR_CODE, 0); |
| if (err_attr) { |
| status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); |
| err_msg = &err_attr->reason; |
| } |
| } |
| |
| /* Notify and destroy */ |
| on_session_fail(sess, method, status, err_msg); |
| } |
| |
| } else if (method == PJ_STUN_CHANNEL_BIND_METHOD) { |
| /* Handle ChannelBind response */ |
| if (status==PJ_SUCCESS && |
| PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) |
| { |
| /* Successful ChannelBind response */ |
| struct ch_t *ch = (struct ch_t*)token; |
| |
| pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL); |
| ch->bound = PJ_TRUE; |
| |
| /* Update hash table */ |
| lookup_ch_by_addr(sess, &ch->addr, |
| pj_sockaddr_get_len(&ch->addr), |
| PJ_TRUE, PJ_TRUE); |
| |
| } else { |
| /* Failed ChannelBind response */ |
| pj_str_t reason = {"", 0}; |
| int err_code = 0; |
| char errbuf[PJ_ERR_MSG_SIZE]; |
| |
| pj_assert(status != PJ_SUCCESS); |
| |
| if (response) { |
| const pj_stun_errcode_attr *err_attr; |
| err_attr = (const pj_stun_errcode_attr*) |
| pj_stun_msg_find_attr(response, |
| PJ_STUN_ATTR_ERROR_CODE, 0); |
| if (err_attr) { |
| err_code = err_attr->err_code; |
| status = PJ_STATUS_FROM_STUN_CODE(err_attr->err_code); |
| reason = err_attr->reason; |
| } |
| } else { |
| err_code = status; |
| reason = pj_strerror(status, errbuf, sizeof(errbuf)); |
| } |
| |
| PJ_LOG(1,(sess->obj_name, "ChannelBind failed: %d/%.*s", |
| err_code, (int)reason.slen, reason.ptr)); |
| |
| if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) { |
| /* Allocation mismatch means allocation no longer exists */ |
| on_session_fail(sess, PJ_STUN_CHANNEL_BIND_METHOD, |
| status, &reason); |
| return; |
| } |
| } |
| |
| } else if (method == PJ_STUN_CREATE_PERM_METHOD) { |
| /* Handle CreatePermission response */ |
| if (status==PJ_SUCCESS && |
| PJ_STUN_IS_SUCCESS_RESPONSE(response->hdr.type)) |
| { |
| /* No special handling when the request is successful. */ |
| } else { |
| /* Iterate the permission table and invalidate all permissions |
| * that are related to this request. |
| */ |
| pj_hash_iterator_t it_buf, *it; |
| char ipstr[PJ_INET6_ADDRSTRLEN+10]; |
| int err_code; |
| char errbuf[PJ_ERR_MSG_SIZE]; |
| pj_str_t reason; |
| |
| pj_assert(status != PJ_SUCCESS); |
| |
| if (response) { |
| const pj_stun_errcode_attr *eattr; |
| |
| eattr = (const pj_stun_errcode_attr*) |
| pj_stun_msg_find_attr(response, |
| PJ_STUN_ATTR_ERROR_CODE, 0); |
| if (eattr) { |
| err_code = eattr->err_code; |
| reason = eattr->reason; |
| } else { |
| err_code = -1; |
| reason = pj_str("?"); |
| } |
| } else { |
| err_code = status; |
| reason = pj_strerror(status, errbuf, sizeof(errbuf)); |
| } |
| |
| it = pj_hash_first(sess->perm_table, &it_buf); |
| while (it) { |
| struct perm_t *perm = (struct perm_t*) |
| pj_hash_this(sess->perm_table, it); |
| it = pj_hash_next(sess->perm_table, it); |
| |
| if (perm->req_token == token) { |
| PJ_LOG(1,(sess->obj_name, |
| "CreatePermission failed for IP %s: %d/%.*s", |
| pj_sockaddr_print(&perm->addr, ipstr, |
| sizeof(ipstr), 2), |
| err_code, (int)reason.slen, reason.ptr)); |
| |
| invalidate_perm(sess, perm); |
| } |
| } |
| |
| if (err_code == PJ_STUN_SC_ALLOCATION_MISMATCH) { |
| /* Allocation mismatch means allocation no longer exists */ |
| on_session_fail(sess, PJ_STUN_CREATE_PERM_METHOD, |
| status, &reason); |
| return; |
| } |
| } |
| |
| } else { |
| PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s response", |
| pj_stun_get_method_name(response->hdr.type))); |
| } |
| } |
| |
| |
| /* |
| * Notification from STUN session on incoming STUN Indication |
| * message. |
| */ |
| static pj_status_t stun_on_rx_indication(pj_stun_session *stun, |
| const pj_uint8_t *pkt, |
| unsigned pkt_len, |
| const pj_stun_msg *msg, |
| void *token, |
| const pj_sockaddr_t *src_addr, |
| unsigned src_addr_len) |
| { |
| pj_turn_session *sess; |
| pj_stun_xor_peer_addr_attr *peer_attr; |
| pj_stun_icmp_attr *icmp; |
| pj_stun_data_attr *data_attr; |
| |
| PJ_UNUSED_ARG(token); |
| PJ_UNUSED_ARG(pkt); |
| PJ_UNUSED_ARG(pkt_len); |
| PJ_UNUSED_ARG(src_addr); |
| PJ_UNUSED_ARG(src_addr_len); |
| |
| sess = (pj_turn_session*)pj_stun_session_get_user_data(stun); |
| |
| /* Expecting Data Indication only */ |
| if (msg->hdr.type != PJ_STUN_DATA_INDICATION) { |
| PJ_LOG(4,(sess->obj_name, "Unexpected STUN %s indication", |
| pj_stun_get_method_name(msg->hdr.type))); |
| return PJ_EINVALIDOP; |
| } |
| |
| /* Check if there is ICMP attribute in the message */ |
| icmp = (pj_stun_icmp_attr*) |
| pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ICMP, 0); |
| if (icmp != NULL) { |
| /* This is a forwarded ICMP packet. Ignore it for now */ |
| return PJ_SUCCESS; |
| } |
| |
| /* Get XOR-PEER-ADDRESS attribute */ |
| peer_attr = (pj_stun_xor_peer_addr_attr*) |
| pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_XOR_PEER_ADDR, 0); |
| |
| /* Get DATA attribute */ |
| data_attr = (pj_stun_data_attr*) |
| pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_DATA, 0); |
| |
| /* Must have both XOR-PEER-ADDRESS and DATA attributes */ |
| if (!peer_attr || !data_attr) { |
| PJ_LOG(4,(sess->obj_name, |
| "Received Data indication with missing attributes")); |
| return PJ_EINVALIDOP; |
| } |
| |
| /* Notify application */ |
| if (sess->cb.on_rx_data) { |
| (*sess->cb.on_rx_data)(sess, data_attr->data, data_attr->length, |
| &peer_attr->sockaddr, |
| pj_sockaddr_get_len(&peer_attr->sockaddr)); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Notification on completion of DNS SRV resolution. |
| */ |
| static void dns_srv_resolver_cb(void *user_data, |
| pj_status_t status, |
| const pj_dns_srv_record *rec) |
| { |
| pj_turn_session *sess = (pj_turn_session*) user_data; |
| unsigned i, cnt, tot_cnt; |
| |
| /* Check failure */ |
| if (status != PJ_SUCCESS || sess->pending_destroy) { |
| set_state(sess, PJ_TURN_STATE_DESTROYING); |
| sess_shutdown(sess, status); |
| return; |
| } |
| |
| /* Calculate total number of server entries in the response */ |
| tot_cnt = 0; |
| for (i=0; i<rec->count; ++i) { |
| tot_cnt += rec->entry[i].server.addr_count; |
| } |
| |
| if (tot_cnt > PJ_TURN_MAX_DNS_SRV_CNT) |
| tot_cnt = PJ_TURN_MAX_DNS_SRV_CNT; |
| |
| /* Allocate server entries */ |
| sess->srv_addr_list = (pj_sockaddr*) |
| pj_pool_calloc(sess->pool, tot_cnt, |
| sizeof(pj_sockaddr)); |
| |
| /* Copy results to server entries */ |
| for (i=0, cnt=0; i<rec->count && cnt<PJ_TURN_MAX_DNS_SRV_CNT; ++i) { |
| unsigned j; |
| |
| for (j=0; j<rec->entry[i].server.addr_count && |
| cnt<PJ_TURN_MAX_DNS_SRV_CNT; ++j) |
| { |
| pj_sockaddr_in *addr = &sess->srv_addr_list[cnt].ipv4; |
| |
| addr->sin_family = sess->af; |
| addr->sin_port = pj_htons(rec->entry[i].port); |
| addr->sin_addr.s_addr = rec->entry[i].server.addr[j].s_addr; |
| |
| ++cnt; |
| } |
| } |
| sess->srv_addr_cnt = (pj_uint16_t)cnt; |
| |
| /* Set current server */ |
| sess->srv_addr = &sess->srv_addr_list[0]; |
| |
| /* Set state to PJ_TURN_STATE_RESOLVED */ |
| set_state(sess, PJ_TURN_STATE_RESOLVED); |
| |
| /* Run pending allocation */ |
| if (sess->pending_alloc) { |
| pj_turn_session_alloc(sess, NULL); |
| } |
| } |
| |
| |
| /* |
| * Lookup peer descriptor from its address. |
| */ |
| static struct ch_t *lookup_ch_by_addr(pj_turn_session *sess, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len, |
| pj_bool_t update, |
| pj_bool_t bind_channel) |
| { |
| pj_uint32_t hval = 0; |
| struct ch_t *ch; |
| |
| ch = (struct ch_t*) |
| pj_hash_get(sess->ch_table, addr, addr_len, &hval); |
| if (ch == NULL && update) { |
| ch = PJ_POOL_ZALLOC_T(sess->pool, struct ch_t); |
| ch->num = PJ_TURN_INVALID_CHANNEL; |
| pj_memcpy(&ch->addr, addr, addr_len); |
| |
| /* Register by peer address */ |
| pj_hash_set(sess->pool, sess->ch_table, &ch->addr, addr_len, |
| hval, ch); |
| } |
| |
| if (ch && update) { |
| pj_gettimeofday(&ch->expiry); |
| ch->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; |
| |
| if (bind_channel) { |
| pj_uint32_t hval = 0; |
| /* Register by channel number */ |
| pj_assert(ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound); |
| |
| if (pj_hash_get(sess->ch_table, &ch->num, |
| sizeof(ch->num), &hval)==0) { |
| pj_hash_set(sess->pool, sess->ch_table, &ch->num, |
| sizeof(ch->num), hval, ch); |
| } |
| } |
| } |
| |
| /* Also create/update permission for this destination. Ideally we |
| * should update this when we receive the successful response, |
| * but that would cause duplicate CreatePermission to be sent |
| * during refreshing. |
| */ |
| if (ch && update) { |
| lookup_perm(sess, &ch->addr, pj_sockaddr_get_len(&ch->addr), PJ_TRUE); |
| } |
| |
| return ch; |
| } |
| |
| |
| /* |
| * Lookup channel descriptor from its channel number. |
| */ |
| static struct ch_t *lookup_ch_by_chnum(pj_turn_session *sess, |
| pj_uint16_t chnum) |
| { |
| return (struct ch_t*) pj_hash_get(sess->ch_table, &chnum, |
| sizeof(chnum), NULL); |
| } |
| |
| |
| /* |
| * Lookup permission and optionally create if it doesn't exist. |
| */ |
| static struct perm_t *lookup_perm(pj_turn_session *sess, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len, |
| pj_bool_t update) |
| { |
| pj_uint32_t hval = 0; |
| pj_sockaddr perm_addr; |
| struct perm_t *perm; |
| |
| /* make sure port number if zero */ |
| if (pj_sockaddr_get_port(addr) != 0) { |
| pj_memcpy(&perm_addr, addr, addr_len); |
| pj_sockaddr_set_port(&perm_addr, 0); |
| addr = &perm_addr; |
| } |
| |
| /* lookup and create if it doesn't exist and wanted */ |
| perm = (struct perm_t*) |
| pj_hash_get(sess->perm_table, addr, addr_len, &hval); |
| if (perm == NULL && update) { |
| perm = PJ_POOL_ZALLOC_T(sess->pool, struct perm_t); |
| pj_memcpy(&perm->addr, addr, addr_len); |
| perm->hval = hval; |
| |
| pj_hash_set(sess->pool, sess->perm_table, &perm->addr, addr_len, |
| perm->hval, perm); |
| } |
| |
| if (perm && update) { |
| pj_gettimeofday(&perm->expiry); |
| perm->expiry.sec += PJ_TURN_PERM_TIMEOUT - sess->ka_interval - 1; |
| |
| } |
| |
| return perm; |
| } |
| |
| /* |
| * Delete permission |
| */ |
| static void invalidate_perm(pj_turn_session *sess, |
| struct perm_t *perm) |
| { |
| pj_hash_set(NULL, sess->perm_table, &perm->addr, |
| pj_sockaddr_get_len(&perm->addr), perm->hval, NULL); |
| } |
| |
| /* |
| * Scan permission's hash table to refresh the permission. |
| */ |
| static unsigned refresh_permissions(pj_turn_session *sess, |
| const pj_time_val *now) |
| { |
| pj_stun_tx_data *tdata = NULL; |
| unsigned count = 0; |
| void *req_token = NULL; |
| pj_hash_iterator_t *it, itbuf; |
| pj_status_t status; |
| |
| it = pj_hash_first(sess->perm_table, &itbuf); |
| while (it) { |
| struct perm_t *perm = (struct perm_t*) |
| pj_hash_this(sess->perm_table, it); |
| |
| it = pj_hash_next(sess->perm_table, it); |
| |
| if (perm->expiry.sec-1 <= now->sec) { |
| if (perm->renew) { |
| /* Renew this permission */ |
| if (tdata == NULL) { |
| /* Create a bare CreatePermission request */ |
| status = pj_stun_session_create_req( |
| sess->stun, |
| PJ_STUN_CREATE_PERM_REQUEST, |
| PJ_STUN_MAGIC, NULL, &tdata); |
| if (status != PJ_SUCCESS) { |
| PJ_LOG(1,(sess->obj_name, |
| "Error creating CreatePermission request: %d", |
| status)); |
| return 0; |
| } |
| |
| /* Create request token to map the request to the perm |
| * structures which the request belongs. |
| */ |
| req_token = (void*)(pj_ssize_t)pj_rand(); |
| } |
| |
| status = pj_stun_msg_add_sockaddr_attr( |
| tdata->pool, |
| tdata->msg, |
| PJ_STUN_ATTR_XOR_PEER_ADDR, |
| PJ_TRUE, |
| &perm->addr, |
| sizeof(perm->addr)); |
| if (status != PJ_SUCCESS) { |
| pj_stun_msg_destroy_tdata(sess->stun, tdata); |
| return 0; |
| } |
| |
| perm->expiry = *now; |
| perm->expiry.sec += PJ_TURN_PERM_TIMEOUT-sess->ka_interval-1; |
| perm->req_token = req_token; |
| ++count; |
| |
| } else { |
| /* This permission has expired and app doesn't want |
| * us to renew, so delete it from the hash table. |
| */ |
| invalidate_perm(sess, perm); |
| } |
| } |
| } |
| |
| if (tdata) { |
| status = pj_stun_session_send_msg(sess->stun, req_token, PJ_FALSE, |
| (sess->conn_type==PJ_TURN_TP_UDP), |
| sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr), |
| tdata); |
| if (status != PJ_SUCCESS) { |
| PJ_LOG(1,(sess->obj_name, |
| "Error sending CreatePermission request: %d", |
| status)); |
| count = 0; |
| } |
| |
| } |
| |
| return count; |
| } |
| |
| /* |
| * Timer event. |
| */ |
| static void on_timer_event(pj_timer_heap_t *th, pj_timer_entry *e) |
| { |
| pj_turn_session *sess = (pj_turn_session*)e->user_data; |
| enum timer_id_t eid; |
| |
| PJ_UNUSED_ARG(th); |
| |
| pj_grp_lock_acquire(sess->grp_lock); |
| |
| eid = (enum timer_id_t) e->id; |
| e->id = TIMER_NONE; |
| |
| if (eid == TIMER_KEEP_ALIVE) { |
| pj_time_val now; |
| pj_hash_iterator_t itbuf, *it; |
| pj_bool_t resched = PJ_TRUE; |
| pj_bool_t pkt_sent = PJ_FALSE; |
| |
| if (sess->state >= PJ_TURN_STATE_DEALLOCATING) { |
| /* Ignore if we're deallocating */ |
| goto on_return; |
| } |
| |
| pj_gettimeofday(&now); |
| |
| /* Refresh allocation if it's time to do so */ |
| if (PJ_TIME_VAL_LTE(sess->expiry, now)) { |
| int lifetime = sess->alloc_param.lifetime; |
| |
| if (lifetime == 0) |
| lifetime = -1; |
| |
| send_refresh(sess, lifetime); |
| resched = PJ_FALSE; |
| pkt_sent = PJ_TRUE; |
| } |
| |
| /* Scan hash table to refresh bound channels */ |
| it = pj_hash_first(sess->ch_table, &itbuf); |
| while (it) { |
| struct ch_t *ch = (struct ch_t*) |
| pj_hash_this(sess->ch_table, it); |
| if (ch->bound && PJ_TIME_VAL_LTE(ch->expiry, now)) { |
| |
| /* Send ChannelBind to refresh channel binding and |
| * permission. |
| */ |
| pj_turn_session_bind_channel(sess, &ch->addr, |
| pj_sockaddr_get_len(&ch->addr)); |
| pkt_sent = PJ_TRUE; |
| } |
| |
| it = pj_hash_next(sess->ch_table, it); |
| } |
| |
| /* Scan permission table to refresh permissions */ |
| if (refresh_permissions(sess, &now)) |
| pkt_sent = PJ_TRUE; |
| |
| /* If no packet is sent, send a blank Send indication to |
| * refresh local NAT. |
| */ |
| if (!pkt_sent && sess->alloc_param.ka_interval > 0) { |
| pj_stun_tx_data *tdata; |
| pj_status_t rc; |
| |
| /* Create blank SEND-INDICATION */ |
| rc = pj_stun_session_create_ind(sess->stun, |
| PJ_STUN_SEND_INDICATION, &tdata); |
| if (rc == PJ_SUCCESS) { |
| /* Add DATA attribute with zero length */ |
| pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg, |
| PJ_STUN_ATTR_DATA, NULL, 0); |
| |
| /* Send the indication */ |
| pj_stun_session_send_msg(sess->stun, NULL, PJ_FALSE, |
| PJ_FALSE, sess->srv_addr, |
| pj_sockaddr_get_len(sess->srv_addr), |
| tdata); |
| } |
| } |
| |
| /* Reshcedule timer */ |
| if (resched) { |
| pj_time_val delay; |
| |
| delay.sec = sess->ka_interval; |
| delay.msec = 0; |
| |
| pj_timer_heap_schedule_w_grp_lock(sess->timer_heap, &sess->timer, |
| &delay, TIMER_KEEP_ALIVE, |
| sess->grp_lock); |
| } |
| |
| } else if (eid == TIMER_DESTROY) { |
| /* Time to destroy */ |
| do_destroy(sess); |
| } else { |
| pj_assert(!"Unknown timer event"); |
| } |
| |
| on_return: |
| pj_grp_lock_release(sess->grp_lock); |
| } |
| |