blob: c51527db51157b538995c454cefee5848d3205a9 [file] [log] [blame]
/* $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/stun_sock.h>
#include <pjnath/errno.h>
#include <pjnath/stun_transaction.h>
#include <pjnath/stun_session.h>
#include <pjlib-util/srv_resolver.h>
#include <pj/activesock.h>
#include <pj/addr_resolv.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ip_helper.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#if 1
# define TRACE_(x) PJ_LOG(5,x)
#else
# define TRACE_(x)
#endif
enum { MAX_BIND_RETRY = 100 };
struct pj_stun_sock
{
char *obj_name; /* Log identification */
pj_pool_t *pool; /* Pool */
void *user_data; /* Application user data */
pj_bool_t is_destroying; /* Destroy already called */
int af; /* Address family */
pj_stun_config stun_cfg; /* STUN config (ioqueue etc)*/
pj_stun_sock_cb cb; /* Application callbacks */
int ka_interval; /* Keep alive interval */
pj_timer_entry ka_timer; /* Keep alive timer. */
pj_sockaddr srv_addr; /* Resolved server addr */
pj_sockaddr mapped_addr; /* Our public address */
pj_dns_srv_async_query *q; /* Pending DNS query */
pj_sock_t sock_fd; /* Socket descriptor */
pj_activesock_t *active_sock; /* Active socket object */
pj_ioqueue_op_key_t send_key; /* Default send key for app */
pj_ioqueue_op_key_t int_send_key; /* Send key for internal */
pj_uint16_t tsx_id[6]; /* .. to match STUN msg */
pj_stun_session *stun_sess; /* STUN session */
pj_grp_lock_t *grp_lock; /* Session group lock */
};
/*
* Prototypes for static functions
*/
/* Destructor for group lock */
static void stun_sock_destructor(void *obj);
/* This callback is called by the STUN session to send packet */
static pj_status_t sess_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);
/* This callback is called by the STUN session when outgoing transaction
* is complete
*/
static void sess_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);
/* DNS resolver callback */
static void dns_srv_resolver_cb(void *user_data,
pj_status_t status,
const pj_dns_srv_record *rec);
/* Start sending STUN Binding request */
static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock);
/* Callback from active socket when incoming packet is received */
static pj_bool_t on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status);
/* Callback from active socket about send status */
static pj_bool_t on_data_sent(pj_activesock_t *asock,
pj_ioqueue_op_key_t *send_key,
pj_ssize_t sent);
/* Schedule keep-alive timer */
static void start_ka_timer(pj_stun_sock *stun_sock);
/* Keep-alive timer callback */
static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te);
#define INTERNAL_MSG_TOKEN (void*)(pj_ssize_t)1
/*
* Retrieve the name representing the specified operation.
*/
PJ_DEF(const char*) pj_stun_sock_op_name(pj_stun_sock_op op)
{
const char *names[] = {
"?",
"DNS resolution",
"STUN Binding request",
"Keep-alive",
"Mapped addr. changed"
};
return op < PJ_ARRAY_SIZE(names) ? names[op] : "???";
};
/*
* Initialize the STUN transport setting with its default values.
*/
PJ_DEF(void) pj_stun_sock_cfg_default(pj_stun_sock_cfg *cfg)
{
pj_bzero(cfg, sizeof(*cfg));
cfg->max_pkt_size = PJ_STUN_SOCK_PKT_LEN;
cfg->async_cnt = 1;
cfg->ka_interval = PJ_STUN_KEEP_ALIVE_SEC;
cfg->qos_type = PJ_QOS_TYPE_BEST_EFFORT;
cfg->qos_ignore_error = PJ_TRUE;
}
/* Check that configuration setting is valid */
static pj_bool_t pj_stun_sock_cfg_is_valid(const pj_stun_sock_cfg *cfg)
{
return cfg->max_pkt_size > 1 && cfg->async_cnt >= 1;
}
/*
* Create the STUN transport using the specified configuration.
*/
PJ_DEF(pj_status_t) pj_stun_sock_create( pj_stun_config *stun_cfg,
const char *name,
int af,
const pj_stun_sock_cb *cb,
const pj_stun_sock_cfg *cfg,
void *user_data,
pj_stun_sock **p_stun_sock)
{
pj_pool_t *pool;
pj_stun_sock *stun_sock;
pj_stun_sock_cfg default_cfg;
pj_sockaddr bound_addr;
unsigned i;
pj_uint16_t max_bind_retry;
pj_status_t status;
PJ_ASSERT_RETURN(stun_cfg && cb && p_stun_sock, PJ_EINVAL);
PJ_ASSERT_RETURN(af==pj_AF_INET()||af==pj_AF_INET6(), PJ_EAFNOTSUP);
PJ_ASSERT_RETURN(!cfg || pj_stun_sock_cfg_is_valid(cfg), PJ_EINVAL);
PJ_ASSERT_RETURN(cb->on_status, PJ_EINVAL);
status = pj_stun_config_check_valid(stun_cfg);
if (status != PJ_SUCCESS)
return status;
if (name == NULL)
name = "stuntp%p";
if (cfg == NULL) {
pj_stun_sock_cfg_default(&default_cfg);
cfg = &default_cfg;
}
/* Create structure */
pool = pj_pool_create(stun_cfg->pf, name, 256, 512, NULL);
stun_sock = PJ_POOL_ZALLOC_T(pool, pj_stun_sock);
stun_sock->pool = pool;
stun_sock->obj_name = pool->obj_name;
stun_sock->user_data = user_data;
stun_sock->af = af;
stun_sock->sock_fd = PJ_INVALID_SOCKET;
pj_memcpy(&stun_sock->stun_cfg, stun_cfg, sizeof(*stun_cfg));
pj_memcpy(&stun_sock->cb, cb, sizeof(*cb));
stun_sock->ka_interval = cfg->ka_interval;
if (stun_sock->ka_interval == 0)
stun_sock->ka_interval = PJ_STUN_KEEP_ALIVE_SEC;
if (cfg->grp_lock) {
stun_sock->grp_lock = cfg->grp_lock;
} else {
status = pj_grp_lock_create(pool, NULL, &stun_sock->grp_lock);
if (status != PJ_SUCCESS) {
pj_pool_release(pool);
return status;
}
}
pj_grp_lock_add_ref(stun_sock->grp_lock);
pj_grp_lock_add_handler(stun_sock->grp_lock, pool, stun_sock,
&stun_sock_destructor);
/* Create socket and bind socket */
status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &stun_sock->sock_fd);
if (status != PJ_SUCCESS)
goto on_error;
/* Apply QoS, if specified */
status = pj_sock_apply_qos2(stun_sock->sock_fd, cfg->qos_type,
&cfg->qos_params, 2, stun_sock->obj_name,
NULL);
if (status != PJ_SUCCESS && !cfg->qos_ignore_error)
goto on_error;
/* Apply socket buffer size */
if (cfg->so_rcvbuf_size > 0) {
unsigned sobuf_size = cfg->so_rcvbuf_size;
status = pj_sock_setsockopt_sobuf(stun_sock->sock_fd, pj_SO_RCVBUF(),
PJ_TRUE, &sobuf_size);
if (status != PJ_SUCCESS) {
pj_perror(3, stun_sock->obj_name, status,
"Failed setting SO_RCVBUF");
} else {
if (sobuf_size < cfg->so_rcvbuf_size) {
PJ_LOG(4, (stun_sock->obj_name,
"Warning! Cannot set SO_RCVBUF as configured, "
"now=%d, configured=%d",
sobuf_size, cfg->so_rcvbuf_size));
} else {
PJ_LOG(5, (stun_sock->obj_name, "SO_RCVBUF set to %d",
sobuf_size));
}
}
}
if (cfg->so_sndbuf_size > 0) {
unsigned sobuf_size = cfg->so_sndbuf_size;
status = pj_sock_setsockopt_sobuf(stun_sock->sock_fd, pj_SO_SNDBUF(),
PJ_TRUE, &sobuf_size);
if (status != PJ_SUCCESS) {
pj_perror(3, stun_sock->obj_name, status,
"Failed setting SO_SNDBUF");
} else {
if (sobuf_size < cfg->so_sndbuf_size) {
PJ_LOG(4, (stun_sock->obj_name,
"Warning! Cannot set SO_SNDBUF as configured, "
"now=%d, configured=%d",
sobuf_size, cfg->so_sndbuf_size));
} else {
PJ_LOG(5, (stun_sock->obj_name, "SO_SNDBUF set to %d",
sobuf_size));
}
}
}
/* Bind socket */
max_bind_retry = MAX_BIND_RETRY;
if (cfg->port_range && cfg->port_range < max_bind_retry)
max_bind_retry = cfg->port_range;
pj_sockaddr_init(af, &bound_addr, NULL, 0);
if (cfg->bound_addr.addr.sa_family == pj_AF_INET() ||
cfg->bound_addr.addr.sa_family == pj_AF_INET6())
{
pj_sockaddr_cp(&bound_addr, &cfg->bound_addr);
}
status = pj_sock_bind_random(stun_sock->sock_fd, &bound_addr,
cfg->port_range, max_bind_retry);
if (status != PJ_SUCCESS)
goto on_error;
/* Create more useful information string about this transport */
#if 0
{
pj_sockaddr bound_addr;
int addr_len = sizeof(bound_addr);
status = pj_sock_getsockname(stun_sock->sock_fd, &bound_addr,
&addr_len);
if (status != PJ_SUCCESS)
goto on_error;
stun_sock->info = pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+10);
pj_sockaddr_print(&bound_addr, stun_sock->info,
PJ_INET6_ADDRSTRLEN, 3);
}
#endif
/* Init active socket configuration */
{
pj_activesock_cfg activesock_cfg;
pj_activesock_cb activesock_cb;
pj_activesock_cfg_default(&activesock_cfg);
activesock_cfg.grp_lock = stun_sock->grp_lock;
activesock_cfg.async_cnt = cfg->async_cnt;
activesock_cfg.concurrency = 0;
/* Create the active socket */
pj_bzero(&activesock_cb, sizeof(activesock_cb));
activesock_cb.on_data_recvfrom = &on_data_recvfrom;
activesock_cb.on_data_sent = &on_data_sent;
status = pj_activesock_create(pool, stun_sock->sock_fd,
pj_SOCK_DGRAM(),
&activesock_cfg, stun_cfg->ioqueue,
&activesock_cb, stun_sock,
&stun_sock->active_sock);
if (status != PJ_SUCCESS)
goto on_error;
/* Start asynchronous read operations */
status = pj_activesock_start_recvfrom(stun_sock->active_sock, pool,
cfg->max_pkt_size, 0);
if (status != PJ_SUCCESS)
goto on_error;
/* Init send keys */
pj_ioqueue_op_key_init(&stun_sock->send_key,
sizeof(stun_sock->send_key));
pj_ioqueue_op_key_init(&stun_sock->int_send_key,
sizeof(stun_sock->int_send_key));
}
/* Create STUN session */
{
pj_stun_session_cb sess_cb;
pj_bzero(&sess_cb, sizeof(sess_cb));
sess_cb.on_request_complete = &sess_on_request_complete;
sess_cb.on_send_msg = &sess_on_send_msg;
status = pj_stun_session_create(&stun_sock->stun_cfg,
stun_sock->obj_name,
&sess_cb, PJ_FALSE,
stun_sock->grp_lock,
&stun_sock->stun_sess);
if (status != PJ_SUCCESS)
goto on_error;
}
/* Associate us with the STUN session */
pj_stun_session_set_user_data(stun_sock->stun_sess, stun_sock);
/* Initialize random numbers to be used as STUN transaction ID for
* outgoing Binding request. We use the 80bit number to distinguish
* STUN messages we sent with STUN messages that the application sends.
* The last 16bit value in the array is a counter.
*/
for (i=0; i<PJ_ARRAY_SIZE(stun_sock->tsx_id); ++i) {
stun_sock->tsx_id[i] = (pj_uint16_t) pj_rand();
}
stun_sock->tsx_id[5] = 0;
/* Init timer entry */
stun_sock->ka_timer.cb = &ka_timer_cb;
stun_sock->ka_timer.user_data = stun_sock;
/* Done */
*p_stun_sock = stun_sock;
return PJ_SUCCESS;
on_error:
pj_stun_sock_destroy(stun_sock);
return status;
}
/* Start socket. */
PJ_DEF(pj_status_t) pj_stun_sock_start( pj_stun_sock *stun_sock,
const pj_str_t *domain,
pj_uint16_t default_port,
pj_dns_resolver *resolver)
{
pj_status_t status;
PJ_ASSERT_RETURN(stun_sock && domain && default_port, PJ_EINVAL);
pj_grp_lock_acquire(stun_sock->grp_lock);
/* Check whether the domain contains IP address */
stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)stun_sock->af;
status = pj_inet_pton(stun_sock->af, domain,
pj_sockaddr_get_addr(&stun_sock->srv_addr));
if (status != PJ_SUCCESS) {
stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)0;
}
/* If resolver is set, try to resolve with DNS SRV first. It
* will fallback to DNS A/AAAA when no SRV record is found.
*/
if (status != PJ_SUCCESS && resolver) {
const pj_str_t res_name = pj_str("_stun._udp.");
unsigned opt;
pj_assert(stun_sock->q == NULL);
opt = PJ_DNS_SRV_FALLBACK_A;
if (stun_sock->af == pj_AF_INET6()) {
opt |= (PJ_DNS_SRV_RESOLVE_AAAA | PJ_DNS_SRV_FALLBACK_AAAA);
}
status = pj_dns_srv_resolve(domain, &res_name, default_port,
stun_sock->pool, resolver, opt,
stun_sock, &dns_srv_resolver_cb,
&stun_sock->q);
/* Processing will resume when the DNS SRV callback is called */
} else {
if (status != PJ_SUCCESS) {
pj_addrinfo ai;
unsigned cnt = 1;
status = pj_getaddrinfo(stun_sock->af, domain, &cnt, &ai);
if (status != PJ_SUCCESS)
return status;
pj_sockaddr_cp(&stun_sock->srv_addr, &ai.ai_addr);
}
pj_sockaddr_set_port(&stun_sock->srv_addr, (pj_uint16_t)default_port);
/* Start sending Binding request */
status = get_mapped_addr(stun_sock);
}
pj_grp_lock_release(stun_sock->grp_lock);
return status;
}
/* Destructor */
static void stun_sock_destructor(void *obj)
{
pj_stun_sock *stun_sock = (pj_stun_sock*)obj;
if (stun_sock->q) {
pj_dns_srv_cancel_query(stun_sock->q, PJ_FALSE);
stun_sock->q = NULL;
}
/*
if (stun_sock->stun_sess) {
pj_stun_session_destroy(stun_sock->stun_sess);
stun_sock->stun_sess = NULL;
}
*/
if (stun_sock->pool) {
pj_pool_t *pool = stun_sock->pool;
stun_sock->pool = NULL;
pj_pool_release(pool);
}
TRACE_(("", "STUN sock %p destroyed", stun_sock));
}
/* Destroy */
PJ_DEF(pj_status_t) pj_stun_sock_destroy(pj_stun_sock *stun_sock)
{
TRACE_((stun_sock->obj_name, "STUN sock %p request, ref_cnt=%d",
stun_sock, pj_grp_lock_get_ref(stun_sock->grp_lock)));
pj_grp_lock_acquire(stun_sock->grp_lock);
if (stun_sock->is_destroying) {
/* Destroy already called */
pj_grp_lock_release(stun_sock->grp_lock);
return PJ_EINVALIDOP;
}
stun_sock->is_destroying = PJ_TRUE;
pj_timer_heap_cancel_if_active(stun_sock->stun_cfg.timer_heap,
&stun_sock->ka_timer, 0);
if (stun_sock->active_sock != NULL) {
stun_sock->sock_fd = PJ_INVALID_SOCKET;
pj_activesock_close(stun_sock->active_sock);
} else if (stun_sock->sock_fd != PJ_INVALID_SOCKET) {
pj_sock_close(stun_sock->sock_fd);
stun_sock->sock_fd = PJ_INVALID_SOCKET;
}
if (stun_sock->stun_sess) {
pj_stun_session_destroy(stun_sock->stun_sess);
}
pj_grp_lock_dec_ref(stun_sock->grp_lock);
pj_grp_lock_release(stun_sock->grp_lock);
return PJ_SUCCESS;
}
/* Associate user data */
PJ_DEF(pj_status_t) pj_stun_sock_set_user_data( pj_stun_sock *stun_sock,
void *user_data)
{
PJ_ASSERT_RETURN(stun_sock, PJ_EINVAL);
stun_sock->user_data = user_data;
return PJ_SUCCESS;
}
/* Get user data */
PJ_DEF(void*) pj_stun_sock_get_user_data(pj_stun_sock *stun_sock)
{
PJ_ASSERT_RETURN(stun_sock, NULL);
return stun_sock->user_data;
}
/* Get group lock */
PJ_DECL(pj_grp_lock_t *) pj_stun_sock_get_grp_lock(pj_stun_sock *stun_sock)
{
PJ_ASSERT_RETURN(stun_sock, NULL);
return stun_sock->grp_lock;
}
/* Notify application that session has failed */
static pj_bool_t sess_fail(pj_stun_sock *stun_sock,
pj_stun_sock_op op,
pj_status_t status)
{
pj_bool_t ret;
PJ_PERROR(4,(stun_sock->obj_name, status,
"Session failed because %s failed",
pj_stun_sock_op_name(op)));
ret = (*stun_sock->cb.on_status)(stun_sock, op, status);
return ret;
}
/* DNS resolver callback */
static void dns_srv_resolver_cb(void *user_data,
pj_status_t status,
const pj_dns_srv_record *rec)
{
pj_stun_sock *stun_sock = (pj_stun_sock*) user_data;
pj_grp_lock_acquire(stun_sock->grp_lock);
/* Clear query */
stun_sock->q = NULL;
/* Handle error */
if (status != PJ_SUCCESS) {
sess_fail(stun_sock, PJ_STUN_SOCK_DNS_OP, status);
pj_grp_lock_release(stun_sock->grp_lock);
return;
}
pj_assert(rec->count);
pj_assert(rec->entry[0].server.addr_count);
PJ_TODO(SUPPORT_IPV6_IN_RESOLVER);
pj_assert(stun_sock->af == pj_AF_INET());
/* Set the address */
pj_sockaddr_in_init(&stun_sock->srv_addr.ipv4, NULL,
rec->entry[0].port);
stun_sock->srv_addr.ipv4.sin_addr = rec->entry[0].server.addr[0];
/* Start sending Binding request */
get_mapped_addr(stun_sock);
pj_grp_lock_release(stun_sock->grp_lock);
}
/* Start sending STUN Binding request */
static pj_status_t get_mapped_addr(pj_stun_sock *stun_sock)
{
pj_stun_tx_data *tdata;
pj_status_t status;
/* Increment request counter and create STUN Binding request */
++stun_sock->tsx_id[5];
status = pj_stun_session_create_req(stun_sock->stun_sess,
PJ_STUN_BINDING_REQUEST,
PJ_STUN_MAGIC,
(const pj_uint8_t*)stun_sock->tsx_id,
&tdata);
if (status != PJ_SUCCESS)
goto on_error;
/* Send request */
status=pj_stun_session_send_msg(stun_sock->stun_sess, INTERNAL_MSG_TOKEN,
PJ_FALSE, PJ_TRUE, &stun_sock->srv_addr,
pj_sockaddr_get_len(&stun_sock->srv_addr),
tdata);
if (status != PJ_SUCCESS && status != PJ_EPENDING)
goto on_error;
return PJ_SUCCESS;
on_error:
sess_fail(stun_sock, PJ_STUN_SOCK_BINDING_OP, status);
return status;
}
/* Get info */
PJ_DEF(pj_status_t) pj_stun_sock_get_info( pj_stun_sock *stun_sock,
pj_stun_sock_info *info)
{
int addr_len;
pj_status_t status;
PJ_ASSERT_RETURN(stun_sock && info, PJ_EINVAL);
pj_grp_lock_acquire(stun_sock->grp_lock);
/* Copy STUN server address and mapped address */
pj_memcpy(&info->srv_addr, &stun_sock->srv_addr,
sizeof(pj_sockaddr));
pj_memcpy(&info->mapped_addr, &stun_sock->mapped_addr,
sizeof(pj_sockaddr));
/* Retrieve bound address */
addr_len = sizeof(info->bound_addr);
status = pj_sock_getsockname(stun_sock->sock_fd, &info->bound_addr,
&addr_len);
if (status != PJ_SUCCESS) {
pj_grp_lock_release(stun_sock->grp_lock);
return status;
}
/* If socket is bound to a specific interface, then only put that
* interface in the alias list. Otherwise query all the interfaces
* in the host.
*/
if (pj_sockaddr_has_addr(&info->bound_addr)) {
info->alias_cnt = 1;
pj_sockaddr_cp(&info->aliases[0], &info->bound_addr);
} else {
pj_sockaddr def_addr;
pj_uint16_t port = pj_sockaddr_get_port(&info->bound_addr);
unsigned i;
/* Get the default address */
status = pj_gethostip(stun_sock->af, &def_addr);
if (status != PJ_SUCCESS) {
pj_grp_lock_release(stun_sock->grp_lock);
return status;
}
pj_sockaddr_set_port(&def_addr, port);
/* Enum all IP interfaces in the host */
info->alias_cnt = PJ_ARRAY_SIZE(info->aliases);
status = pj_enum_ip_interface(stun_sock->af, &info->alias_cnt,
info->aliases);
if (status != PJ_SUCCESS) {
pj_grp_lock_release(stun_sock->grp_lock);
return status;
}
/* Set the port number for each address.
*/
for (i=0; i<info->alias_cnt; ++i) {
pj_sockaddr_set_port(&info->aliases[i], port);
}
/* Put the default IP in the first slot */
for (i=0; i<info->alias_cnt; ++i) {
if (pj_sockaddr_cmp(&info->aliases[i], &def_addr)==0) {
if (i!=0) {
pj_sockaddr_cp(&info->aliases[i], &info->aliases[0]);
pj_sockaddr_cp(&info->aliases[0], &def_addr);
}
break;
}
}
}
pj_grp_lock_release(stun_sock->grp_lock);
return PJ_SUCCESS;
}
/* Send application data */
PJ_DEF(pj_status_t) pj_stun_sock_sendto( pj_stun_sock *stun_sock,
pj_ioqueue_op_key_t *send_key,
const void *pkt,
unsigned pkt_len,
unsigned flag,
const pj_sockaddr_t *dst_addr,
unsigned addr_len)
{
pj_ssize_t size;
pj_status_t status;
PJ_ASSERT_RETURN(stun_sock && pkt && dst_addr && addr_len, PJ_EINVAL);
pj_grp_lock_acquire(stun_sock->grp_lock);
if (!stun_sock->active_sock) {
/* We have been shutdown, but this callback may still get called
* by retransmit timer.
*/
pj_grp_lock_release(stun_sock->grp_lock);
return PJ_EINVALIDOP;
}
if (send_key==NULL)
send_key = &stun_sock->send_key;
size = pkt_len;
status = pj_activesock_sendto(stun_sock->active_sock, send_key,
pkt, &size, flag, dst_addr, addr_len);
pj_grp_lock_release(stun_sock->grp_lock);
return status;
}
/* This callback is called by the STUN session to send packet */
static pj_status_t sess_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)
{
pj_stun_sock *stun_sock;
pj_ssize_t size;
stun_sock = (pj_stun_sock *) pj_stun_session_get_user_data(sess);
if (!stun_sock || !stun_sock->active_sock) {
/* We have been shutdown, but this callback may still get called
* by retransmit timer.
*/
return PJ_EINVALIDOP;
}
pj_assert(token==INTERNAL_MSG_TOKEN);
PJ_UNUSED_ARG(token);
size = pkt_size;
return pj_activesock_sendto(stun_sock->active_sock,
&stun_sock->int_send_key,
pkt, &size, 0, dst_addr, addr_len);
}
/* This callback is called by the STUN session when outgoing transaction
* is complete
*/
static void sess_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)
{
pj_stun_sock *stun_sock;
const pj_stun_sockaddr_attr *mapped_attr;
pj_stun_sock_op op;
pj_bool_t mapped_changed;
pj_bool_t resched = PJ_TRUE;
stun_sock = (pj_stun_sock *) pj_stun_session_get_user_data(sess);
if (!stun_sock)
return;
PJ_UNUSED_ARG(tdata);
PJ_UNUSED_ARG(token);
PJ_UNUSED_ARG(src_addr);
PJ_UNUSED_ARG(src_addr_len);
/* Check if this is a keep-alive or the first Binding request */
if (pj_sockaddr_has_addr(&stun_sock->mapped_addr))
op = PJ_STUN_SOCK_KEEP_ALIVE_OP;
else
op = PJ_STUN_SOCK_BINDING_OP;
/* Handle failure */
if (status != PJ_SUCCESS) {
resched = sess_fail(stun_sock, op, status);
goto on_return;
}
/* Get XOR-MAPPED-ADDRESS, or MAPPED-ADDRESS when XOR-MAPPED-ADDRESS
* doesn't exist.
*/
mapped_attr = (const pj_stun_sockaddr_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR,
0);
if (mapped_attr==NULL) {
mapped_attr = (const pj_stun_sockaddr_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR,
0);
}
if (mapped_attr == NULL) {
resched = sess_fail(stun_sock, op, PJNATH_ESTUNNOMAPPEDADDR);
goto on_return;
}
/* Determine if mapped address has changed, and save the new mapped
* address and call callback if so
*/
mapped_changed = !pj_sockaddr_has_addr(&stun_sock->mapped_addr) ||
pj_sockaddr_cmp(&stun_sock->mapped_addr,
&mapped_attr->sockaddr) != 0;
if (mapped_changed) {
/* Print mapped adress */
{
char addrinfo[PJ_INET6_ADDRSTRLEN+10];
PJ_LOG(4,(stun_sock->obj_name,
"STUN mapped address found/changed: %s",
pj_sockaddr_print(&mapped_attr->sockaddr,
addrinfo, sizeof(addrinfo), 3)));
}
pj_sockaddr_cp(&stun_sock->mapped_addr, &mapped_attr->sockaddr);
if (op==PJ_STUN_SOCK_KEEP_ALIVE_OP)
op = PJ_STUN_SOCK_MAPPED_ADDR_CHANGE;
}
/* Notify user */
resched = (*stun_sock->cb.on_status)(stun_sock, op, PJ_SUCCESS);
on_return:
/* Start/restart keep-alive timer */
if (resched)
start_ka_timer(stun_sock);
}
/* Schedule keep-alive timer */
static void start_ka_timer(pj_stun_sock *stun_sock)
{
pj_timer_heap_cancel_if_active(stun_sock->stun_cfg.timer_heap,
&stun_sock->ka_timer, 0);
pj_assert(stun_sock->ka_interval != 0);
if (stun_sock->ka_interval > 0 && !stun_sock->is_destroying) {
pj_time_val delay;
delay.sec = stun_sock->ka_interval;
delay.msec = 0;
pj_timer_heap_schedule_w_grp_lock(stun_sock->stun_cfg.timer_heap,
&stun_sock->ka_timer,
&delay, PJ_TRUE,
stun_sock->grp_lock);
}
}
/* Keep-alive timer callback */
static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
{
pj_stun_sock *stun_sock;
stun_sock = (pj_stun_sock *) te->user_data;
PJ_UNUSED_ARG(th);
pj_grp_lock_acquire(stun_sock->grp_lock);
/* Time to send STUN Binding request */
if (get_mapped_addr(stun_sock) != PJ_SUCCESS) {
pj_grp_lock_release(stun_sock->grp_lock);
return;
}
/* Next keep-alive timer will be scheduled once the request
* is complete.
*/
pj_grp_lock_release(stun_sock->grp_lock);
}
/* Callback from active socket when incoming packet is received */
static pj_bool_t on_data_recvfrom(pj_activesock_t *asock,
void *data,
pj_size_t size,
const pj_sockaddr_t *src_addr,
int addr_len,
pj_status_t status)
{
pj_stun_sock *stun_sock;
pj_stun_msg_hdr *hdr;
pj_uint16_t type;
stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock);
if (!stun_sock)
return PJ_FALSE;
/* Log socket error */
if (status != PJ_SUCCESS) {
PJ_PERROR(2,(stun_sock->obj_name, status, "recvfrom() error"));
return PJ_TRUE;
}
pj_grp_lock_acquire(stun_sock->grp_lock);
/* Check that this is STUN message */
status = pj_stun_msg_check((const pj_uint8_t*)data, size,
PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET);
if (status != PJ_SUCCESS) {
/* Not STUN -- give it to application */
goto process_app_data;
}
/* Treat packet as STUN header and copy the STUN message type.
* We don't want to access the type directly from the header
* since it may not be properly aligned.
*/
hdr = (pj_stun_msg_hdr*) data;
pj_memcpy(&type, &hdr->type, 2);
type = pj_ntohs(type);
/* If the packet is a STUN Binding response and part of the
* transaction ID matches our internal ID, then this is
* our internal STUN message (Binding request or keep alive).
* Give it to our STUN session.
*/
if (!PJ_STUN_IS_RESPONSE(type) ||
PJ_STUN_GET_METHOD(type) != PJ_STUN_BINDING_METHOD ||
pj_memcmp(hdr->tsx_id, stun_sock->tsx_id, 10) != 0)
{
/* Not STUN Binding response, or STUN transaction ID mismatch.
* This is not our message too -- give it to application.
*/
goto process_app_data;
}
/* This is our STUN Binding response. Give it to the STUN session */
status = pj_stun_session_on_rx_pkt(stun_sock->stun_sess, data, size,
PJ_STUN_IS_DATAGRAM, NULL, NULL,
src_addr, addr_len);
status = pj_grp_lock_release(stun_sock->grp_lock);
return status!=PJ_EGONE ? PJ_TRUE : PJ_FALSE;
process_app_data:
if (stun_sock->cb.on_rx_data) {
pj_bool_t ret;
ret = (*stun_sock->cb.on_rx_data)(stun_sock, data, (unsigned)size,
src_addr, addr_len);
status = pj_grp_lock_release(stun_sock->grp_lock);
return status!=PJ_EGONE ? PJ_TRUE : PJ_FALSE;
}
status = pj_grp_lock_release(stun_sock->grp_lock);
return status!=PJ_EGONE ? PJ_TRUE : PJ_FALSE;
}
/* Callback from active socket about send status */
static pj_bool_t on_data_sent(pj_activesock_t *asock,
pj_ioqueue_op_key_t *send_key,
pj_ssize_t sent)
{
pj_stun_sock *stun_sock;
stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock);
if (!stun_sock)
return PJ_FALSE;
/* Don't report to callback if this is internal message */
if (send_key == &stun_sock->int_send_key) {
return PJ_TRUE;
}
/* Report to callback */
if (stun_sock->cb.on_data_sent) {
pj_bool_t ret;
pj_grp_lock_acquire(stun_sock->grp_lock);
/* If app gives NULL send_key in sendto() function, then give
* NULL in the callback too
*/
if (send_key == &stun_sock->send_key)
send_key = NULL;
/* Call callback */
ret = (*stun_sock->cb.on_data_sent)(stun_sock, send_key, sent);
pj_grp_lock_release(stun_sock->grp_lock);
return ret;
}
return PJ_TRUE;
}