summaryrefslogtreecommitdiff
path: root/pjnath/src/pjnath/ice_strans.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjnath/src/pjnath/ice_strans.c')
-rw-r--r--pjnath/src/pjnath/ice_strans.c984
1 files changed, 984 insertions, 0 deletions
diff --git a/pjnath/src/pjnath/ice_strans.c b/pjnath/src/pjnath/ice_strans.c
new file mode 100644
index 00000000..ab6524a8
--- /dev/null
+++ b/pjnath/src/pjnath/ice_strans.c
@@ -0,0 +1,984 @@
+/* $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/string.h>
+
+
+
+/* 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, unsigned cand_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);
+
+/* Utility: print error */
+#if PJ_LOG_MAX_LEVEL >= 3
+static void ice_st_perror(pj_ice_strans *ice_st, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(ice_st->obj_name, "%s: %s", title, errmsg));
+}
+#else
+# define ice_st_perror(ice_st, title, status)
+#endif
+
+
+/*
+ * Create ICE stream transport
+ */
+PJ_DECL(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 = "icest%p";
+
+ pool = pj_pool_create(stun_cfg->pf, name, 4000, 4000, 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"));
+ }
+
+ /* 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;
+
+ PJ_ASSERT_RETURN(ice_st && comp && addr, PJ_EINVAL);
+ PJ_ASSERT_RETURN(comp->cand_cnt < PJ_ICE_ST_MAX_CAND, PJ_ETOOMANY);
+
+ 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,
+ (const pj_sockaddr*)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;
+ 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 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;
+
+ /* 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;
+ } else {
+ set_default = PJ_FALSE;
+ }
+
+ status = add_cand(ice_st, comp, comp_id,
+ PJ_ICE_CAND_TYPE_HOST,
+ (pj_uint16_t)(65535-i), &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;
+ 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.
+ */
+ if (ice_st->ice) {
+ PJ_TODO(DISTINGUISH_BETWEEN_LOCAL_AND_RELAY);
+ status = pj_ice_sess_on_rx_pkt(ice_st->ice, comp->comp_id,
+ comp->cand_list[0].ice_cand_id,
+ comp->pkt, bytes_read,
+ &comp->src_addr, comp->src_addr_len);
+ } else if (comp->stun_sess) {
+ status = pj_stun_msg_check(comp->pkt, bytes_read,
+ PJ_STUN_IS_DATAGRAM);
+ if (status == PJ_SUCCESS) {
+ 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 {
+ (*ice_st->cb.on_rx_data)(ice_st, comp->comp_id,
+ comp->pkt, bytes_read,
+ &comp->src_addr, comp->src_addr_len);
+
+ }
+ } 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 */
+ 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);
+ }
+}
+
+/*
+ * 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;
+ }
+}
+
+
+
+/*
+ * 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, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Attach alias instance to tdata */
+ cand = &comp->cand_list[comp->cand_cnt];
+ tdata->user_data = (void*)cand;
+
+ /* 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)
+ return status;
+
+
+ /* 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;
+
+ /* Add pending count for this component */
+ comp->pending_cnt++;
+
+ 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;
+ 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;
+
+ /* 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;
+
+ /* 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_DECL(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);
+ }
+
+#if 1
+ PJ_UNUSED_ARG(pkt_size);
+ PJ_UNUSED_ARG(status);
+
+ /* Otherwise return error */
+ return PJNATH_EICEINPROGRESS;
+#else
+ /* Otherwise send direcly with the socket */
+ 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;
+#endif
+}
+
+/*
+ * 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, unsigned cand_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];
+
+ 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)
+{
+ 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;
+
+ /* 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;
+}
+