michael@0: /* michael@0: Copyright (c) 2007, Adobe Systems, Incorporated michael@0: All rights reserved. michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions are michael@0: met: michael@0: michael@0: * Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: michael@0: * Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: michael@0: * Neither the name of Adobe Systems, Network Resonance nor the names of its michael@0: contributors may be used to endorse or promote products derived from michael@0: this software without specific prior written permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: michael@0: michael@0: static char *RCSSTRING __UNUSED__="$Id: ice_candidate.c,v 1.2 2008/04/28 17:59:01 ekr Exp $"; michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #ifdef WIN32 michael@0: #include michael@0: #else michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: #include "nr_api.h" michael@0: #include "registry.h" michael@0: #include "nr_socket.h" michael@0: #include "async_timer.h" michael@0: michael@0: #include "stun_client_ctx.h" michael@0: #include "stun_server_ctx.h" michael@0: #include "turn_client_ctx.h" michael@0: #include "ice_ctx.h" michael@0: #include "ice_candidate.h" michael@0: #include "ice_codeword.h" michael@0: #include "ice_reg.h" michael@0: #include "ice_util.h" michael@0: #include "nr_socket_turn.h" michael@0: #include "nr_socket.h" michael@0: michael@0: static int next_automatic_preference = 224; michael@0: michael@0: static int nr_ice_candidate_initialize2(nr_ice_candidate *cand); michael@0: static int nr_ice_get_foundation(nr_ice_ctx *ctx,nr_ice_candidate *cand); michael@0: static int nr_ice_srvrflx_start_stun(nr_ice_candidate *cand); michael@0: static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_arg); michael@0: #ifdef USE_TURN michael@0: static int nr_ice_start_relay_turn(nr_ice_candidate *cand); michael@0: static void nr_ice_turn_allocated_cb(NR_SOCKET sock, int how, void *cb_arg); michael@0: static int nr_ice_candidate_resolved_cb(void *cb_arg, nr_transport_addr *addr); michael@0: #endif /* USE_TURN */ michael@0: michael@0: void nr_ice_candidate_compute_codeword(nr_ice_candidate *cand) michael@0: { michael@0: char as_string[1024]; michael@0: michael@0: snprintf(as_string, michael@0: sizeof(as_string), michael@0: "%s(%s)", michael@0: cand->addr.as_string, michael@0: cand->label); michael@0: michael@0: nr_ice_compute_codeword(as_string,strlen(as_string),cand->codeword); michael@0: } michael@0: michael@0: char *nr_ice_candidate_type_names[]={0,"host","srflx","prflx","relay",0}; michael@0: michael@0: static const char *nr_ctype_name(nr_ice_candidate_type ctype) { michael@0: assert(ctype0); michael@0: if (ctype <= 0 || ctype >= CTYPE_MAX) { michael@0: return "ERROR"; michael@0: } michael@0: return nr_ice_candidate_type_names[ctype]; michael@0: } michael@0: michael@0: static int nr_ice_candidate_format_stun_label(char *label, size_t size, nr_ice_candidate *cand) michael@0: { michael@0: int _status; michael@0: michael@0: *label = 0; michael@0: switch(cand->stun_server->type) { michael@0: case NR_ICE_STUN_SERVER_TYPE_ADDR: michael@0: snprintf(label, size, "%s(%s|%s)", nr_ctype_name(cand->type), cand->base.as_string, michael@0: cand->stun_server->u.addr.as_string); michael@0: break; michael@0: case NR_ICE_STUN_SERVER_TYPE_DNSNAME: michael@0: snprintf(label, size, "%s(%s|%s:%u)", nr_ctype_name(cand->type), cand->base.as_string, michael@0: cand->stun_server->u.dnsname.host, cand->stun_server->u.dnsname.port); michael@0: break; michael@0: default: michael@0: assert(0); michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int nr_ice_candidate_create(nr_ice_ctx *ctx,nr_ice_component *comp,nr_ice_socket *isock, nr_socket *osock, nr_ice_candidate_type ctype, nr_ice_stun_server *stun_server, UCHAR component_id, nr_ice_candidate **candp) michael@0: { michael@0: nr_ice_candidate *cand=0; michael@0: nr_ice_candidate *tmp=0; michael@0: int r,_status; michael@0: char label[512]; michael@0: michael@0: if(!(cand=RCALLOC(sizeof(nr_ice_candidate)))) michael@0: ABORT(R_NO_MEMORY); michael@0: cand->state=NR_ICE_CAND_STATE_CREATED; michael@0: cand->ctx=ctx; michael@0: cand->isock=isock; michael@0: cand->osock=osock; michael@0: cand->type=ctype; michael@0: cand->stun_server=stun_server; michael@0: cand->component_id=component_id; michael@0: cand->component=comp; michael@0: cand->stream=comp->stream; michael@0: michael@0: /* Extract the addr as the base */ michael@0: if(r=nr_socket_getaddr(cand->isock->sock,&cand->base)) michael@0: ABORT(r); michael@0: michael@0: switch(ctype) { michael@0: case HOST: michael@0: snprintf(label, sizeof(label), "host(%s)", cand->base.as_string); michael@0: break; michael@0: michael@0: case SERVER_REFLEXIVE: michael@0: if(r=nr_ice_candidate_format_stun_label(label, sizeof(label),cand)) michael@0: ABORT(r); michael@0: break; michael@0: michael@0: case RELAYED: michael@0: if(r=nr_ice_candidate_format_stun_label(label, sizeof(label),cand)) michael@0: ABORT(r); michael@0: break; michael@0: michael@0: case PEER_REFLEXIVE: michael@0: snprintf(label, sizeof(label), "prflx"); michael@0: break; michael@0: michael@0: default: michael@0: assert(0); /* Can't happen */ michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: if(!(cand->label=r_strdup(label))) michael@0: ABORT(R_NO_MEMORY); michael@0: michael@0: if(r=nr_ice_get_foundation(ctx,cand)) michael@0: ABORT(r); michael@0: if(r=nr_ice_candidate_compute_priority(cand)) michael@0: ABORT(r); michael@0: michael@0: TAILQ_FOREACH(tmp,&isock->candidates,entry_sock){ michael@0: if(cand->priority==tmp->priority){ michael@0: r_log(LOG_ICE,LOG_ERR,"ICE(%s): duplicate priority %u candidate %s and candidate %s", michael@0: ctx->label,cand->priority,cand->label,tmp->label); michael@0: } michael@0: } michael@0: michael@0: if(ctype==RELAYED) michael@0: cand->u.relayed.turn_sock=osock; michael@0: michael@0: michael@0: /* Add the candidate to the isock list*/ michael@0: TAILQ_INSERT_TAIL(&isock->candidates,cand,entry_sock); michael@0: michael@0: nr_ice_candidate_compute_codeword(cand); michael@0: michael@0: r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): created candidate %s with type %s", michael@0: ctx->label,cand->label,nr_ctype_name(ctype)); michael@0: michael@0: *candp=cand; michael@0: michael@0: _status=0; michael@0: abort: michael@0: if (_status){ michael@0: r_log(LOG_ICE,LOG_ERR,"ICE(%s): Failed to create candidate of type %s", ctx->label,nr_ctype_name(ctype)); michael@0: nr_ice_candidate_destroy(&cand); michael@0: } michael@0: return(_status); michael@0: } michael@0: michael@0: michael@0: /* Create a peer reflexive candidate */ michael@0: int nr_ice_peer_peer_rflx_candidate_create(nr_ice_ctx *ctx,char *label, nr_ice_component *comp,nr_transport_addr *addr, nr_ice_candidate **candp) michael@0: { michael@0: nr_ice_candidate *cand=0; michael@0: nr_ice_candidate_type ctype=PEER_REFLEXIVE; michael@0: int r,_status; michael@0: michael@0: if(!(cand=RCALLOC(sizeof(nr_ice_candidate)))) michael@0: ABORT(R_NO_MEMORY); michael@0: if(!(cand->label=r_strdup(label))) michael@0: ABORT(R_NO_MEMORY); michael@0: michael@0: cand->state=NR_ICE_CAND_STATE_INITIALIZED; michael@0: cand->ctx=ctx; michael@0: cand->type=ctype; michael@0: cand->component_id=comp->component_id; michael@0: cand->component=comp; michael@0: cand->stream=comp->stream; michael@0: michael@0: michael@0: r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): creating candidate with type %s", michael@0: ctx->label,label,nr_ctype_name(ctype)); michael@0: michael@0: if(r=nr_transport_addr_copy(&cand->base,addr)) michael@0: ABORT(r); michael@0: if(r=nr_transport_addr_copy(&cand->addr,addr)) michael@0: ABORT(r); michael@0: /* Bogus foundation */ michael@0: if(!(cand->foundation=r_strdup(cand->addr.as_string))) michael@0: ABORT(R_NO_MEMORY); michael@0: michael@0: nr_ice_candidate_compute_codeword(cand); michael@0: michael@0: *candp=cand; michael@0: michael@0: _status=0; michael@0: abort: michael@0: if (_status){ michael@0: nr_ice_candidate_destroy(&cand); michael@0: } michael@0: return(_status); michael@0: } michael@0: michael@0: int nr_ice_candidate_destroy(nr_ice_candidate **candp) michael@0: { michael@0: nr_ice_candidate *cand=0; michael@0: michael@0: if(!candp || !*candp) michael@0: return(0); michael@0: michael@0: cand=*candp; michael@0: michael@0: switch(cand->type){ michael@0: case HOST: michael@0: break; michael@0: #ifdef USE_TURN michael@0: case RELAYED: michael@0: if (cand->u.relayed.turn_handle) michael@0: nr_ice_socket_deregister(cand->isock, cand->u.relayed.turn_handle); michael@0: nr_turn_client_ctx_destroy(&cand->u.relayed.turn); michael@0: nr_socket_destroy(&cand->u.relayed.turn_sock); michael@0: break; michael@0: #endif /* USE_TURN */ michael@0: case SERVER_REFLEXIVE: michael@0: if (cand->u.srvrflx.stun_handle) michael@0: nr_ice_socket_deregister(cand->isock, cand->u.srvrflx.stun_handle); michael@0: nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: NR_async_timer_cancel(cand->delay_timer); michael@0: NR_async_timer_cancel(cand->ready_cb_timer); michael@0: if(cand->resolver_handle){ michael@0: nr_resolver_cancel(cand->ctx->resolver,cand->resolver_handle); michael@0: } michael@0: michael@0: RFREE(cand->foundation); michael@0: RFREE(cand->label); michael@0: RFREE(cand); michael@0: michael@0: return(0); michael@0: } michael@0: michael@0: void nr_ice_candidate_destroy_cb(NR_SOCKET s, int h, void *cb_arg) michael@0: { michael@0: nr_ice_candidate *cand=cb_arg; michael@0: nr_ice_candidate_destroy(&cand); michael@0: } michael@0: michael@0: /* This algorithm is not super-fast, but I don't think we need a hash michael@0: table just yet and it produces a small foundation string */ michael@0: static int nr_ice_get_foundation(nr_ice_ctx *ctx,nr_ice_candidate *cand) michael@0: { michael@0: nr_ice_foundation *foundation; michael@0: int i=0; michael@0: char fnd[20]; michael@0: int _status; michael@0: michael@0: foundation=STAILQ_FIRST(&ctx->foundations); michael@0: while(foundation){ michael@0: if(nr_transport_addr_cmp(&cand->base,&foundation->addr,NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) michael@0: goto next; michael@0: if(cand->type != foundation->type) michael@0: goto next; michael@0: if(cand->stun_server != foundation->stun_server) michael@0: goto next; michael@0: michael@0: snprintf(fnd,sizeof(fnd),"%d",i); michael@0: if(!(cand->foundation=r_strdup(fnd))) michael@0: ABORT(R_NO_MEMORY); michael@0: return(0); michael@0: michael@0: next: michael@0: foundation=STAILQ_NEXT(foundation,entry); michael@0: i++; michael@0: } michael@0: michael@0: if(!(foundation=RCALLOC(sizeof(nr_ice_foundation)))) michael@0: ABORT(R_NO_MEMORY); michael@0: nr_transport_addr_copy(&foundation->addr,&cand->base); michael@0: foundation->type=cand->type; michael@0: foundation->stun_server=cand->stun_server; michael@0: STAILQ_INSERT_TAIL(&ctx->foundations,foundation,entry); michael@0: michael@0: snprintf(fnd,sizeof(fnd),"%d",i); michael@0: if(!(cand->foundation=r_strdup(fnd))) michael@0: ABORT(R_NO_MEMORY); michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: int nr_ice_candidate_compute_priority(nr_ice_candidate *cand) michael@0: { michael@0: UCHAR type_preference; michael@0: UCHAR interface_preference; michael@0: UCHAR stun_priority; michael@0: int r,_status; michael@0: michael@0: switch(cand->type){ michael@0: case HOST: michael@0: if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_HOST,&type_preference)) michael@0: ABORT(r); michael@0: stun_priority=0; michael@0: break; michael@0: case RELAYED: michael@0: if(cand->base.protocol == IPPROTO_UDP) { michael@0: if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_RELAYED,&type_preference)) michael@0: ABORT(r); michael@0: } michael@0: else if(cand->base.protocol == IPPROTO_TCP) { michael@0: if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_RELAYED_TCP,&type_preference)) michael@0: ABORT(r); michael@0: michael@0: } michael@0: else { michael@0: r_log(LOG_ICE,LOG_ERR,"Unknown protocol type %u", michael@0: (unsigned int)cand->base.protocol); michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: stun_priority=255-cand->stun_server->index; michael@0: break; michael@0: case SERVER_REFLEXIVE: michael@0: if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_SRV_RFLX,&type_preference)) michael@0: ABORT(r); michael@0: stun_priority=255-cand->stun_server->index; michael@0: break; michael@0: case PEER_REFLEXIVE: michael@0: if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_PEER_RFLX,&type_preference)) michael@0: ABORT(r); michael@0: stun_priority=0; michael@0: break; michael@0: default: michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: if(type_preference > 126) michael@0: r_log(LOG_ICE,LOG_ERR,"Illegal type preference %d",type_preference); michael@0: michael@0: if(!cand->ctx->interface_prioritizer) { michael@0: /* Prioritizer is not set, read from registry */ michael@0: if(r=NR_reg_get2_uchar(NR_ICE_REG_PREF_INTERFACE_PRFX,cand->base.ifname, michael@0: &interface_preference)) { michael@0: if (r==R_NOT_FOUND) { michael@0: if (next_automatic_preference == 1) { michael@0: r_log(LOG_ICE,LOG_ERR,"Out of preference values. Can't assign one for interface %s",cand->base.ifname); michael@0: ABORT(R_NOT_FOUND); michael@0: } michael@0: r_log(LOG_ICE,LOG_DEBUG,"Automatically assigning preference for interface %s->%d",cand->base.ifname, michael@0: next_automatic_preference); michael@0: if (r=NR_reg_set2_uchar(NR_ICE_REG_PREF_INTERFACE_PRFX,cand->base.ifname,next_automatic_preference)){ michael@0: ABORT(r); michael@0: } michael@0: interface_preference=next_automatic_preference; michael@0: next_automatic_preference--; michael@0: } michael@0: else { michael@0: ABORT(r); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: char key_of_interface[MAXIFNAME + 41]; michael@0: nr_transport_addr addr; michael@0: michael@0: if(r=nr_socket_getaddr(cand->isock->sock, &addr)) michael@0: ABORT(r); michael@0: michael@0: if(r=nr_transport_addr_fmt_ifname_addr_string(&addr,key_of_interface, michael@0: sizeof(key_of_interface))) { michael@0: ABORT(r); michael@0: } michael@0: if(r=nr_interface_prioritizer_get_priority(cand->ctx->interface_prioritizer, michael@0: key_of_interface,&interface_preference)) { michael@0: ABORT(r); michael@0: } michael@0: } michael@0: michael@0: cand->priority= michael@0: (type_preference << 24) | michael@0: (interface_preference << 16) | michael@0: (stun_priority << 8) | michael@0: (256 - cand->component_id); michael@0: michael@0: /* S 4.1.2 */ michael@0: assert(cand->priority>=1&&cand->priority<=2147483647); michael@0: michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: michael@0: static void nr_ice_candidate_fire_ready_cb(NR_SOCKET s, int how, void *cb_arg) michael@0: { michael@0: nr_ice_candidate *cand = cb_arg; michael@0: michael@0: cand->ready_cb_timer = 0; michael@0: cand->ready_cb(0, 0, cand->ready_cb_arg); michael@0: } michael@0: michael@0: int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, void *cb_arg) michael@0: { michael@0: int r,_status; michael@0: int protocol=NR_RESOLVE_PROTOCOL_STUN; michael@0: int transport=IPPROTO_UDP; michael@0: cand->done_cb=ready_cb; michael@0: cand->cb_arg=cb_arg; michael@0: michael@0: switch(cand->type){ michael@0: case HOST: michael@0: if(r=nr_socket_getaddr(cand->isock->sock,&cand->addr)) michael@0: ABORT(r); michael@0: cand->osock=cand->isock->sock; michael@0: cand->state=NR_ICE_CAND_STATE_INITIALIZED; michael@0: // Post this so that it doesn't happen in-line michael@0: cand->ready_cb = ready_cb; michael@0: cand->ready_cb_arg = cb_arg; michael@0: NR_ASYNC_TIMER_SET(0, nr_ice_candidate_fire_ready_cb, (void *)cand, &cand->ready_cb_timer); michael@0: break; michael@0: #ifdef USE_TURN michael@0: case RELAYED: michael@0: protocol=NR_RESOLVE_PROTOCOL_TURN; michael@0: transport=cand->u.relayed.server->transport; michael@0: /* Fall through */ michael@0: #endif michael@0: case SERVER_REFLEXIVE: michael@0: cand->state=NR_ICE_CAND_STATE_INITIALIZING; michael@0: michael@0: if(cand->stun_server->type == NR_ICE_STUN_SERVER_TYPE_ADDR) { michael@0: /* Just copy the address */ michael@0: if (r=nr_transport_addr_copy(&cand->stun_server_addr, michael@0: &cand->stun_server->u.addr)) { michael@0: r_log(LOG_ICE,LOG_ERR,"ICE-CANDIDATE(%s): Could not copy STUN server addr", cand->label); michael@0: ABORT(r); michael@0: } michael@0: michael@0: if(r=nr_ice_candidate_initialize2(cand)) michael@0: ABORT(r); michael@0: } michael@0: else { michael@0: nr_resolver_resource resource; michael@0: resource.domain_name=cand->stun_server->u.dnsname.host; michael@0: resource.port=cand->stun_server->u.dnsname.port; michael@0: resource.stun_turn=protocol; michael@0: resource.transport_protocol=transport; michael@0: michael@0: /* Try to resolve */ michael@0: if(!cand->ctx->resolver) { michael@0: r_log(LOG_ICE, LOG_ERR, "ICE-CANDIDATE(%s): Can't use DNS names without a resolver", cand->label); michael@0: ABORT(R_BAD_ARGS); michael@0: } michael@0: michael@0: if(r=nr_resolver_resolve(cand->ctx->resolver, michael@0: &resource, michael@0: nr_ice_candidate_resolved_cb, michael@0: (void *)cand, michael@0: &cand->resolver_handle)){ michael@0: r_log(LOG_ICE,LOG_ERR,"ICE-CANDIDATE(%s): Could not invoke DNS resolver",cand->label); michael@0: ABORT(r); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: nr_ice_candidate_compute_codeword(cand); michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status && _status!=R_WOULDBLOCK) michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: return(_status); michael@0: } michael@0: michael@0: michael@0: static int nr_ice_candidate_resolved_cb(void *cb_arg, nr_transport_addr *addr) michael@0: { michael@0: nr_ice_candidate *cand=cb_arg; michael@0: int r,_status; michael@0: michael@0: cand->resolver_handle=0; michael@0: michael@0: if(addr){ michael@0: r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): resolved candidate %s. addr=%s", michael@0: cand->ctx->label,cand->label,addr->as_string); michael@0: } michael@0: else { michael@0: r_log(LOG_ICE,LOG_WARNING,"ICE(%s): failed to resolve candidate %s.", michael@0: cand->ctx->label,cand->label); michael@0: ABORT(R_NOT_FOUND); michael@0: } michael@0: michael@0: /* Copy the address */ michael@0: if(r=nr_transport_addr_copy(&cand->stun_server_addr,addr)) michael@0: ABORT(r); michael@0: michael@0: /* Now start initializing */ michael@0: if(r=nr_ice_candidate_initialize2(cand)) michael@0: ABORT(r); michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status && _status!=R_WOULDBLOCK) { michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: cand->done_cb(0,0,cand->cb_arg); michael@0: } michael@0: return(_status); michael@0: } michael@0: michael@0: static int nr_ice_candidate_initialize2(nr_ice_candidate *cand) michael@0: { michael@0: int r,_status; michael@0: michael@0: switch(cand->type){ michael@0: case HOST: michael@0: assert(0); /* Can't happen */ michael@0: ABORT(R_INTERNAL); michael@0: break; michael@0: #ifdef USE_TURN michael@0: case RELAYED: michael@0: if(r=nr_ice_start_relay_turn(cand)) michael@0: ABORT(r); michael@0: ABORT(R_WOULDBLOCK); michael@0: break; michael@0: #endif /* USE_TURN */ michael@0: case SERVER_REFLEXIVE: michael@0: /* Need to start stun */ michael@0: if(r=nr_ice_srvrflx_start_stun(cand)) michael@0: ABORT(r); michael@0: cand->osock=cand->isock->sock; michael@0: ABORT(R_WOULDBLOCK); michael@0: break; michael@0: default: michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status && _status!=R_WOULDBLOCK) michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: return(_status); michael@0: } michael@0: michael@0: static void nr_ice_srvrflx_start_stun_timer_cb(NR_SOCKET s, int how, void *cb_arg) michael@0: { michael@0: nr_ice_candidate *cand=cb_arg; michael@0: int r,_status; michael@0: michael@0: cand->delay_timer=0; michael@0: michael@0: /* TODO: if the response is a BINDING-ERROR-RESPONSE, then restart michael@0: * TODO: using NR_STUN_CLIENT_MODE_BINDING_REQUEST because the michael@0: * TODO: server may not have understood the 0.96-style request */ michael@0: if(r=nr_stun_client_start(cand->u.srvrflx.stun, NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH, nr_ice_srvrflx_stun_finished_cb, cand)) michael@0: ABORT(r); michael@0: michael@0: if(r=nr_ice_ctx_remember_id(cand->ctx, cand->u.srvrflx.stun->request)) michael@0: ABORT(r); michael@0: michael@0: if(r=nr_ice_socket_register_stun_client(cand->isock,cand->u.srvrflx.stun,&cand->u.srvrflx.stun_handle)) michael@0: ABORT(r); michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status){ michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: static int nr_ice_srvrflx_start_stun(nr_ice_candidate *cand) michael@0: { michael@0: int r,_status; michael@0: michael@0: assert(!cand->delay_timer); michael@0: if(r=nr_stun_client_ctx_create(cand->label, cand->isock->sock, michael@0: &cand->stun_server_addr, cand->stream->ctx->gather_rto, michael@0: &cand->u.srvrflx.stun)) michael@0: ABORT(r); michael@0: michael@0: NR_ASYNC_TIMER_SET(cand->stream->ctx->stun_delay,nr_ice_srvrflx_start_stun_timer_cb,cand,&cand->delay_timer); michael@0: cand->stream->ctx->stun_delay += cand->stream->ctx->Ta; michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status){ michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: } michael@0: return(_status); michael@0: } michael@0: michael@0: #ifdef USE_TURN michael@0: static void nr_ice_start_relay_turn_timer_cb(NR_SOCKET s, int how, void *cb_arg) michael@0: { michael@0: nr_ice_candidate *cand=cb_arg; michael@0: int r,_status; michael@0: michael@0: cand->delay_timer=0; michael@0: michael@0: if(r=nr_turn_client_allocate(cand->u.relayed.turn, nr_ice_turn_allocated_cb, cb_arg)) michael@0: ABORT(r); michael@0: michael@0: if(r=nr_ice_socket_register_turn_client(cand->isock, cand->u.relayed.turn, michael@0: cand->osock, &cand->u.relayed.turn_handle)) michael@0: ABORT(r); michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status){ michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: static int nr_ice_start_relay_turn(nr_ice_candidate *cand) michael@0: { michael@0: int r,_status; michael@0: assert(!cand->delay_timer); michael@0: if(r=nr_turn_client_ctx_create(cand->label, cand->isock->sock, michael@0: cand->u.relayed.server->username, michael@0: cand->u.relayed.server->password, michael@0: &cand->stun_server_addr, michael@0: &cand->u.relayed.turn)) michael@0: ABORT(r); michael@0: michael@0: if(r=nr_socket_turn_set_ctx(cand->osock, cand->u.relayed.turn)) michael@0: ABORT(r); michael@0: michael@0: NR_ASYNC_TIMER_SET(cand->stream->ctx->stun_delay,nr_ice_start_relay_turn_timer_cb,cand,&cand->delay_timer); michael@0: cand->stream->ctx->stun_delay += cand->stream->ctx->Ta; michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status){ michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: } michael@0: return(_status); michael@0: } michael@0: #endif /* USE_TURN */ michael@0: michael@0: static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_arg) michael@0: { michael@0: int _status; michael@0: nr_ice_candidate *cand=cb_arg; michael@0: michael@0: r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): %s",cand->ctx->label,cand->label,__FUNCTION__); michael@0: michael@0: /* Deregister to suppress duplicates */ michael@0: if(cand->u.srvrflx.stun_handle){ /* This test because we might have failed before CB registered */ michael@0: nr_ice_socket_deregister(cand->isock,cand->u.srvrflx.stun_handle); michael@0: cand->u.srvrflx.stun_handle=0; michael@0: } michael@0: michael@0: switch(cand->u.srvrflx.stun->state){ michael@0: /* OK, we should have a mapped address */ michael@0: case NR_STUN_CLIENT_STATE_DONE: michael@0: /* Copy the address */ michael@0: nr_transport_addr_copy(&cand->addr, &cand->u.srvrflx.stun->results.stun_binding_response.mapped_addr); michael@0: nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun); michael@0: cand->state=NR_ICE_CAND_STATE_INITIALIZED; michael@0: /* Execute the ready callback */ michael@0: cand->done_cb(0,0,cand->cb_arg); michael@0: break; michael@0: michael@0: /* This failed, so go to the next STUN server if there is one */ michael@0: case NR_STUN_CLIENT_STATE_FAILED: michael@0: ABORT(R_NOT_FOUND); michael@0: break; michael@0: default: michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: _status = 0; michael@0: abort: michael@0: if(_status){ michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: cand->done_cb(0,0,cand->cb_arg); michael@0: } michael@0: } michael@0: michael@0: #ifdef USE_TURN michael@0: static void nr_ice_turn_allocated_cb(NR_SOCKET s, int how, void *cb_arg) michael@0: { michael@0: int r,_status; michael@0: nr_ice_candidate *cand=cb_arg; michael@0: nr_turn_client_ctx *turn=cand->u.relayed.turn; michael@0: char *label; michael@0: nr_transport_addr relay_addr; michael@0: michael@0: switch(turn->state){ michael@0: /* OK, we should have a mapped address */ michael@0: case NR_TURN_CLIENT_STATE_ALLOCATED: michael@0: if (r=nr_turn_client_get_relayed_address(turn, &relay_addr)) michael@0: ABORT(r); michael@0: michael@0: if(r=nr_concat_strings(&label,"turn-relay(",cand->base.as_string,"|", michael@0: relay_addr.as_string,")",NULL)) michael@0: ABORT(r); michael@0: michael@0: r_log(LOG_ICE,LOG_DEBUG,"TURN-CLIENT(%s)/CAND(%s): Switching from TURN to RELAY (%s)",cand->u.relayed.turn->label,cand->label,label); michael@0: michael@0: /* Copy the relayed address into the candidate addr and michael@0: into the candidate base. Note that we need to keep the michael@0: ifname in the base. */ michael@0: if (r=nr_transport_addr_copy(&cand->addr, &relay_addr)) michael@0: ABORT(r); michael@0: if (r=nr_transport_addr_copy_keep_ifname(&cand->base, &relay_addr)) /* Need to keep interface for priority calculation */ michael@0: ABORT(r); michael@0: michael@0: r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): new relay base=%s addr=%s", cand->ctx->label, cand->label, cand->base.as_string, cand->addr.as_string); michael@0: michael@0: RFREE(cand->label); michael@0: cand->label=label; michael@0: cand->state=NR_ICE_CAND_STATE_INITIALIZED; michael@0: michael@0: /* We also need to activate the associated STUN candidate */ michael@0: if(cand->u.relayed.srvflx_candidate){ michael@0: nr_ice_candidate *cand2=cand->u.relayed.srvflx_candidate; michael@0: michael@0: if (r=nr_turn_client_get_mapped_address(cand->u.relayed.turn, &cand2->addr)) michael@0: ABORT(r); michael@0: michael@0: cand2->state=NR_ICE_CAND_STATE_INITIALIZED; michael@0: cand2->done_cb(0,0,cand2->cb_arg); michael@0: } michael@0: michael@0: /* Execute the ready callback */ michael@0: cand->done_cb(0,0,cand->cb_arg); michael@0: cand = 0; michael@0: michael@0: break; michael@0: michael@0: case NR_TURN_CLIENT_STATE_FAILED: michael@0: case NR_TURN_CLIENT_STATE_CANCELLED: michael@0: r_log(NR_LOG_TURN, LOG_WARNING, michael@0: "ICE-CANDIDATE(%s): nr_turn_allocated_cb called with state %d", michael@0: cand->label, turn->state); michael@0: /* This failed, so go to the next TURN server if there is one */ michael@0: ABORT(R_NOT_FOUND); michael@0: break; michael@0: default: michael@0: assert(0); /* should never happen */ michael@0: ABORT(R_INTERNAL); michael@0: } michael@0: michael@0: _status=0; michael@0: abort: michael@0: if(_status){ michael@0: if (cand) { michael@0: r_log(NR_LOG_TURN, LOG_WARNING, michael@0: "ICE-CANDIDATE(%s): nr_turn_allocated_cb failed", cand->label); michael@0: cand->state=NR_ICE_CAND_STATE_FAILED; michael@0: cand->done_cb(0,0,cand->cb_arg); michael@0: michael@0: if(cand->u.relayed.srvflx_candidate){ michael@0: nr_ice_candidate *cand2=cand->u.relayed.srvflx_candidate; michael@0: michael@0: cand2->state=NR_ICE_CAND_STATE_FAILED; michael@0: cand2->done_cb(0,0,cand2->cb_arg); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: #endif /* USE_TURN */ michael@0: michael@0: /* Format the candidate attribute as per ICE S 15.1 */ michael@0: int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int maxlen) michael@0: { michael@0: int r,_status; michael@0: char addr[64]; michael@0: int port; michael@0: int len; michael@0: michael@0: assert(!strcmp(nr_ice_candidate_type_names[HOST], "host")); michael@0: assert(!strcmp(nr_ice_candidate_type_names[RELAYED], "relay")); michael@0: michael@0: if(r=nr_transport_addr_get_addrstring(&cand->addr,addr,sizeof(addr))) michael@0: ABORT(r); michael@0: if(r=nr_transport_addr_get_port(&cand->addr,&port)) michael@0: ABORT(r); michael@0: snprintf(attr,maxlen,"candidate:%s %d UDP %u %s %d typ %s", michael@0: cand->foundation, cand->component_id, cand->priority, addr, port, michael@0: nr_ctype_name(cand->type)); michael@0: michael@0: len=strlen(attr); attr+=len; maxlen-=len; michael@0: michael@0: /* raddr, rport */ michael@0: switch(cand->type){ michael@0: case HOST: michael@0: break; michael@0: case SERVER_REFLEXIVE: michael@0: case PEER_REFLEXIVE: michael@0: if(r=nr_transport_addr_get_addrstring(&cand->base,addr,sizeof(addr))) michael@0: ABORT(r); michael@0: if(r=nr_transport_addr_get_port(&cand->base,&port)) michael@0: ABORT(r); michael@0: michael@0: snprintf(attr,maxlen," raddr %s rport %d",addr,port); michael@0: break; michael@0: case RELAYED: michael@0: // comes from XorMappedAddress via AllocateResponse michael@0: if(r=nr_transport_addr_get_addrstring(&cand->base,addr,sizeof(addr))) michael@0: ABORT(r); michael@0: if(r=nr_transport_addr_get_port(&cand->base,&port)) michael@0: ABORT(r); michael@0: michael@0: snprintf(attr,maxlen," raddr %s rport %d",addr,port); michael@0: break; michael@0: default: michael@0: assert(0); michael@0: ABORT(R_INTERNAL); michael@0: break; michael@0: } michael@0: _status=0; michael@0: abort: michael@0: return(_status); michael@0: } michael@0: