pjproject/pjnath/src/pjnath/ice_strans.c

1144 lines
31 KiB
C

/* $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 <pjnath/ice_strans.h>
#include <pjnath/errno.h>
#include <pj/addr_resolv.h>
#include <pj/assert.h>
#include <pj/ip_helper.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
#if 0
# define TRACE_PKT(expr) PJ_LOG(5,expr)
#else
# define TRACE_PKT(expr)
#endif
/* ICE callbacks */
static void on_ice_complete(pj_ice_sess *ice, pj_status_t status);
static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
unsigned comp_id,
const void *pkt, pj_size_t size,
const pj_sockaddr_t *dst_addr,
unsigned dst_addr_len);
static void ice_rx_data(pj_ice_sess *ice,
unsigned comp_id,
void *pkt, pj_size_t size,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len);
/* Ioqueue callback */
static void on_read_complete(pj_ioqueue_key_t *key,
pj_ioqueue_op_key_t *op_key,
pj_ssize_t bytes_read);
static void destroy_component(pj_ice_strans_comp *comp);
static void destroy_ice_st(pj_ice_strans *ice_st, pj_status_t reason);
/* STUN session callback */
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 void stun_on_request_complete(pj_stun_session *sess,
pj_status_t status,
pj_stun_tx_data *tdata,
const pj_stun_msg *response,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len);
/* Keep-alive timer */
static void start_ka_timer(pj_ice_strans *ice_st);
static void stop_ka_timer(pj_ice_strans *ice_st);
/* Utility: print error */
#define ice_st_perror(ice_st,msg,rc) pjnath_perror(ice_st->obj_name,msg,rc)
/*
* Create ICE stream transport
*/
PJ_DEF(pj_status_t) pj_ice_strans_create( pj_stun_config *stun_cfg,
const char *name,
unsigned comp_cnt,
void *user_data,
const pj_ice_strans_cb *cb,
pj_ice_strans **p_ice_st)
{
pj_pool_t *pool;
pj_ice_strans *ice_st;
PJ_ASSERT_RETURN(stun_cfg && comp_cnt && cb && p_ice_st, PJ_EINVAL);
PJ_ASSERT_RETURN(stun_cfg->ioqueue && stun_cfg->timer_heap, PJ_EINVAL);
if (name == NULL)
name = "icstr%p";
pool = pj_pool_create(stun_cfg->pf, name, 1000, 512, NULL);
ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_strans);
ice_st->pool = pool;
pj_memcpy(ice_st->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
ice_st->user_data = user_data;
ice_st->comp_cnt = comp_cnt;
ice_st->comp = (pj_ice_strans_comp**) pj_pool_calloc(pool, comp_cnt,
sizeof(void*));
pj_memcpy(&ice_st->cb, cb, sizeof(*cb));
pj_memcpy(&ice_st->stun_cfg, stun_cfg, sizeof(*stun_cfg));
PJ_LOG(4,(ice_st->obj_name, "ICE stream transport created"));
*p_ice_st = ice_st;
return PJ_SUCCESS;
}
/* Destroy ICE */
static void destroy_ice_st(pj_ice_strans *ice_st, pj_status_t reason)
{
unsigned i;
char obj_name[PJ_MAX_OBJ_NAME];
if (reason == PJ_SUCCESS) {
pj_memcpy(obj_name, ice_st->obj_name, PJ_MAX_OBJ_NAME);
PJ_LOG(4,(obj_name, "ICE stream transport shutting down"));
}
/* Kill keep-alive timer, if any */
stop_ka_timer(ice_st);
/* Destroy ICE if we have ICE */
if (ice_st->ice) {
pj_ice_sess_destroy(ice_st->ice);
ice_st->ice = NULL;
}
/* Destroy all components */
for (i=0; i<ice_st->comp_cnt; ++i) {
if (ice_st->comp[i]) {
destroy_component(ice_st->comp[i]);
ice_st->comp[i] = NULL;
}
}
ice_st->comp_cnt = 0;
/* Done */
pj_pool_release(ice_st->pool);
if (reason == PJ_SUCCESS) {
PJ_LOG(4,(obj_name, "ICE stream transport destroyed"));
}
}
/*
* Destroy ICE stream transport.
*/
PJ_DEF(pj_status_t) pj_ice_strans_destroy(pj_ice_strans *ice_st)
{
destroy_ice_st(ice_st, PJ_SUCCESS);
return PJ_SUCCESS;
}
/*
* Resolve STUN server
*/
PJ_DEF(pj_status_t) pj_ice_strans_set_stun_domain(pj_ice_strans *ice_st,
pj_dns_resolver *resolver,
const pj_str_t *domain)
{
/* Yeah, TODO */
PJ_UNUSED_ARG(ice_st);
PJ_UNUSED_ARG(resolver);
PJ_UNUSED_ARG(domain);
return -1;
}
/*
* Set STUN server address.
*/
PJ_DEF(pj_status_t) pj_ice_strans_set_stun_srv( pj_ice_strans *ice_st,
const pj_sockaddr_in *stun_srv,
const pj_sockaddr_in *turn_srv)
{
PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
/* Must not have pending resolver job */
PJ_ASSERT_RETURN(ice_st->has_rjob==PJ_FALSE, PJ_EINVALIDOP);
if (stun_srv) {
pj_memcpy(&ice_st->stun_srv, stun_srv, sizeof(pj_sockaddr_in));
} else {
pj_bzero(&ice_st->stun_srv, sizeof(pj_sockaddr_in));
}
if (turn_srv) {
pj_memcpy(&ice_st->turn_srv, turn_srv, sizeof(pj_sockaddr_in));
} else {
pj_bzero(&ice_st->turn_srv, sizeof(pj_sockaddr_in));
}
return PJ_SUCCESS;
}
/* Add new candidate */
static pj_status_t add_cand( pj_ice_strans *ice_st,
pj_ice_strans_comp *comp,
unsigned comp_id,
pj_ice_cand_type type,
pj_uint16_t local_pref,
const pj_sockaddr_in *addr,
pj_bool_t set_default)
{
pj_ice_strans_cand *cand;
unsigned i;
PJ_ASSERT_RETURN(ice_st && comp && addr, PJ_EINVAL);
PJ_ASSERT_RETURN(comp->cand_cnt < PJ_ICE_ST_MAX_CAND, PJ_ETOOMANY);
/* Check that we don't have candidate with the same
* address.
*/
for (i=0; i<comp->cand_cnt; ++i) {
if (pj_memcmp(addr, &comp->cand_list[i].addr,
sizeof(pj_sockaddr_in))==0)
{
/* Duplicate */
PJ_LOG(5,(ice_st->obj_name, "Duplicate candidate not added"));
return PJ_SUCCESS;
}
}
cand = &comp->cand_list[comp->cand_cnt];
pj_bzero(cand, sizeof(*cand));
cand->type = type;
cand->status = PJ_SUCCESS;
pj_memcpy(&cand->addr, addr, sizeof(pj_sockaddr_in));
cand->ice_cand_id = -1;
cand->local_pref = local_pref;
pj_ice_calc_foundation(ice_st->pool, &cand->foundation, type,
&comp->local_addr);
if (set_default)
comp->default_cand = comp->cand_cnt;
PJ_LOG(5,(ice_st->obj_name,
"Candidate %s:%d (type=%s) added to component %d",
pj_inet_ntoa(addr->sin_addr),
(int)pj_ntohs(addr->sin_port),
pj_ice_get_cand_type_name(type),
comp_id));
comp->cand_cnt++;
return PJ_SUCCESS;
}
/* Create new component (i.e. socket) */
static pj_status_t create_component(pj_ice_strans *ice_st,
unsigned comp_id,
pj_uint32_t options,
const pj_sockaddr_in *addr,
pj_ice_strans_comp **p_comp)
{
enum { MAX_RETRY=100, PORT_INC=2 };
pj_ioqueue_callback ioqueue_cb;
pj_ice_strans_comp *comp;
int retry, addr_len;
struct {
pj_uint32_t a1, a2, a3;
} tsx_id;
pj_status_t status;
comp = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_strans_comp);
comp->ice_st = ice_st;
comp->comp_id = comp_id;
comp->options = options;
comp->sock = PJ_INVALID_SOCKET;
comp->last_status = PJ_SUCCESS;
/* Create transaction ID for STUN keep alives */
tsx_id.a1 = 0;
tsx_id.a2 = comp_id;
tsx_id.a3 = (pj_uint32_t) ice_st;
pj_memcpy(comp->ka_tsx_id, &tsx_id, sizeof(comp->ka_tsx_id));
/* Create socket */
status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &comp->sock);
if (status != PJ_SUCCESS)
return status;
/* Init address */
if (addr)
pj_memcpy(&comp->local_addr, addr, sizeof(pj_sockaddr_in));
else
pj_sockaddr_in_init(&comp->local_addr.ipv4, NULL, 0);
/* Retry binding socket */
for (retry=0; retry<MAX_RETRY; ++retry) {
pj_uint16_t port;
status = pj_sock_bind(comp->sock, &comp->local_addr,
sizeof(pj_sockaddr_in));
if (status == PJ_SUCCESS)
break;
if (options & PJ_ICE_ST_OPT_NO_PORT_RETRY)
goto on_error;
port = pj_ntohs(comp->local_addr.ipv4.sin_port);
port += PORT_INC;
comp->local_addr.ipv4.sin_port = pj_htons(port);
}
/* Get the actual port where the socket is bound to.
* (don't care about the address, it will be retrieved later)
*/
addr_len = sizeof(comp->local_addr);
status = pj_sock_getsockname(comp->sock, &comp->local_addr, &addr_len);
if (status != PJ_SUCCESS)
goto on_error;
/* Register to ioqueue */
pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
ioqueue_cb.on_read_complete = &on_read_complete;
status = pj_ioqueue_register_sock(ice_st->pool, ice_st->stun_cfg.ioqueue,
comp->sock, comp, &ioqueue_cb,
&comp->key);
if (status != PJ_SUCCESS)
goto on_error;
pj_ioqueue_op_key_init(&comp->read_op, sizeof(comp->read_op));
pj_ioqueue_op_key_init(&comp->write_op, sizeof(comp->write_op));
/* Kick start reading the socket */
on_read_complete(comp->key, &comp->read_op, 0);
/* If the socket is bound to INADDR_ANY, then lookup all interfaces in
* the host and add them into cand_list. Otherwise if the socket is bound
* to a specific interface, then only add that specific interface to
* cand_list.
*/
if (((options & PJ_ICE_ST_OPT_DONT_ADD_CAND)==0) &&
comp->local_addr.ipv4.sin_addr.s_addr == 0)
{
/* Socket is bound to INADDR_ANY */
unsigned i, ifs_cnt;
pj_in_addr ifs[PJ_ICE_ST_MAX_CAND-2];
/* Reset default candidate */
comp->default_cand = -1;
/* Enum all IP interfaces in the host */
ifs_cnt = PJ_ARRAY_SIZE(ifs);
status = pj_enum_ip_interface(&ifs_cnt, ifs);
if (status != PJ_SUCCESS)
goto on_error;
/* Set default IP interface as the base address */
status = pj_gethostip(&comp->local_addr.ipv4.sin_addr);
if (status != PJ_SUCCESS)
goto on_error;
/* Add candidate entry for each interface */
for (i=0; i<ifs_cnt; ++i) {
pj_sockaddr_in cand_addr;
pj_bool_t set_default;
pj_uint16_t local_pref;
/* Ignore 127.0.0.0/24 address */
if ((pj_ntohl(ifs[i].s_addr) >> 24)==127)
continue;
pj_memcpy(&cand_addr, &comp->local_addr, sizeof(pj_sockaddr_in));
cand_addr.sin_addr.s_addr = ifs[i].s_addr;
/* If the IP address is equal to local address, assign it
* as default candidate.
*/
if (ifs[i].s_addr == comp->local_addr.ipv4.sin_addr.s_addr) {
set_default = PJ_TRUE;
local_pref = 65535;
} else {
set_default = PJ_FALSE;
local_pref = 0;
}
status = add_cand(ice_st, comp, comp_id,
PJ_ICE_CAND_TYPE_HOST,
local_pref, &cand_addr, set_default);
if (status != PJ_SUCCESS)
goto on_error;
}
} else if ((options & PJ_ICE_ST_OPT_DONT_ADD_CAND)==0) {
/* Socket is bound to specific address.
* In this case only add that address as a single entry in the
* cand_list table.
*/
status = add_cand(ice_st, comp, comp_id,
PJ_ICE_CAND_TYPE_HOST,
65535, &comp->local_addr.ipv4,
PJ_TRUE);
if (status != PJ_SUCCESS)
goto on_error;
} else if (options & PJ_ICE_ST_OPT_DONT_ADD_CAND) {
/* If application doesn't want to add candidate, just fix local_addr
* in case its value is zero.
*/
if (comp->local_addr.ipv4.sin_addr.s_addr == 0) {
status = pj_gethostip(&comp->local_addr.ipv4.sin_addr);
if (status != PJ_SUCCESS)
return status;
}
}
/* Done */
if (p_comp)
*p_comp = comp;
return PJ_SUCCESS;
on_error:
destroy_component(comp);
return status;
}
/*
* This is callback called by ioqueue on incoming packet
*/
static void on_read_complete(pj_ioqueue_key_t *key,
pj_ioqueue_op_key_t *op_key,
pj_ssize_t bytes_read)
{
pj_ice_strans_comp *comp = (pj_ice_strans_comp*)
pj_ioqueue_get_user_data(key);
pj_ice_strans *ice_st = comp->ice_st;
pj_ssize_t pkt_size;
enum { RETRY = 4 };
unsigned retry;
pj_status_t status;
if (bytes_read > 0) {
/*
* Okay, we got a packet from the socket for the component. There is
* a bit of situation here, since this packet could be one of these:
*
* 1) this could be the response of STUN binding request sent by
* this component to a) an initial request to get the STUN mapped
* address of this component, or b) subsequent request to keep
* the binding alive.
*
* 2) this could be a packet (STUN or not STUN) sent from the STUN
* relay server. In this case, still there are few options to do
* for this packet: a) process this locally if this packet is
* related to TURN session management (e.g. Allocate response),
* b) forward this packet to ICE if this is related to ICE
* discovery process.
*
* 3) this could be a STUN request or response sent as part of ICE
* discovery process.
*
* 4) this could be application's packet, e.g. when ICE processing
* is done and agents start sending RTP/RTCP packets to each
* other, or when ICE processing is not done and this ICE stream
* transport decides to allow sending data.
*
* So far we don't have good solution for this.
* The process below is just a workaround.
*/
status = pj_stun_msg_check(comp->pkt, bytes_read,
PJ_STUN_IS_DATAGRAM);
if (status == PJ_SUCCESS) {
if (ice_st->ice==NULL &&
(comp->stun_sess &&
pj_memcmp(comp->pkt+8, comp->ka_tsx_id, 12) == 0))
{
status = pj_stun_session_on_rx_pkt(comp->stun_sess, comp->pkt,
bytes_read,
PJ_STUN_IS_DATAGRAM, NULL,
&comp->src_addr,
comp->src_addr_len);
} else if (ice_st->ice) {
PJ_TODO(DISTINGUISH_BETWEEN_LOCAL_AND_RELAY);
TRACE_PKT((comp->ice_st->obj_name,
"Component %d RX packet from %s:%d",
comp->comp_id,
pj_inet_ntoa(comp->src_addr.ipv4.sin_addr),
(int)pj_ntohs(comp->src_addr.ipv4.sin_port)));
status = pj_ice_sess_on_rx_pkt(ice_st->ice, comp->comp_id,
comp->pkt, bytes_read,
&comp->src_addr,
comp->src_addr_len);
} else {
/* This must have been a very late STUN reponse */
}
} else {
(*ice_st->cb.on_rx_data)(ice_st, comp->comp_id,
comp->pkt, bytes_read,
&comp->src_addr, comp->src_addr_len);
}
} else if (bytes_read < 0) {
ice_st_perror(comp->ice_st, "ioqueue read callback error",
-bytes_read);
}
/* Read next packet */
for (retry=0; retry<RETRY; ++retry) {
pkt_size = sizeof(comp->pkt);
comp->src_addr_len = sizeof(comp->src_addr);
status = pj_ioqueue_recvfrom(key, op_key, comp->pkt, &pkt_size,
PJ_IOQUEUE_ALWAYS_ASYNC,
&comp->src_addr, &comp->src_addr_len);
if (status != PJ_SUCCESS && status != PJ_EPENDING) {
ice_st_perror(comp->ice_st, "ioqueue recvfrom() error", status);
} else {
break;
}
}
}
/*
* Destroy a component
*/
static void destroy_component(pj_ice_strans_comp *comp)
{
if (comp->stun_sess) {
pj_stun_session_destroy(comp->stun_sess);
comp->stun_sess = NULL;
}
if (comp->key) {
pj_ioqueue_unregister(comp->key);
comp->key = NULL;
comp->sock = PJ_INVALID_SOCKET;
} else if (comp->sock != PJ_INVALID_SOCKET && comp->sock != 0) {
pj_sock_close(comp->sock);
comp->sock = PJ_INVALID_SOCKET;
}
}
/* STUN keep-alive timer callback */
static void ka_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
{
pj_ice_strans *ice_st = (pj_ice_strans*)te->user_data;
unsigned i;
pj_status_t status;
PJ_UNUSED_ARG(th);
ice_st->ka_timer.id = PJ_FALSE;
for (i=0; i<ice_st->comp_cnt; ++i) {
pj_ice_strans_comp *comp = ice_st->comp[i];
pj_stun_tx_data *tdata;
unsigned j;
/* Does this component have STUN server reflexive candidate? */
for (j=0; j<comp->cand_cnt; ++j) {
if (comp->cand_list[j].type == PJ_ICE_CAND_TYPE_SRFLX)
break;
}
if (j == comp->cand_cnt)
continue;
/* Create STUN binding request */
status = pj_stun_session_create_req(comp->stun_sess,
PJ_STUN_BINDING_REQUEST,
comp->ka_tsx_id, &tdata);
if (status != PJ_SUCCESS)
continue;
/* tdata->user_data is NULL for keep-alive */
tdata->user_data = NULL;
/* Send STUN binding request */
PJ_LOG(5,(ice_st->obj_name, "Sending STUN keep-alive"));
status = pj_stun_session_send_msg(comp->stun_sess, PJ_FALSE,
&ice_st->stun_srv,
sizeof(pj_sockaddr_in), tdata);
}
/* Start next timer */
start_ka_timer(ice_st);
}
/* Start STUN keep-alive timer */
static void start_ka_timer(pj_ice_strans *ice_st)
{
pj_time_val delay;
/* Skip if timer is already running */
if (ice_st->ka_timer.id != PJ_FALSE)
return;
delay.sec = PJ_ICE_ST_KEEP_ALIVE_MIN;
delay.msec = pj_rand() % (PJ_ICE_ST_KEEP_ALIVE_MAX_RAND * 1000);
pj_time_val_normalize(&delay);
ice_st->ka_timer.cb = &ka_timer_cb;
ice_st->ka_timer.user_data = ice_st;
if (pj_timer_heap_schedule(ice_st->stun_cfg.timer_heap,
&ice_st->ka_timer, &delay)==PJ_SUCCESS)
{
ice_st->ka_timer.id = PJ_TRUE;
}
}
/* Stop STUN keep-alive timer */
static void stop_ka_timer(pj_ice_strans *ice_st)
{
/* Skip if timer is already stop */
if (ice_st->ka_timer.id == PJ_FALSE)
return;
pj_timer_heap_cancel(ice_st->stun_cfg.timer_heap, &ice_st->ka_timer);
ice_st->ka_timer.id = PJ_FALSE;
}
/*
* Add STUN mapping to a component.
*/
static pj_status_t get_stun_mapped_addr(pj_ice_strans *ice_st,
pj_ice_strans_comp *comp)
{
pj_ice_strans_cand *cand;
pj_stun_session_cb sess_cb;
pj_stun_tx_data *tdata;
pj_status_t status;
PJ_ASSERT_RETURN(ice_st && comp, PJ_EINVAL);
/* Bail out if STUN server is still being resolved */
if (ice_st->has_rjob)
return PJ_EBUSY;
/* Just return (successfully) if STUN server is not configured */
if (ice_st->stun_srv.sin_family == 0)
return PJ_SUCCESS;
/* Create STUN session for this component */
pj_bzero(&sess_cb, sizeof(sess_cb));
sess_cb.on_request_complete = &stun_on_request_complete;
sess_cb.on_send_msg = &stun_on_send_msg;
status = pj_stun_session_create(&ice_st->stun_cfg, ice_st->obj_name,
&sess_cb, PJ_FALSE, &comp->stun_sess);
if (status != PJ_SUCCESS)
return status;
/* Associate component with STUN session */
pj_stun_session_set_user_data(comp->stun_sess, (void*)comp);
/* Create STUN binding request */
status = pj_stun_session_create_req(comp->stun_sess,
PJ_STUN_BINDING_REQUEST,
comp->ka_tsx_id,
&tdata);
if (status != PJ_SUCCESS)
return status;
/* Attach alias instance to tdata */
cand = &comp->cand_list[comp->cand_cnt];
tdata->user_data = (void*)cand;
/* Add pending count first, since stun_on_request_complete()
* may be called before this function completes
*/
comp->pending_cnt++;
/* Add new alias to this component */
cand->type = PJ_ICE_CAND_TYPE_SRFLX;
cand->status = PJ_EPENDING;
cand->ice_cand_id = -1;
cand->local_pref = 65535;
pj_ice_calc_foundation(ice_st->pool, &cand->foundation,
PJ_ICE_CAND_TYPE_SRFLX, &comp->local_addr);
++comp->cand_cnt;
/* Send STUN binding request */
status = pj_stun_session_send_msg(comp->stun_sess, PJ_FALSE,
&ice_st->stun_srv,
sizeof(pj_sockaddr_in), tdata);
if (status != PJ_SUCCESS) {
--comp->pending_cnt;
--comp->cand_cnt;
return status;
}
return PJ_SUCCESS;
}
/*
* Create the component.
*/
PJ_DEF(pj_status_t) pj_ice_strans_create_comp(pj_ice_strans *ice_st,
unsigned comp_id,
pj_uint32_t options,
const pj_sockaddr_in *addr)
{
pj_ice_strans_comp *comp = NULL;
pj_status_t status;
/* Verify arguments */
PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL);
/* Check that component ID present */
PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJNATH_EICEINCOMPID);
/* Can't add new component while ICE is running */
PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EBUSY);
/* Can't add new component while resolver is running */
PJ_ASSERT_RETURN(ice_st->has_rjob == PJ_FALSE, PJ_EBUSY);
/* Create component */
status = create_component(ice_st, comp_id, options, addr, &comp);
if (status != PJ_SUCCESS)
return status;
if ((options & PJ_ICE_ST_OPT_DISABLE_STUN) == 0) {
status = get_stun_mapped_addr(ice_st, comp);
if (status != PJ_SUCCESS) {
destroy_component(comp);
return status;
}
}
/* Store this component */
ice_st->comp[comp_id-1] = comp;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pj_ice_strans_add_cand( pj_ice_strans *ice_st,
unsigned comp_id,
pj_ice_cand_type type,
pj_uint16_t local_pref,
const pj_sockaddr_in *addr,
pj_bool_t set_default)
{
pj_ice_strans_comp *comp;
PJ_ASSERT_RETURN(ice_st && comp_id && addr, PJ_EINVAL);
PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJ_EINVAL);
PJ_ASSERT_RETURN(ice_st->comp[comp_id-1] != NULL, PJ_EINVALIDOP);
comp = ice_st->comp[comp_id-1];
return add_cand(ice_st, comp, comp_id, type, local_pref, addr,
set_default);
}
PJ_DEF(pj_status_t) pj_ice_strans_get_comps_status(pj_ice_strans *ice_st)
{
unsigned i;
pj_status_t worst = PJ_SUCCESS;
for (i=0; i<ice_st->comp_cnt; ++i) {
pj_ice_strans_comp *comp = ice_st->comp[i];
if (comp->last_status == PJ_SUCCESS) {
/* okay */
} else if (comp->pending_cnt && worst==PJ_SUCCESS) {
worst = PJ_EPENDING;
break;
} else if (comp->last_status != PJ_SUCCESS) {
worst = comp->last_status;
break;
}
if (worst != PJ_SUCCESS)
break;
}
return worst;
}
/*
* Create ICE!
*/
PJ_DEF(pj_status_t) pj_ice_strans_init_ice(pj_ice_strans *ice_st,
pj_ice_sess_role role,
const pj_str_t *local_ufrag,
const pj_str_t *local_passwd)
{
pj_status_t status;
unsigned i;
pj_ice_sess_cb ice_cb;
const pj_uint8_t srflx_prio[4] = { 100, 126, 110, 0 };
/* Check arguments */
PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
/* Must not have ICE */
PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP);
/* Components must have been created */
PJ_ASSERT_RETURN(ice_st->comp[0] != NULL, PJ_EINVALIDOP);
/* Init callback */
pj_bzero(&ice_cb, sizeof(ice_cb));
ice_cb.on_ice_complete = &on_ice_complete;
ice_cb.on_rx_data = &ice_rx_data;
ice_cb.on_tx_pkt = &ice_tx_pkt;
/* Create! */
status = pj_ice_sess_create(&ice_st->stun_cfg, ice_st->obj_name, role,
ice_st->comp_cnt, &ice_cb,
local_ufrag, local_passwd, &ice_st->ice);
if (status != PJ_SUCCESS)
return status;
/* Associate user data */
ice_st->ice->user_data = (void*)ice_st;
/* If default candidate for components are SRFLX one, upload a custom
* type priority to ICE session so that SRFLX candidates will get
* checked first.
*/
if (ice_st->comp[0]->default_cand >= 0 &&
ice_st->comp[0]->cand_list[ice_st->comp[0]->default_cand].type
== PJ_ICE_CAND_TYPE_SRFLX)
{
pj_ice_sess_set_prefs(ice_st->ice, srflx_prio);
}
/* Add candidates */
for (i=0; i<ice_st->comp_cnt; ++i) {
unsigned j;
pj_ice_strans_comp *comp= ice_st->comp[i];
for (j=0; j<comp->cand_cnt; ++j) {
pj_ice_strans_cand *cand = &comp->cand_list[j];
/* Skip if candidate is not ready */
if (cand->status != PJ_SUCCESS) {
PJ_LOG(5,(ice_st->obj_name,
"Candidate %d in component %d is not added",
j, i));
continue;
}
status = pj_ice_sess_add_cand(ice_st->ice, comp->comp_id,
cand->type, cand->local_pref,
&cand->foundation, &cand->addr,
&comp->local_addr, NULL,
sizeof(pj_sockaddr_in),
(unsigned*)&cand->ice_cand_id);
if (status != PJ_SUCCESS)
goto on_error;
}
}
return PJ_SUCCESS;
on_error:
pj_ice_strans_stop_ice(ice_st);
return status;
}
/*
* Enum candidates
*/
PJ_DEF(pj_status_t) pj_ice_strans_enum_cands(pj_ice_strans *ice_st,
unsigned *count,
pj_ice_sess_cand cand[])
{
unsigned i, cnt;
pj_ice_sess_cand *pcand;
PJ_ASSERT_RETURN(ice_st && count && cand, PJ_EINVAL);
PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP);
cnt = ice_st->ice->lcand_cnt;
cnt = (cnt > *count) ? *count : cnt;
*count = 0;
for (i=0; i<cnt; ++i) {
pcand = &ice_st->ice->lcand[i];
pj_memcpy(&cand[i], pcand, sizeof(pj_ice_sess_cand));
}
*count = cnt;
return PJ_SUCCESS;
}
/*
* Start ICE processing !
*/
PJ_DEF(pj_status_t) pj_ice_strans_start_ice( pj_ice_strans *ice_st,
const pj_str_t *rem_ufrag,
const pj_str_t *rem_passwd,
unsigned rem_cand_cnt,
const pj_ice_sess_cand rem_cand[])
{
pj_status_t status;
status = pj_ice_sess_create_check_list(ice_st->ice, rem_ufrag, rem_passwd,
rem_cand_cnt, rem_cand);
if (status != PJ_SUCCESS)
return status;
status = pj_ice_sess_start_check(ice_st->ice);
if (status != PJ_SUCCESS) {
pj_ice_strans_stop_ice(ice_st);
}
return status;
}
/*
* Stop ICE!
*/
PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st)
{
unsigned i;
if (ice_st->ice) {
pj_ice_sess_destroy(ice_st->ice);
ice_st->ice = NULL;
}
/* Invalidate all candidate Ids */
for (i=0; i<ice_st->comp_cnt; ++i) {
unsigned j;
for (j=0; j<ice_st->comp[i]->cand_cnt; ++j) {
ice_st->comp[i]->cand_list[j].ice_cand_id = -1;
}
}
return PJ_SUCCESS;
}
/*
* Send packet using non-ICE means (e.g. when ICE was not negotiated).
*/
PJ_DEF(pj_status_t) pj_ice_strans_sendto( pj_ice_strans *ice_st,
unsigned comp_id,
const void *data,
pj_size_t data_len,
const pj_sockaddr_t *dst_addr,
int dst_addr_len)
{
pj_ssize_t pkt_size;
pj_ice_strans_comp *comp;
pj_status_t status;
PJ_ASSERT_RETURN(ice_st && comp_id && comp_id <= ice_st->comp_cnt &&
dst_addr && dst_addr_len, PJ_EINVAL);
comp = ice_st->comp[comp_id-1];
/* If ICE is available, send data with ICE */
if (ice_st->ice) {
return pj_ice_sess_send_data(ice_st->ice, comp_id, data, data_len);
}
/* Otherwise send direcly with the socket. This is for compatibility
* with remote that doesn't support ICE.
*/
pkt_size = data_len;
status = pj_ioqueue_sendto(comp->key, &comp->write_op,
data, &pkt_size, 0,
dst_addr, dst_addr_len);
return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
}
/*
* Callback called by ICE session when ICE processing is complete, either
* successfully or with failure.
*/
static void on_ice_complete(pj_ice_sess *ice, pj_status_t status)
{
pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
if (ice_st->cb.on_ice_complete) {
(*ice_st->cb.on_ice_complete)(ice_st, status);
}
}
/*
* Callback called by ICE session when it wants to send outgoing packet.
*/
static pj_status_t ice_tx_pkt(pj_ice_sess *ice,
unsigned comp_id,
const void *pkt, pj_size_t size,
const pj_sockaddr_t *dst_addr,
unsigned dst_addr_len)
{
pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
pj_ice_strans_comp *comp = NULL;
pj_ssize_t pkt_size;
pj_status_t status;
PJ_TODO(TX_TO_RELAY);
PJ_ASSERT_RETURN(comp_id && comp_id <= ice_st->comp_cnt, PJ_EINVAL);
comp = ice_st->comp[comp_id-1];
TRACE_PKT((comp->ice_st->obj_name,
"Component %d TX packet to %s:%d",
comp_id,
pj_inet_ntoa(((pj_sockaddr_in*)dst_addr)->sin_addr),
(int)pj_ntohs(((pj_sockaddr_in*)dst_addr)->sin_port)));
pkt_size = size;
status = pj_ioqueue_sendto(comp->key, &comp->write_op,
pkt, &pkt_size, 0,
dst_addr, dst_addr_len);
return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
}
/*
* Callback called by ICE session when it receives application data.
*/
static void ice_rx_data(pj_ice_sess *ice,
unsigned comp_id,
void *pkt, pj_size_t size,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_ice_strans *ice_st = (pj_ice_strans*)ice->user_data;
if (ice_st->cb.on_rx_data) {
(*ice_st->cb.on_rx_data)(ice_st, comp_id, pkt, size,
src_addr, src_addr_len);
}
}
/*
* Callback called by STUN session to send outgoing packet.
*/
static pj_status_t stun_on_send_msg(pj_stun_session *sess,
const void *pkt,
pj_size_t size,
const pj_sockaddr_t *dst_addr,
unsigned dst_addr_len)
{
pj_ice_strans_comp *comp;
pj_ssize_t pkt_size;
pj_status_t status;
comp = (pj_ice_strans_comp*) pj_stun_session_get_user_data(sess);
pkt_size = size;
status = pj_ioqueue_sendto(comp->key, &comp->write_op,
pkt, &pkt_size, 0,
dst_addr, dst_addr_len);
return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
}
/*
* Callback sent by STUN session when outgoing STUN request has
* completed.
*/
static void stun_on_request_complete(pj_stun_session *sess,
pj_status_t status,
pj_stun_tx_data *tdata,
const pj_stun_msg *response,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len)
{
pj_ice_strans_comp *comp;
pj_ice_strans_cand *cand = NULL;
pj_stun_xor_mapped_addr_attr *xa;
pj_stun_mapped_addr_attr *ma;
pj_sockaddr *mapped_addr;
comp = (pj_ice_strans_comp*) pj_stun_session_get_user_data(sess);
cand = (pj_ice_strans_cand*) tdata->user_data;
PJ_UNUSED_ARG(src_addr);
PJ_UNUSED_ARG(src_addr_len);
if (cand == NULL) {
/* This is keep-alive */
if (status != PJ_SUCCESS) {
ice_st_perror(comp->ice_st, "STUN keep-alive request failed",
status);
}
return;
}
/* Decrement pending count for this component */
pj_assert(comp->pending_cnt > 0);
comp->pending_cnt--;
if (status != PJ_SUCCESS) {
comp->last_status = cand->status = status;
ice_st_perror(comp->ice_st, "STUN Binding request failed",
cand->status);
return;
}
xa = (pj_stun_xor_mapped_addr_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0);
ma = (pj_stun_mapped_addr_attr*)
pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0);
if (xa)
mapped_addr = &xa->sockaddr;
else if (ma)
mapped_addr = &ma->sockaddr;
else {
cand->status = PJNATH_ESTUNNOMAPPEDADDR;
ice_st_perror(comp->ice_st, "STUN Binding request failed",
cand->status);
return;
}
PJ_LOG(4,(comp->ice_st->obj_name,
"STUN mapped address: %s:%d",
pj_inet_ntoa(mapped_addr->ipv4.sin_addr),
(int)pj_ntohs(mapped_addr->ipv4.sin_port)));
pj_memcpy(&cand->addr, mapped_addr, sizeof(pj_sockaddr_in));
cand->status = PJ_SUCCESS;
/* Set this candidate as the default candidate */
comp->default_cand = (cand - comp->cand_list);
comp->last_status = PJ_SUCCESS;
/* We have STUN, so we must start the keep-alive timer */
start_ka_timer(comp->ice_st);
}