diff options
author | Mamadou DIOP <bossiel@yahoo.fr> | 2015-08-17 01:56:35 +0200 |
---|---|---|
committer | Mamadou DIOP <bossiel@yahoo.fr> | 2015-08-17 01:56:35 +0200 |
commit | 631fffee8a28b1bec5ed1f1d26a20e0135967f99 (patch) | |
tree | 74afe3bf3efe15aa82bcd0272b2b0f4d48c2d837 /tinyNET/src/ice/tnet_ice_ctx.c | |
parent | 7908865936604036e6f200f1b5e069f8752f3a3a (diff) | |
download | doubango-631fffee8a28b1bec5ed1f1d26a20e0135967f99.zip doubango-631fffee8a28b1bec5ed1f1d26a20e0135967f99.tar.gz |
-
Diffstat (limited to 'tinyNET/src/ice/tnet_ice_ctx.c')
-rw-r--r-- | tinyNET/src/ice/tnet_ice_ctx.c | 2883 |
1 files changed, 2883 insertions, 0 deletions
diff --git a/tinyNET/src/ice/tnet_ice_ctx.c b/tinyNET/src/ice/tnet_ice_ctx.c new file mode 100644 index 0000000..94d3042 --- /dev/null +++ b/tinyNET/src/ice/tnet_ice_ctx.c @@ -0,0 +1,2883 @@ +/* + * Copyright (C) 2012-2015 Mamadou DIOP + * Copyright (C) 2012-2015 Doubango Telecom <http://www.doubango.org>. + * + * This file is part of Open Source Doubango Framework. + * + * DOUBANGO 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 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO 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 DOUBANGO. + * + */ + +/**@file tnet_ice_ctx.c + * @brief Interactive Connectivity Establishment (ICE) implementation as per RFC 5245 + * + */ +#include "tnet_ice_ctx.h" +#include "tnet_ice_event.h" +#include "tnet_ice_candidate.h" +#include "tnet_ice_pair.h" +#include "tnet_ice_utils.h" +#include "tnet_utils.h" +#include "tnet_endianness.h" +#include "tnet_transport.h" +#include "tnet_proxydetect.h" + +#include "stun/tnet_stun.h" +#include "stun/tnet_stun_message.h" +#include "stun/tnet_stun_types.h" +#include "turn/tnet_turn_session.h" + +#include "tsk_condwait.h" +#include "tsk_time.h" +#include "tsk_timer.h" +#include "tsk_runnable.h" +#include "tsk_memory.h" +#include "tsk_string.h" +#include "tsk_fsm.h" +#include "tsk_debug.h" + +#include <stdlib.h> +#include <string.h> + +#ifndef LONG_MAX +# define LONG_MAX 2147483647L +#endif + +#if !defined(TNET_ICE_DEBUG_STATE_MACHINE) +# define TNET_ICE_DEBUG_STATE_MACHINE 1 +#endif + +/**@ingroup tnet_nat_group + * Estimate of the round-trip time (RTT) in millisecond. + */ +#define kIceDefaultRTO 500 +/**@ingroup tnet_nat_group + * Number of retransmission for UDP retransmission in millisecond. + * 7.2.1. Sending over UDP + Rc SHOULD be configurable and SHOULD have a default of 7. + */ +#define kIceDefaultRC 4 //7 + +#define kIceDefaultTurnEnabled 0 // Relay candidates +#define kIceDefaultStunEnabled 1 // Reflexive candidates + +#define kIceCandidatesCountMax 40 +#define kIceServersCountMax 10 + +#define kIceConnCheckMinTriesMin 0 +#define kIceConnCheckMinTriesMax 3 + +#define kIcePairsBuildingTimeMax 2500 // maximum time to build pairs + +typedef tsk_list_t tnet_ice_servers_L_t; + +static const char* foundation_default = tsk_null; + +typedef enum tnet_ice_server_proto_e +{ + tnet_ice_server_proto_none = 0x00, + tnet_ice_server_proto_stun = (0x01 << 0), + tnet_ice_server_proto_turn = (0x01 << 1), + tnet_ice_server_proto_all = 0xFF +} +tnet_ice_server_proto_t; + +static int _tnet_ice_ctx_fsm_act(struct tnet_ice_ctx_s* self, tsk_fsm_action_id action_id); +static int _tnet_ice_ctx_signal_async(struct tnet_ice_ctx_s* self, tnet_ice_event_type_t type, const char* phrase); +static int _tnet_ice_ctx_cancel(struct tnet_ice_ctx_s* self, tsk_bool_t silent); +static int _tnet_ice_ctx_restart(struct tnet_ice_ctx_s* self); +static int _tnet_ice_ctx_recv_stun_message_for_pair(struct tnet_ice_ctx_s* self, const struct tnet_ice_pair_s* pair, const void* data, tsk_size_t size, tnet_fd_t local_fd, const struct sockaddr_storage* remote_addr, tsk_bool_t *role_conflict); +static int _tnet_ice_ctx_send_turn_raw(struct tnet_ice_ctx_s* self, struct tnet_turn_session_s* turn_ss, tnet_turn_peer_id_t turn_peer_id, const void* data, tsk_size_t size); +static int _tnet_ice_ctx_build_pairs(struct tnet_ice_ctx_s* self, tnet_ice_candidates_L_t* local_candidates, tnet_ice_candidates_L_t* remote_candidates, tnet_ice_pairs_L_t* result_pairs, tsk_bool_t is_controlling, uint64_t tie_breaker, tsk_bool_t is_ice_jingle, tsk_bool_t is_rtcpmuxed); +static void* TSK_STDCALL _tnet_ice_ctx_run(void* self); + +static int _tnet_ice_ctx_fsm_Started_2_GatheringHostCandidates_X_GatherHostCandidates(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringHostCandidates_2_GatheringHostCandidatesDone_X_Success(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringHostCandidates_2_Terminated_X_Failure(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringHostCandidatesDone_2_GatheringReflexiveCandidates_X_GatherReflexiveCandidates(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringReflexiveCandidates_2_GatheringReflexiveCandidatesDone_X_Success(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringReflexiveCandidates_2_Terminated_X_Failure(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringReflexiveCandidatesDone_2_GatheringRelayCandidates_X_GatherRelayCandidates(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringRelayCandidates_2_GatheringRelayCandidatesDone_X_Success(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringRelayCandidates_2_Terminated_X_Failure(va_list *app); +static int _tnet_ice_ctx_fsm_Any_2_GatheringCompleted_X_GatheringComplet(va_list *app); +static int _tnet_ice_ctx_fsm_Any_2_Started_X_Cancel(va_list *app); +static int _tnet_ice_ctx_fsm_GatheringCompleted_2_ConnChecking_X_ConnCheck(va_list *app); +static int _tnet_ice_ctx_fsm_ConnChecking_2_ConnCheckingCompleted_X_Success(va_list *app); +static int _tnet_ice_ctx_fsm_ConnChecking_2_Terminated_X_Failure(va_list *app); +static int _tnet_ice_ctx_fsm_Any_2_Terminated_X_AnyNotStarted(va_list *app); // Any action if not started + +static int _tnet_ice_ctx_servers_clear(struct tnet_ice_ctx_s* self); +static int _tnet_ice_ctx_server_add(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, + enum tnet_socket_type_e e_transport, + const char* str_server_addr, uint16_t u_server_port, + const char* str_software, + const char* str_username, const char* str_password); +static int _tnet_ice_ctx_server_remove(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, enum tnet_socket_type_e e_transport, const char* str_server_addr, uint16_t u_server_port); +static const struct tnet_ice_server_s* _tnet_ice_ctx_server_find(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, enum tnet_socket_type_e e_transport, const char* str_server_addr, uint16_t u_server_port); +static tsk_bool_t _tnet_ice_ctx_server_exists(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, enum tnet_socket_type_e e_transport, const char* str_server_addr, uint16_t u_server_port); +static tsk_size_t _tnet_ice_ctx_servers_count_by_proto(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto); +static tnet_ice_servers_L_t* _tnet_ice_ctx_servers_copy(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto); + +static int _tnet_ice_ctx_fsm_OnTerminated(struct tnet_ice_ctx_s* self); +static tsk_bool_t _tnet_ice_ctx_fsm_cond_NotStarted(struct tnet_ice_ctx_s* self, const void* _any); +static int _tnet_ice_ctx_turn_callback(const struct tnet_turn_session_event_xs *e); + +typedef struct tnet_ice_server_s +{ + TSK_DECLARE_OBJECT; + + enum tnet_socket_type_e e_transport; + tnet_ice_server_proto_t e_proto; + char* str_server_addr; + uint16_t u_server_port; + struct sockaddr_storage obj_server_addr; + char* str_software; + char* str_username; + char* str_password; + int rto; +} +tnet_ice_server_t; + +static tsk_object_t* tnet_ice_server_ctor(tsk_object_t * self, va_list * app) +{ + tnet_ice_server_t *ice_server = self; + if (ice_server) { + } + return self; +} +static tsk_object_t* tnet_ice_server_dtor(tsk_object_t * self) +{ + tnet_ice_server_t *ice_server = self; + if (ice_server) { + TSK_FREE(ice_server->str_server_addr); + TSK_FREE(ice_server->str_software); + TSK_FREE(ice_server->str_username); + TSK_FREE(ice_server->str_password); + + TSK_DEBUG_INFO("*** ICE server destroyed ***"); + } + return self; +} +static const tsk_object_def_t tnet_ice_server_def_s = +{ + sizeof(tnet_ice_server_t), + tnet_ice_server_ctor, + tnet_ice_server_dtor, + tsk_null, +}; + +static tnet_ice_server_t* tnet_ice_server_create( + enum tnet_ice_server_proto_e e_proto, + enum tnet_socket_type_e e_transport, + const char* str_server_addr, uint16_t u_server_port, + const char* str_software, + const char* str_username, const char* str_password) +{ + tnet_ice_server_t *ice_server; + struct sockaddr_storage obj_server_addr; + + if (tsk_strnullORempty(str_server_addr) || !u_server_port) { + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + if (tnet_sockaddr_init(str_server_addr, u_server_port, e_transport, &obj_server_addr) != 0) { + TSK_DEBUG_ERROR("Invalid server address (host=%s, port=%d, transport=%d)", str_server_addr, u_server_port, e_transport); + return tsk_null; + } + + if ((ice_server = tsk_object_new(&tnet_ice_server_def_s))) { + ice_server->e_proto = e_proto; + ice_server->e_transport = e_transport; + tsk_strupdate(&ice_server->str_server_addr, str_server_addr); + ice_server->u_server_port = u_server_port; + tsk_strupdate(&ice_server->str_software, str_software); + tsk_strupdate(&ice_server->str_username, str_username); + tsk_strupdate(&ice_server->str_password, str_password); + memcpy(&ice_server->obj_server_addr, &obj_server_addr, sizeof(struct sockaddr_storage)); + } + return ice_server; +} + +typedef struct tnet_ice_ctx_s +{ + TSK_DECLARE_RUNNABLE; + + tsk_bool_t is_started; + tsk_bool_t is_active; + tsk_bool_t is_sync_mode; + tsk_bool_t is_silent_mode; + tnet_ice_callback_f callback; + const void* userdata; + tsk_bool_t use_ipv6; + tsk_bool_t use_rtcp; + tsk_bool_t use_rtcpmux; + tsk_bool_t is_video; + tsk_bool_t is_building_pairs; + tsk_bool_t unicast; + tsk_bool_t anycast; + tsk_bool_t multicast; + + tsk_bool_t is_connchecking; + tsk_bool_t is_controlling; + tsk_bool_t is_ice_jingle; + tsk_bool_t is_turn_enabled; + tsk_bool_t is_stun_enabled; + uint64_t tie_breaker; + uint64_t concheck_timeout; + + const void* rtp_callback_data; + tnet_ice_rtp_callback_f rtp_callback; + + tnet_ice_servers_L_t *servers; + + char* ufrag; + char* pwd; + + tsk_timer_manager_handle_t* h_timer_mgr; + + tsk_fsm_t* fsm; + + tsk_condwait_handle_t* condwait_pairs; + tnet_ice_candidates_L_t* candidates_local; + tnet_ice_candidates_L_t* candidates_remote; + tnet_ice_pairs_L_t* candidates_pairs; + tsk_bool_t have_nominated_offer; + tsk_bool_t have_nominated_answer; + tsk_bool_t have_nominated_symetric; /**< Whether symetic RTP has been negotiated */ + + uint16_t RTO; /**< Estimate of the round-trip time (RTT) in millisecond */ + uint16_t Rc; /**< Number of retransmissions for UDP in millisecond */ + + struct { + char* path_priv; + char* path_pub; + char* path_ca; + tsk_bool_t verify; + } ssl; + + struct { + tsk_bool_t auto_detect; + struct tnet_proxyinfo_s* info; + } + proxy; + + struct { + tsk_condwait_handle_t* condwait; + struct tnet_turn_session_s* ss_nominated_rtp; + tnet_turn_peer_id_t peer_id_rtp; + struct tnet_turn_session_s* ss_nominated_rtcp; + tnet_turn_peer_id_t peer_id_rtcp; + } turn; + + TSK_DECLARE_SAFEOBJ; +} +tnet_ice_ctx_t; + +typedef struct tnet_ice_action_s +{ + TSK_DECLARE_OBJECT; + + tsk_fsm_action_id id; +} +tnet_ice_action_t; + +typedef enum _fsm_state_e +{ + _fsm_state_Started, + _fsm_state_GatheringHostCandidates, + _fsm_state_GatheringHostCandidatesDone, + _fsm_state_GatheringReflexiveCandidates, + _fsm_state_GatheringReflexiveCandidatesDone, + _fsm_state_GatheringRelayCandidates, + _fsm_state_GatheringRelayCandidatesDone, + _fsm_state_GatheringCompleted, + _fsm_state_ConnChecking, + _fsm_state_ConnCheckingCompleted, + _fsm_state_Terminated +} +_fsm_state_t; + +typedef enum _fsm_action_e +{ + _fsm_action_Success, + _fsm_action_Failure, + _fsm_action_GatherHostCandidates, + _fsm_action_GatherReflexiveCandidates, + _fsm_action_GatherRelayCandidates, + _fsm_action_GatheringComplet, + _fsm_action_ConnCheck, + _fsm_action_Cancel, + _fsm_action_Error, +} +_fsm_action_t; + +static tsk_object_t* tnet_ice_action_ctor(tsk_object_t * self, va_list * app) +{ + tnet_ice_action_t *action = self; + if (action){ + } + return self; +} +static tsk_object_t* tnet_ice_action_dtor(tsk_object_t * self) +{ + tnet_ice_action_t *action = self; + if (action){ + } + return self; +} +static const tsk_object_def_t tnet_ice_action_def_s = +{ + sizeof(tnet_ice_action_t), + tnet_ice_action_ctor, + tnet_ice_action_dtor, + tsk_null, +}; +static tnet_ice_action_t* tnet_ice_action_create(tsk_fsm_action_id id) +{ + tnet_ice_action_t *action = tsk_object_new(&tnet_ice_action_def_s); + if (action){ + action->id = id; + } + return action; +} + + + + +static tsk_object_t* tnet_ice_ctx_ctor(tsk_object_t * self, va_list * app) +{ + tnet_ice_ctx_t *ctx = self; + if (ctx){ + tsk_safeobj_init(ctx); + + if (!(ctx->h_timer_mgr = tsk_timer_manager_create())){ + TSK_DEBUG_ERROR("Failed to create timer manager"); + return tsk_null; + } + if (!(ctx->fsm = tsk_fsm_create(_fsm_state_Started, _fsm_state_Terminated))){ + TSK_DEBUG_ERROR("Failed to create state machine"); + return tsk_null; + } + if (!(ctx->candidates_local = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create candidates list"); + return tsk_null; + } + if (!(ctx->candidates_remote = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create candidates list"); + return tsk_null; + } + if (!(ctx->candidates_pairs = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create candidates list"); + return tsk_null; + } + + // Create condwait for pairs + if (!(ctx->condwait_pairs = tsk_condwait_create())) { + TSK_DEBUG_ERROR("Failed to create condwait for pairs"); + return tsk_null; + } + + // Create list objects to hold the servers + if (!(ctx->servers = tsk_list_create())){ + TSK_DEBUG_ERROR("Failed to create server list"); + return tsk_null; + } + + tsk_runnable_set_important(TSK_RUNNABLE(self), tsk_false); + + /* 7.2.1. Sending over UDP + In fixed-line access links, a value of 500 ms is RECOMMENDED. + */ + ctx->RTO = kIceDefaultRTO; + + /* 7.2.1. Sending over UDP + Rc SHOULD be configurable and SHOULD have a default of 7. + */ + ctx->Rc = kIceDefaultRC; + + ctx->tie_breaker = ((tsk_time_now() << 32) ^ tsk_time_now()); + ctx->is_ice_jingle = tsk_false; + ctx->is_stun_enabled = kIceDefaultStunEnabled; + ctx->is_turn_enabled = kIceDefaultTurnEnabled; + + ctx->concheck_timeout = LONG_MAX; + } + return self; +} +static tsk_object_t* tnet_ice_ctx_dtor(tsk_object_t * self) +{ + tnet_ice_ctx_t *ctx = self; + if (ctx){ + tnet_ice_ctx_stop(ctx); + if (ctx->h_timer_mgr){ + tsk_timer_manager_destroy(&ctx->h_timer_mgr); + } + + TSK_OBJECT_SAFE_FREE(ctx->fsm); + TSK_OBJECT_SAFE_FREE(ctx->candidates_local); + TSK_OBJECT_SAFE_FREE(ctx->candidates_remote); + TSK_OBJECT_SAFE_FREE(ctx->candidates_pairs); + + TSK_OBJECT_SAFE_FREE(ctx->turn.ss_nominated_rtp); + TSK_OBJECT_SAFE_FREE(ctx->turn.ss_nominated_rtcp); + if (ctx->turn.condwait) { + tsk_condwait_destroy(&ctx->turn.condwait); + } + if (ctx->condwait_pairs) { + tsk_condwait_destroy(&ctx->condwait_pairs); + } + TSK_OBJECT_SAFE_FREE(ctx->servers); + + TSK_OBJECT_SAFE_FREE(ctx->proxy.info); + + TSK_FREE(ctx->ssl.path_priv); + TSK_FREE(ctx->ssl.path_pub); + TSK_FREE(ctx->ssl.path_ca); + + tsk_safeobj_deinit(ctx); + } + TSK_DEBUG_INFO("*** ICE context destroyed ***"); + return self; +} +static const tsk_object_def_t tnet_ice_ctx_def_s = +{ + sizeof(tnet_ice_ctx_t), + tnet_ice_ctx_ctor, + tnet_ice_ctx_dtor, + tsk_null, +}; + + +tnet_ice_ctx_t* tnet_ice_ctx_create(tsk_bool_t is_ice_jingle, tsk_bool_t use_ipv6, tsk_bool_t use_rtcp, tsk_bool_t is_video, tnet_ice_callback_f callback, const void* userdata) +{ + tnet_ice_ctx_t* ctx; + + if (!(ctx = tsk_object_new(&tnet_ice_ctx_def_s))){ + TSK_DEBUG_ERROR("Failed to create ICE context object"); + return tsk_null; + } + + ctx->is_ice_jingle = is_ice_jingle; + ctx->use_ipv6 = use_ipv6; + ctx->use_rtcp = use_rtcp; + ctx->is_video = is_video; + ctx->callback = callback; + ctx->userdata = userdata; + ctx->unicast = tsk_true; + ctx->anycast = tsk_false; + ctx->multicast = tsk_false; + + tnet_ice_utils_set_ufrag(&ctx->ufrag); + tnet_ice_utils_set_pwd(&ctx->pwd); + + ctx->fsm->debug = TNET_ICE_DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(ctx->fsm, TSK_FSM_ONTERMINATED_F(_tnet_ice_ctx_fsm_OnTerminated), (const void*)ctx); + tsk_fsm_set(ctx->fsm, + // (Started) -> (GatherHostCandidates) -> (GatheringHostCandidates) + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_GatherHostCandidates, _fsm_state_GatheringHostCandidates, _tnet_ice_ctx_fsm_Started_2_GatheringHostCandidates_X_GatherHostCandidates, "ICE_Started_2_GatheringHostCandidates_X_GatherHostCandidates"), + // (GatheringHostCandidates) -> (Success) -> (GatheringHostCandidatesDone) + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringHostCandidates, _fsm_action_Success, _fsm_state_GatheringHostCandidatesDone, _tnet_ice_ctx_fsm_GatheringHostCandidates_2_GatheringHostCandidatesDone_X_Success, "ICE_GatheringHostCandidates_2_GatheringHostCandidatesDone_X_Success"), + // (GatheringHostCandidates) -> (Failure) -> (Terminated) + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringHostCandidates, _fsm_action_Failure, _fsm_state_Terminated, _tnet_ice_ctx_fsm_GatheringHostCandidates_2_Terminated_X_Failure, "ICE_GatheringHostCandidates_2_Terminated_X_Failure"), + + // (GatheringHostCandidatesDone) -> (GatherReflexiveCandidates) -> (GatheringReflexiveCandidates) + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringHostCandidatesDone, _fsm_action_GatherReflexiveCandidates, _fsm_state_GatheringReflexiveCandidates, _tnet_ice_ctx_fsm_GatheringHostCandidatesDone_2_GatheringReflexiveCandidates_X_GatherReflexiveCandidates, "ICE_GatheringHostCandidatesDone_2_GatheringReflexiveCandidates_X_GatherReflexiveCandidates"), + // (GatheringReflexiveCandidates) -> (Success) -> GatheringReflexiveCandidatesDone + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringReflexiveCandidates, _fsm_action_Success, _fsm_state_GatheringReflexiveCandidatesDone, _tnet_ice_ctx_fsm_GatheringReflexiveCandidates_2_GatheringReflexiveCandidatesDone_X_Success, "ICE_fsm_GatheringReflexiveCandidates_2_GatheringReflexiveCandidatesDone_X_Success"), + // (GatheringReflexiveCandidates) -> (Failure) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringReflexiveCandidates, _fsm_action_Failure, _fsm_state_Terminated, _tnet_ice_ctx_fsm_GatheringReflexiveCandidates_2_Terminated_X_Failure, "ICE_GatheringReflexiveCandidates_2_Terminated_X_Failure"), + + // (GatheringReflexiveCandidatesDone) -> (GatherRelayCandidates) -> (GatheringRelayCandidates) + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringReflexiveCandidatesDone, _fsm_action_GatherRelayCandidates, _fsm_state_GatheringRelayCandidates, _tnet_ice_ctx_fsm_GatheringReflexiveCandidatesDone_2_GatheringRelayCandidates_X_GatherRelayCandidates, "ICE_GatheringReflexiveCandidatesDone_2_GatheringRelayCandidates_X_GatherRelayCandidates"), + // (GatheringHostCandidatesDone) -> (GatherRelayCandidates) -> (GatheringRelayCandidates) + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringHostCandidatesDone, _fsm_action_GatherRelayCandidates, _fsm_state_GatheringRelayCandidates, _tnet_ice_ctx_fsm_GatheringReflexiveCandidatesDone_2_GatheringRelayCandidates_X_GatherRelayCandidates, "ICE_GatheringHostCandidatesDone_2_GatheringRelayCandidates_X_GatherRelayCandidates"), + // (GatheringRelayCandidates) -> (Success) -> GatheringRelayCandidatesDone + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringRelayCandidates, _fsm_action_Success, _fsm_state_GatheringRelayCandidatesDone, _tnet_ice_ctx_fsm_GatheringRelayCandidates_2_GatheringRelayCandidatesDone_X_Success, "ICE_fsm_GatheringRelayCandidates_2_GatheringRelayCandidatesDone_X_Success"), + // (GatheringRelayCandidates) -> (Failure) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringRelayCandidates, _fsm_action_Failure, _fsm_state_Terminated, _tnet_ice_ctx_fsm_GatheringRelayCandidates_2_Terminated_X_Failure, "ICE_GatheringRelayCandidates_2_Terminated_X_Failure"), + + // (GatheringComplet) -> (ConnCheck) -> ConnChecking + TSK_FSM_ADD_ALWAYS(_fsm_state_GatheringCompleted, _fsm_action_ConnCheck, _fsm_state_ConnChecking, _tnet_ice_ctx_fsm_GatheringCompleted_2_ConnChecking_X_ConnCheck, "ICE_GatheringCompleted_2_ConnChecking_X_ConnCheck"), + // (ConnChecking) -> (Success) -> ConnCheckingCompleted + TSK_FSM_ADD_ALWAYS(_fsm_state_ConnChecking, _fsm_action_Success, _fsm_state_ConnCheckingCompleted, _tnet_ice_ctx_fsm_ConnChecking_2_ConnCheckingCompleted_X_Success, "ICE_ConnChecking_2_ConnCheckingCompleted_X_Success"), + // (ConnChecking) -> (Failure) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_ConnChecking, _fsm_action_Failure, _fsm_state_Terminated, _tnet_ice_ctx_fsm_ConnChecking_2_Terminated_X_Failure, "ICE_ConnChecking_2_Terminated_X_Failure"), + + // (Any) -> (GatheringComplet) -> GatheringCompleted + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_GatheringComplet, _fsm_state_GatheringCompleted, _tnet_ice_ctx_fsm_Any_2_GatheringCompleted_X_GatheringComplet, "ICE_Any_2_GatheringCompleted_X_GatheringComplet"), + // (Any) -> (Cancel) -> Started + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_Cancel, _fsm_state_Started, _tnet_ice_ctx_fsm_Any_2_Started_X_Cancel, "ICE_Any_2_Started_X_Cancel"), + // (Any) -> (AnyNotStarted) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, tsk_fsm_action_any, _tnet_ice_ctx_fsm_cond_NotStarted, _fsm_state_Terminated, _tnet_ice_ctx_fsm_Any_2_Terminated_X_AnyNotStarted, "ICE_fsm_Any_2_Terminated_X_AnyNotStarted") + ); + + return ctx; +} + +int tnet_ice_ctx_set_userdata(tnet_ice_ctx_t* self, const void* userdata) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->userdata = userdata; + return 0; +} + +// @deprecated: use "tnet_ice_ctx_add_server()" +int tnet_ice_ctx_set_stun( + tnet_ice_ctx_t* self, + const char* server_addr, + uint16_t server_port, + const char* software, + const char* username, + const char* password) +{ + _tnet_ice_ctx_servers_clear(self); + return tnet_ice_ctx_add_server( + self, + "udp", + server_addr, + server_port, + (!tsk_strnullORempty(username) && !tsk_strnullORempty(password)), /* use_turn*/ + tsk_true, /* use_stun*/ + username, + password); +} + +int tnet_ice_ctx_add_server( + struct tnet_ice_ctx_s* self, + const char* transport_proto, // "udp", "tcp", "tls", "ws", "wss" + const char* server_addr, + uint16_t server_port, + tsk_bool_t use_turn, + tsk_bool_t use_stun, + const char* username, + const char* password) +{ + tnet_socket_type_t socket_type; + tnet_ice_server_proto_t e_proto = tnet_ice_server_proto_none; + if (!self || tsk_strnullORempty(server_addr) || !server_port) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if (!use_turn && !use_stun) { + TSK_DEBUG_ERROR("'use_stun' or 'use_turn' must be true"); + return -1; + } + if (use_stun) { + e_proto |= tnet_ice_server_proto_stun; + } + if (use_turn) { + e_proto |= tnet_ice_server_proto_turn; + } + + if (tsk_striequals(transport_proto, "udp")) { + socket_type = self->use_ipv6 ? tnet_socket_type_udp_ipv6 : tnet_socket_type_udp_ipv4; + } + else if (tsk_striequals(transport_proto, "tcp")) { + socket_type = self->use_ipv6 ? tnet_socket_type_tcp_ipv6 : tnet_socket_type_tcp_ipv4; + } + else if (tsk_striequals(transport_proto, "tls")) { + socket_type = self->use_ipv6 ? tnet_socket_type_tls_ipv6 : tnet_socket_type_tls_ipv4; + } + else if (tsk_striequals(transport_proto, "ws")) { + socket_type = self->use_ipv6 ? tnet_socket_type_ws_ipv6 : tnet_socket_type_ws_ipv4; + } + else if (tsk_striequals(transport_proto, "wss")) { + socket_type = self->use_ipv6 ? tnet_socket_type_wss_ipv6 : tnet_socket_type_wss_ipv4; + } + else { + TSK_DEBUG_ERROR("'%s' not a valid transport proto", transport_proto); + return -1; + } + return _tnet_ice_ctx_server_add(self, e_proto, + socket_type, server_addr, server_port, + kStunSoftware, + username, password); +} + +int tnet_ice_ctx_set_sync_mode(tnet_ice_ctx_t* self, tsk_bool_t sync_mode) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->is_sync_mode = sync_mode; + return 0; +} + +int tnet_ice_ctx_set_silent_mode(struct tnet_ice_ctx_s* self, tsk_bool_t silent_mode) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->is_silent_mode = silent_mode; + return 0; +} + +// Whether to gather reflexive candidates +int tnet_ice_ctx_set_stun_enabled(struct tnet_ice_ctx_s* self, tsk_bool_t stun_enabled) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->is_stun_enabled = stun_enabled; + return 0; +} + +// Whether to gather relay candidates +int tnet_ice_ctx_set_turn_enabled(struct tnet_ice_ctx_s* self, tsk_bool_t turn_enabled) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->is_turn_enabled = turn_enabled; + return 0; +} + +int tnet_ice_ctx_start(tnet_ice_ctx_t* self) +{ + int ret; + tsk_bool_t timer_mgr_started = tsk_false; + tsk_bool_t runnable_started = tsk_false; + const char* err = tsk_null; + + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(self); + + TSK_DEBUG_INFO("tnet_ice_ctx_start"); + + if (self->is_started){ + ret = 0; + if (!self->is_active){ + TSK_DEBUG_INFO("ICE restart"); + ret = _tnet_ice_ctx_restart(self); + } + TSK_DEBUG_INFO("ICE already started"); + tsk_safeobj_unlock(self); + return ret; + } + + /* === Timer manager === */ + if ((ret = tsk_timer_manager_start(self->h_timer_mgr))){ + err = "Failed to start timer manager"; + TSK_DEBUG_ERROR("%s", err); + goto bail; + } + timer_mgr_started = tsk_true; + + /* === Runnable === */ + TSK_RUNNABLE(self)->run = _tnet_ice_ctx_run; + if ((ret = tsk_runnable_start(TSK_RUNNABLE(self), tnet_ice_event_def_t))){ + err = "Failed to start runnable"; + TSK_DEBUG_ERROR("%s", err); + goto bail; + } + runnable_started = tsk_true; + + self->is_started = tsk_true; // needed by FSM -> "Must" be before fsm_ast() + self->is_active = tsk_true; + + if ((ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_GatherHostCandidates))){ + err = "FSM execution failed"; + TSK_DEBUG_ERROR("%s", err); + goto bail; + } + +bail: + tsk_safeobj_unlock(self); + + if (ret){ + _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_start_failed, err); + if (timer_mgr_started){ + tsk_timer_manager_stop(self->h_timer_mgr); + } + if (runnable_started){ + tsk_runnable_stop(TSK_RUNNABLE(self)); + } + self->is_started = tsk_false; + self->is_active = tsk_false; + } + return ret; +} + +// register callback to call when we receive early RTP packets while negotaiating ICE pairs +int tnet_ice_ctx_rtp_callback(tnet_ice_ctx_t* self, tnet_ice_rtp_callback_f rtp_callback, const void* rtp_callback_data) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->rtp_callback_data = rtp_callback_data; + self->rtp_callback = rtp_callback; + return 0; +} + +// timeout (millis): <=0 to disable +int tnet_ice_ctx_set_concheck_timeout(tnet_ice_ctx_t* self, int64_t timeout) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->concheck_timeout = (timeout <= 0 ? LONG_MAX : timeout); + + return 0; +} + +// @param candidates (candidate \r\n)+ +int tnet_ice_ctx_set_remote_candidates_2(struct tnet_ice_ctx_s* self, const char* candidates, const char* ufrag, const char* pwd, tsk_bool_t is_controlling, tsk_bool_t is_ice_jingle, tsk_bool_t use_rtcpmux) +{ + int ret = 0; + char *v, *copy, *saveptr; + tsk_size_t size, idx = 0; + tsk_bool_t exists; + tnet_ice_candidate_t* candidate; + tsk_strings_L_t *added_candidates = tsk_null; + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->is_controlling = is_controlling; + self->is_ice_jingle = is_ice_jingle; + tnet_ice_ctx_set_rtcpmux(self, use_rtcpmux); + + if (tsk_strnullORempty(candidates)) { + // remote party is ICE-lite or doesn't support ICE + return tnet_ice_ctx_cancel(self); + } + + TSK_DEBUG_INFO("tnet_ice_ctx_set_remote_candidates(ufrag=%s, pwd=%s, is_controlling=%d, is_ice_jingle=%d, use_rtcpmux=%d)", + ufrag, pwd, is_controlling, is_ice_jingle, use_rtcpmux); + + tsk_list_lock(self->candidates_pairs); + if (!TSK_LIST_IS_EMPTY(self->candidates_pairs)) { + TSK_DEBUG_WARN("Adding Remote ICE candidates after pairs building"); + } + tsk_list_unlock(self->candidates_pairs); + + // active if remote is full-ICE + // in all case we are always full-ICE + // self->is_active = tsk_true; + + tsk_list_lock(self->candidates_remote); + + // clear old candidates + tsk_list_clear_items(self->candidates_remote); + + copy = tsk_strdup(candidates); + size = (tsk_size_t)tsk_strlen(copy); + do { + v = tsk_strtok_r(©[idx], "\r\n", &saveptr); + idx += tsk_strlen(v) + 2; + if (v && (candidate = tnet_ice_candidate_parse(v))) { + exists = tsk_false; + if (!added_candidates) { + added_candidates = tsk_list_create(); + } + if (ufrag && pwd) { + tnet_ice_candidate_set_credential(candidate, ufrag, pwd); + } + if (added_candidates) { + tsk_string_t* str_cand = tsk_string_create(tnet_ice_candidate_tostring(candidate)); + if (str_cand) { + if ((exists = !!tsk_list_find_object_by_data(added_candidates, str_cand))) { + TSK_DEBUG_INFO("Remote candidate [[%s]] is duplicated ...skipping", str_cand->value); + } + else { + tsk_list_push_back_data(added_candidates, (void**)&str_cand); + } + TSK_OBJECT_SAFE_FREE(str_cand); + } + } + if (!exists) { + tsk_list_push_descending_data(self->candidates_remote, (void**)&candidate); + } + TSK_OBJECT_SAFE_FREE(candidate); + } + } while (v && (idx < size)); + + tsk_list_unlock(self->candidates_remote); + + TSK_FREE(copy); + TSK_OBJECT_SAFE_FREE(added_candidates); + + if (!tnet_ice_ctx_is_connected(self) && tnet_ice_ctx_got_local_candidates(self) && !TSK_LIST_IS_EMPTY(self->candidates_remote)) { + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_ConnCheck); + } + return ret; +} + +// @param candidates (candidate \r\n)+ +int tnet_ice_ctx_set_remote_candidates(tnet_ice_ctx_t* self, const char* candidates, const char* ufrag, const char* pwd, tsk_bool_t is_controlling, tsk_bool_t is_ice_jingle) +{ + return tnet_ice_ctx_set_remote_candidates_2(self, candidates, ufrag, pwd, is_controlling, is_ice_jingle, self->use_rtcpmux); +} + +int tnet_ice_ctx_set_rtcpmux(tnet_ice_ctx_t* self, tsk_bool_t use_rtcpmux) +{ + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if (self->is_connchecking && self->use_rtcpmux != use_rtcpmux) { + TSK_DEBUG_WARN("use_rtcpmux changed(%d->%d) while connchecking", self->use_rtcpmux, use_rtcpmux); + } + self->use_rtcpmux = use_rtcpmux; + return 0; +} + +int tnet_ice_ctx_set_ssl_certs(struct tnet_ice_ctx_s* self, const char* path_priv, const char* path_pub, const char* path_ca, tsk_bool_t verify) +{ + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + tsk_strupdate(&self->ssl.path_priv, path_priv); + tsk_strupdate(&self->ssl.path_pub, path_pub); + tsk_strupdate(&self->ssl.path_ca, path_ca); + self->ssl.verify = verify; + return 0; +} + +tsk_size_t tnet_ice_ctx_count_local_candidates(const tnet_ice_ctx_t* self) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 0; + } + return tsk_list_count(self->candidates_local, tsk_null, tsk_null); +} + +tsk_bool_t tnet_ice_ctx_got_local_candidates(const tnet_ice_ctx_t* self) +{ + tsk_fsm_state_id curr_state; + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_false; + } + if (!self->is_started){ + return tsk_false; + } + + curr_state = tsk_fsm_get_current_state(self->fsm); + + return (curr_state >= _fsm_state_GatheringCompleted && curr_state < _fsm_state_Terminated); +} + +const tnet_ice_candidate_t* tnet_ice_ctx_get_local_candidate_at(const tnet_ice_ctx_t* self, tsk_size_t index) +{ + const tsk_list_item_t *item; + tsk_size_t pos = 0; + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + + tsk_list_foreach(item, self->candidates_local){ + if (pos++ == index){ + return (const tnet_ice_candidate_t*)item->data; + } + } + return tsk_null; +} + +tsk_bool_t tnet_ice_ctx_is_started(const tnet_ice_ctx_t* self) +{ + return (self && self->is_started); +} + +// says if ICE is enabled +// doesn't say if the connection has been negotiated (see is_connecte()) +tsk_bool_t tnet_ice_ctx_is_active(const tnet_ice_ctx_t* self) +{ + return (self && self->is_started && self->is_active); +} + +tsk_bool_t tnet_ice_ctx_is_turn_rtp_active(const struct tnet_ice_ctx_s* self) +{ + tsk_bool_t b_active; + return tnet_ice_ctx_is_active(self) + && self->turn.ss_nominated_rtp + && tnet_turn_session_is_active(self->turn.ss_nominated_rtp, self->turn.peer_id_rtp, &b_active) == 0 + && b_active; +} + +tsk_bool_t tnet_ice_ctx_is_turn_rtcp_active(const struct tnet_ice_ctx_s* self) +{ + if (self->use_rtcpmux) { + return tnet_ice_ctx_is_turn_rtp_active(self); + } + else { + tsk_bool_t b_active; + return tnet_ice_ctx_is_active(self) + && self->turn.ss_nominated_rtcp + && tnet_turn_session_is_active(self->turn.ss_nominated_rtcp, self->turn.peer_id_rtcp, &b_active) == 0 + && b_active; + } +} + +// says if media can start in both direction +tsk_bool_t tnet_ice_ctx_is_connected(const tnet_ice_ctx_t* self) +{ + return (self && self->have_nominated_symetric); +} + +tsk_bool_t tnet_ice_ctx_is_can_send(const tnet_ice_ctx_t* self) +{ + return (self && self->have_nominated_offer); +} + +tsk_bool_t tnet_ice_ctx_is_can_recv(const tnet_ice_ctx_t* self) +{ + return (self && self->have_nominated_answer); +} + +tsk_bool_t tnet_ice_ctx_use_ipv6(const tnet_ice_ctx_t* self) +{ + return (self && self->use_ipv6); +} + +tsk_bool_t tnet_ice_ctx_use_rtcp(const tnet_ice_ctx_t* self) +{ + return (self && self->use_rtcp); +} + +int tnet_ice_ctx_get_nominated_symetric_candidates(const tnet_ice_ctx_t* self, uint32_t comp_id, + const tnet_ice_candidate_t** candidate_offer, + const tnet_ice_candidate_t** candidate_answer_src, + const tnet_ice_candidate_t** candidate_answer_dest) +{ + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + return tnet_ice_pairs_get_nominated_symetric_candidates(self->candidates_pairs, comp_id, candidate_offer, candidate_answer_src, candidate_answer_dest); +} + +int tnet_ice_ctx_recv_stun_message(tnet_ice_ctx_t* self, const void* data, tsk_size_t size, tnet_fd_t local_fd, const struct sockaddr_storage* remote_addr, tsk_bool_t *role_conflict) +{ + static const tnet_ice_pair_t* kNullPair = tsk_null; // means seach for the pair using local_fd and remote_addr + return _tnet_ice_ctx_recv_stun_message_for_pair(self, kNullPair, data, size, local_fd, remote_addr, role_conflict); +} + +int tnet_ice_ctx_send_turn_rtp(struct tnet_ice_ctx_s* self, const void* data, tsk_size_t size) +{ + return _tnet_ice_ctx_send_turn_raw(self, self->turn.ss_nominated_rtp, self->turn.peer_id_rtp, data, size); +} + +int tnet_ice_ctx_send_turn_rtcp(struct tnet_ice_ctx_s* self, const void* data, tsk_size_t size) +{ + return self->use_rtcpmux + ? tnet_ice_ctx_send_turn_rtp(self, data, size) + : _tnet_ice_ctx_send_turn_raw(self, self->turn.ss_nominated_rtcp, self->turn.peer_id_rtcp, data, size); +} + +int tnet_ice_ctx_turn_get_bytes_count(const struct tnet_ice_ctx_s* self, uint64_t* bytes_in, uint64_t* bytes_out) +{ + int ret; + + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + ret = tnet_turn_session_get_bytes_count(self->turn.ss_nominated_rtp, bytes_in, bytes_out); + if (ret == 0 && !self->use_rtcpmux) { + uint64_t _bytes_in, _bytes_out; + ret = tnet_turn_session_get_bytes_count(self->turn.ss_nominated_rtcp, &_bytes_in, &_bytes_out); + if (ret == 0) { + if (bytes_in) *bytes_in += _bytes_in; + if (bytes_out) *bytes_out += _bytes_out; + } + } + return ret; +} + +const char* tnet_ice_ctx_get_ufrag(const struct tnet_ice_ctx_s* self) +{ + return (self && self->ufrag) ? self->ufrag : tsk_null; +} + +const char* tnet_ice_ctx_get_pwd(const struct tnet_ice_ctx_s* self) +{ + return (self && self->pwd) ? self->pwd : tsk_null; +} + +int tnet_ice_ctx_set_proxy_auto_detect(struct tnet_ice_ctx_s* self, tsk_bool_t auto_detect) +{ + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->proxy.auto_detect = auto_detect; + return 0; +} + +int tnet_ice_ctx_set_proxy_info(struct tnet_ice_ctx_s* self, enum tnet_proxy_type_e type, const char* host, tnet_port_t port, const char* login, const char* password) +{ + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if (!self->proxy.info && !(self->proxy.info = tnet_proxyinfo_create())) { + return -2; + } + self->proxy.info->type = type; + self->proxy.info->port = port; + tsk_strupdate(&self->proxy.info->hostname, host); + tsk_strupdate(&self->proxy.info->username, login); + tsk_strupdate(&self->proxy.info->password, password); + return 0; +} + +// cancels the ICE processing without stopping the process +int tnet_ice_ctx_cancel(tnet_ice_ctx_t* self) +{ + int ret; + + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(self); + if (tsk_fsm_get_current_state(self->fsm) == _fsm_state_Started) { + // Do nothing if already in the "started" state + ret = 0; + goto bail; + } + + self->is_active = tsk_false; + self->have_nominated_symetric = tsk_false; + self->have_nominated_answer = tsk_false; + self->have_nominated_offer = tsk_false; + tsk_condwait_broadcast(self->condwait_pairs); + if (self->turn.condwait) { + ret = tsk_condwait_broadcast(self->turn.condwait); + } + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Cancel); + +bail: + tsk_safeobj_unlock(self); + return ret; +} + +int tnet_ice_ctx_stop(tnet_ice_ctx_t* self) +{ + int ret; + + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + tsk_safeobj_lock(self); + if (!self->is_started) { + ret = 0; + goto bail; + } + + self->is_started = tsk_false; + tsk_condwait_broadcast(self->condwait_pairs); + if (self->turn.condwait) { + ret = tsk_condwait_broadcast(self->turn.condwait); + } + ret = tsk_timer_manager_stop(self->h_timer_mgr); + ret = tsk_runnable_stop(TSK_RUNNABLE(self)); + +bail: + tsk_safeobj_unlock(self); + return ret; +} + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +// Started -> (GatherHostCandidates) -> (GatheringHostCandidates) +static int _tnet_ice_ctx_fsm_Started_2_GatheringHostCandidates_X_GatherHostCandidates(va_list *app) +{ + int ret = 0; + tnet_ice_ctx_t* self; + tnet_addresses_L_t* addresses; + const tsk_list_item_t *item; + const tnet_address_t* address; + tnet_ice_candidate_t* candidate; + tnet_socket_t* socket_rtp = tsk_null; + tnet_socket_t* socket_rtcp = tsk_null; + tnet_socket_type_t socket_type; + uint16_t local_pref, curr_local_pref; + tnet_ip_t best_local_ip; + tsk_bool_t check_best_local_ip; + static const tsk_bool_t dnsserver = tsk_false; + static const long if_index_any = -1; // any interface + static const char* destination = "doubango.org"; + + self = va_arg(*app, tnet_ice_ctx_t *); + socket_type = self->use_ipv6 ? tnet_socket_type_udp_ipv6 : tnet_socket_type_udp_ipv4; + + addresses = tnet_get_addresses((self->use_ipv6 ? AF_INET6 : AF_INET), self->unicast, self->anycast, self->multicast, dnsserver, if_index_any); + if (!addresses || TSK_LIST_IS_EMPTY(addresses)){ + TSK_DEBUG_ERROR("Failed to get addresses"); + ret = -1; + goto bail; + } + + + check_best_local_ip = (tnet_getbestsource(destination, 5060, socket_type, &best_local_ip) == 0); + curr_local_pref = local_pref = check_best_local_ip ? 0xFFFE : 0xFFFF; + + // lock-list + tsk_list_lock(self->candidates_local); + // clear-list + tsk_list_clear_items(self->candidates_local); + + tsk_list_foreach(item, addresses){ + if (!(address = item->data)){ + continue; + } + + // Skip loopback address to avoid problems :) + if ((address->family == AF_INET && tsk_striequals(address->ip, "127.0.0.1")) || (address->family == AF_INET6 && tsk_striequals(address->ip, "::1"))){ + continue; + } + + // host candidates + ret = tnet_ice_utils_create_sockets(socket_type, + address->ip, &socket_rtp, + self->use_rtcp ? &socket_rtcp : tsk_null); + if (ret == 0){ + const char* foundation_rtp = foundation_default; + tsk_list_lock(self->candidates_local); + if (socket_rtp){ + if ((candidate = tnet_ice_candidate_create(tnet_ice_cand_type_host, socket_rtp, self->is_ice_jingle, tsk_true, self->is_video, self->ufrag, self->pwd, foundation_default))){ + foundation_rtp = (const char*)candidate->foundation; + if (check_best_local_ip && (candidate->socket && (tsk_striequals(candidate->socket->ip, best_local_ip)))){ + curr_local_pref = 0xFFFF; + check_best_local_ip = tsk_false; + tnet_ice_candidate_set_local_pref(candidate, curr_local_pref); + tsk_list_push_front_data(self->candidates_local, (void**)&candidate); + } + else{ + curr_local_pref = local_pref--; + tnet_ice_candidate_set_local_pref(candidate, curr_local_pref); + tsk_list_push_back_data(self->candidates_local, (void**)&candidate); + } + } + } + if (socket_rtcp){ + if ((candidate = tnet_ice_candidate_create(tnet_ice_cand_type_host, socket_rtcp, self->is_ice_jingle, tsk_false, self->is_video, self->ufrag, self->pwd, foundation_rtp))){ + tnet_ice_candidate_set_local_pref(candidate, curr_local_pref); + tsk_list_push_back_data(self->candidates_local, (void**)&candidate); + } + } + tsk_list_unlock(self->candidates_local); + } + + TSK_OBJECT_SAFE_FREE(socket_rtp); + TSK_OBJECT_SAFE_FREE(socket_rtcp); + + // break if no longer running + if (!self->is_started){ + break; + } + + TSK_DEBUG_INFO("local ip address = %s", address->ip); + } + + // unlock-list + tsk_list_unlock(self->candidates_local); + +bail: + if (self->is_started){ + if (ret == 0 && !TSK_LIST_IS_EMPTY(self->candidates_local)){ + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Success); + } + else{ + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Failure); + } + } + + TSK_OBJECT_SAFE_FREE(addresses); + return ret; +} + +// GatheringHostCandidates -> (Success) -> (GatheringHostCandidatesDone) +static int _tnet_ice_ctx_fsm_GatheringHostCandidates_2_GatheringHostCandidatesDone_X_Success(va_list *app) +{ + int ret; + tnet_ice_ctx_t* self; + + self = va_arg(*app, tnet_ice_ctx_t *); + + ret = _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_gathering_host_candidates_succeed, "Gathering host candidates succeed"); + if (ret == 0) { + if (self->is_stun_enabled && _tnet_ice_ctx_servers_count_by_proto(self, tnet_ice_server_proto_stun) > 0) { + TSK_DEBUG_INFO("ICE-STUN enabled and we have STUN servers"); + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_GatherReflexiveCandidates); + } + else { + if (self->is_turn_enabled && _tnet_ice_ctx_servers_count_by_proto(self, tnet_ice_server_proto_turn) > 0) { + TSK_DEBUG_INFO("ICE-TURN enabled and we have STUN servers"); + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_GatherRelayCandidates); + } + else { + TSK_DEBUG_INFO("Do not gather reflexive/relayed candidates because ICE-STUN/TURN is disabled or no server defined"); + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_GatheringComplet); + } + } + } + + return ret; +} + +// GatheringHostCandidates -> (Failure) -> (Terminated) +static int _tnet_ice_ctx_fsm_GatheringHostCandidates_2_Terminated_X_Failure(va_list *app) +{ + tnet_ice_ctx_t* self; + + self = va_arg(*app, tnet_ice_ctx_t *); + return _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_gathering_host_candidates_failed, "Gathering host candidates failed"); +} + +// GatheringHostCandidatesDone -> (GatherReflexiveCandidate) -> GatheringReflexiveCandidates +static int _tnet_ice_ctx_fsm_GatheringHostCandidatesDone_2_GatheringReflexiveCandidates_X_GatherReflexiveCandidates(va_list *app) +{ + /* RFC 5389 - 7.2.1. Sending over UDP + STUN indications are not retransmitted; thus, indication transactions over UDP + are not reliable. + */ + int ret = 0; + tnet_ice_servers_L_t* ice_servers = tsk_null; + tnet_ice_server_t* ice_server; + tnet_ice_ctx_t* self; + uint16_t i, k, rc; + struct timeval tv; + tnet_stun_pkt_resp_t *response = tsk_null; + const tsk_list_item_t *item, *item_server; + tnet_ice_candidate_t* candidate; + tnet_fd_t fds[kIceCandidatesCountMax] = { TNET_INVALID_FD }; // -1, then zeros + tnet_fd_t fds_skipped[kIceCandidatesCountMax] = { TNET_INVALID_FD }; // -1, then zeros + uint16_t fds_count = 0; + tnet_fd_t fd_max = -1; + fd_set set; + tsk_size_t srflx_addr_count_added = 0, srflx_addr_count_skipped = 0, host_addr_count = 0; + long tv_sec, tv_usec; //very important to save these values as timeval could be modified by select() - happens on iOS - + + self = va_arg(*app, tnet_ice_ctx_t *); + + // Get ICE servers to use to gather reflexive candidates + ice_servers = _tnet_ice_ctx_servers_copy(self, tnet_ice_server_proto_stun); + if (!ice_servers || TSK_LIST_IS_EMPTY(ice_servers)) { // not expected to be null or empty because we checked the number of such servers before calling this transition + TSK_DEBUG_WARN("No valid STUN server could be used to gather reflexive candidates"); + goto bail; + } + + // set all default values to -1 + // = {{ -1 }} will only set the first element + for (i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) { + fds[i] = TNET_INVALID_FD; + } + for (i = 0; i < sizeof(fds_skipped) / sizeof(fds_skipped[0]); ++i) { + fds_skipped[i] = TNET_INVALID_FD; + } + + rc = self->Rc; + tv.tv_sec = tv_sec = 0; + tv.tv_usec = tv_usec = 0; + + // load fds for both rtp and rtcp sockets + tsk_list_foreach(item, self->candidates_local) { + if (!(candidate = item->data)) { + continue; + } + + ++host_addr_count; + if ((fds_count < sizeof(fds) / sizeof(fds[0])) && candidate->socket) { + fds[fds_count++] = candidate->socket->fd; + if (candidate->socket->fd > fd_max) { + fd_max = candidate->socket->fd; + } + } + } + + + /* RFC 5389 - 7.2.1. Sending over UDP + A client SHOULD retransmit a STUN request message starting with an + interval of RTO ("Retransmission TimeOut"), doubling after each + retransmission. + + e.g. 0 ms, 500 ms, 1500 ms, 3500 ms, 7500ms, 15500 ms, and 31500 ms + */ + for (i = 0; (i < rc && self->is_started && ((srflx_addr_count_added + srflx_addr_count_skipped) < host_addr_count)); ++i) { + // Try gathering the reflexive candidate for each server + tsk_list_foreach(item_server, ice_servers) { + if (!self->is_started) { + break; + } + if (!(ice_server = item_server->data)) { + continue; // must never happen + } + if (i == 0) { + ice_server->rto = 0; + } + else if (i == 1) { + ice_server->rto = self->RTO; + } + // else // ice_server->rto <<= 1; + tv_sec = ice_server->rto / 1000; + tv_usec = (ice_server->rto % 1000) * 1000; + if (tv_usec >= 1000000) { // > 1000000 is invalid and produce EINVAL when passed to select(iOS) + tv_usec -= 1000000; + tv_sec++; + } + // restore values for new select + tv.tv_sec = tv_sec; +#if TNET_UNDER_APPLE + tv.tv_usec = (__darwin_suseconds_t)tv_usec; +#else + tv.tv_usec = tv_usec; +#endif + + TSK_DEBUG_INFO("ICE reflexive candidates gathering ...srv_addr=%s,srv_port=%u,tv_sec=%lu,tv_usec=%lu,rto=%d", ice_server->str_server_addr, ice_server->u_server_port, tv_sec, tv_usec, ice_server->rto); + + FD_ZERO(&set); + for (k = 0; k < fds_count; ++k) { + FD_SET(fds[k], &set); + } + + // sends STUN binding requets + tsk_list_foreach(item, self->candidates_local){ + if (!(candidate = (tnet_ice_candidate_t*)item->data)) { + continue; + } + if (candidate->socket && tsk_strnullORempty(candidate->stun.srflx_addr)) { + ret = tnet_ice_candidate_send_stun_bind_request(candidate, &ice_server->obj_server_addr, ice_server->str_username, ice_server->str_password); + } + } + + if ((ret = select(fd_max + 1, &set, NULL, NULL, &tv)) < 0) { + TSK_DEBUG_ERROR("select() failed with error code = %d", tnet_geterrno()); + goto bail; + } + else if (ret == 0) { + // timeout + TSK_DEBUG_INFO("STUN request timedout at %d, rc = %d, rto=%d", i, rc - 1, ice_server->rto); + ice_server->rto <<= 1; + continue; + } + else if (ret > 0) { + // there is data to read + for (k = 0; k < fds_count; ++k) { + tnet_fd_t fd = fds[k]; + if (FD_ISSET(fd, &set)) { + unsigned int len = 0; + void* data = 0; + const tnet_ice_candidate_t* candidate_curr; + + // Check how many bytes are pending + if ((ret = tnet_ioctlt(fd, FIONREAD, &len)) < 0) { + TSK_DEBUG_ERROR("tnet_ioctlt() failed"); + continue; + } + + if (len == 0) { + TSK_DEBUG_INFO("tnet_ioctlt() retured zero bytes"); + continue; + } + + // Receive pending data + data = tsk_calloc(len, sizeof(uint8_t)); + if ((ret = tnet_sockfd_recv(fd, data, len, 0)) < 0) { + TSK_FREE(data); + + TSK_DEBUG_ERROR("Recving STUN dgrams failed with error code:%d", tnet_geterrno()); + continue; + } + + // Parse the incoming response + if ((ret = tnet_stun_pkt_read(data, (tsk_size_t)ret, &response))) { + TSK_FREE(data); + continue; + } + TSK_FREE(data); + if (response) { + ret = 0; + if ((candidate_curr = tnet_ice_candidate_find_by_fd(self->candidates_local, fd))) { + if (tsk_strnullORempty(candidate_curr->stun.srflx_addr)) { // "srflx" candidate? + ret = tnet_ice_candidate_process_stun_response((tnet_ice_candidate_t*)candidate_curr, response, fd); + if (!tsk_strnullORempty(candidate_curr->stun.srflx_addr)) { // ...and now (after processing the response)...is it "srflx" candidate? + if (tsk_striequals(candidate_curr->connection_addr, candidate_curr->stun.srflx_addr) && candidate_curr->port == candidate_curr->stun.srflx_port) { + tsk_size_t j; + tsk_bool_t already_skipped = tsk_false; + /* refc 5245- 4.1.3. Eliminating Redundant Candidates + + Next, the agent eliminates redundant candidates. A candidate is + redundant if its transport address equals another candidate, and its + base equals the base of that other candidate. Note that two + candidates can have the same transport address yet have different + bases, and these would not be considered redundant. Frequently, a + server reflexive candidate and a host candidate will be redundant + when the agent is not behind a NAT. The agent SHOULD eliminate the + redundant candidate with the lower priority. */ + for (j = 0; (fds_skipped[j] != TNET_INVALID_FD && j < (sizeof(fds_skipped) / sizeof(fds_skipped[0]))); ++j) { + if (fds_skipped[j] == fd) { + already_skipped = tsk_true; + break; + } + } + + if (!already_skipped) { + ++srflx_addr_count_skipped; + fds_skipped[j] = fd; + } + TSK_DEBUG_INFO("Skipping redundant candidate address=%s and port=%d, fd=%d, already_skipped(%u)=%s", + candidate_curr->stun.srflx_addr, + candidate_curr->stun.srflx_port, + fd, + (unsigned)j, already_skipped ? "yes" : "no"); + } + else { + char* foundation = tsk_strdup(TNET_ICE_CANDIDATE_TYPE_SRFLX); + tnet_ice_candidate_t* new_cand; + tsk_strcat(&foundation, (const char*)candidate_curr->foundation); + new_cand = tnet_ice_candidate_create(tnet_ice_cand_type_srflx, candidate_curr->socket, candidate_curr->is_ice_jingle, candidate_curr->is_rtp, self->is_video, self->ufrag, self->pwd, foundation); + TSK_FREE(foundation); + if (new_cand) { + ++srflx_addr_count_added; + tsk_list_lock(self->candidates_local); + tnet_ice_candidate_set_rflx_addr(new_cand, candidate_curr->stun.srflx_addr, candidate_curr->stun.srflx_port); + tsk_list_push_descending_data(self->candidates_local, (void**)&new_cand); + tsk_list_unlock(self->candidates_local); + } + } + } + } + } + } + TSK_OBJECT_SAFE_FREE(response); + } + } + } + else { + continue; + } + } // tsk_list_foreach (item, ice_servers)... + } // for (i = 0; (i < rc.... + +bail: + TSK_DEBUG_INFO("srflx_addr_count_added=%u, srflx_addr_count_skipped=%u", (unsigned)srflx_addr_count_added, (unsigned)srflx_addr_count_skipped); + if ((srflx_addr_count_added + srflx_addr_count_skipped) > 0) ret = 0; // Hack the returned value if we have at least one success (happens when timeouts) + if (self->is_started) { + if (ret == 0) { + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Success); + } + else{ + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Failure); + } + } + + tsk_list_foreach(item, self->candidates_local) { + if (!(candidate = (tnet_ice_candidate_t*)item->data)) { + continue; + } + TSK_DEBUG_INFO("Candidate: %s", tnet_ice_candidate_tostring(candidate)); + } + TSK_OBJECT_SAFE_FREE(ice_servers); + return ret; +} + +// GatheringReflexiveCandidates -> (Success) -> GatheringReflexiveCandidatesDone +static int _tnet_ice_ctx_fsm_GatheringReflexiveCandidates_2_GatheringReflexiveCandidatesDone_X_Success(va_list *app) +{ + tnet_ice_ctx_t* self; + + self = va_arg(*app, tnet_ice_ctx_t *); + + if (self->is_started) { + int ret = _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_gathering_reflexive_candidates_succeed, "Gathering reflexive candidates succeed"); + if (ret == 0) { + enum _fsm_action_e action_next = _fsm_action_GatheringComplet; + if (self->is_turn_enabled) { + if (_tnet_ice_ctx_servers_count_by_proto(self, tnet_ice_server_proto_turn) == 0) { + TSK_DEBUG_WARN("TURN is enabled but no TURN server could be found"); + } + else { + action_next = _fsm_action_GatherRelayCandidates; + } + } + ret = _tnet_ice_ctx_fsm_act(self, action_next); + } + return ret; + } + else { + return -1; + } +} + +// GatheringReflexiveCandidates -> (Failure) -> Terminated +static int _tnet_ice_ctx_fsm_GatheringReflexiveCandidates_2_Terminated_X_Failure(va_list *app) +{ + tnet_ice_ctx_t* self = va_arg(*app, tnet_ice_ctx_t *); + return _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_gathering_reflexive_candidates_failed, "Gathering reflexive candidates failed"); +} + +// GatheringReflexiveCandidatesDone -> (GatherRelayCandidates) -> GatheringRelayCandidates +static int _tnet_ice_ctx_fsm_GatheringReflexiveCandidatesDone_2_GatheringRelayCandidates_X_GatherRelayCandidates(va_list *app) +{ + tnet_ice_ctx_t* self = va_arg(*app, tnet_ice_ctx_t *); + int ret = 0; + tsk_list_item_t *item, *item_server = tsk_null; + tnet_ice_candidate_t* candidate; + uint16_t i, rto, rc; + tsk_size_t relay_addr_count_ok = 0, relay_addr_count_nok = 0, relay_addr_count_added = 0, host_addr_count = 0; + uint64_t u_t0, u_t1; + enum tnet_stun_state_e e_tunrn_state; + tnet_ice_servers_L_t* ice_servers = tsk_null; + tnet_ice_server_t* ice_server; + tnet_ice_candidates_L_t* candidates_local_copy = tsk_null;; + + // Create TURN condwait handle if not already done + if (!self->turn.condwait && !(self->turn.condwait = tsk_condwait_create())) { + TSK_DEBUG_ERROR("Failed to create TURN condwait handle"); + ret = -2; + goto bail; + } + + // Copy local ICE candidates + tsk_list_lock(self->candidates_local); + candidates_local_copy = tsk_list_clone(self->candidates_local); + tsk_list_unlock(self->candidates_local); + + // Take reference to the TURN servers + ice_servers = _tnet_ice_ctx_servers_copy(self, tnet_ice_server_proto_turn); + if (!ice_servers || TSK_LIST_IS_EMPTY(ice_servers)) { + TSK_DEBUG_WARN("TURN enabled but no server could be found"); // should never happen...but who knows? + goto bail; + } +next_server: + if (!self->is_started) { + goto bail; + } + relay_addr_count_ok = 0, relay_addr_count_nok = 0, relay_addr_count_added = 0, host_addr_count = 0; + if (!item_server) { + item_server = ice_servers->head; + } + else { + item_server = item_server->next; + } + if (!item_server) { + TSK_DEBUG_INFO("We have reached the end of TURN servers"); + goto bail; + } + ice_server = (tnet_ice_server_t*)item_server->data; + + // Create TURN sessions for each local host candidate + tsk_list_foreach(item, candidates_local_copy) { + if (!(candidate = item->data)) { + continue; + } + TSK_DEBUG_INFO("Gathering relay candidate: local addr=%s=%d, TURN server=%s:%d", candidate->connection_addr, candidate->port, ice_server->str_server_addr, ice_server->u_server_port); + + // Destroy previvious TURN session (if exist) + TSK_OBJECT_SAFE_FREE(candidate->turn.ss); + if (candidate->type_e == tnet_ice_cand_type_host && candidate->socket) { // do not create TURN session for reflexive candidates + // create the TURN session + // FIXME: For now we support UDP relaying only (like Chrome): more info at https://groups.google.com/forum/#!topic/turn-server-project-rfc5766-turn-server/vR_2OAV9a_w + // This is not an issue even if both peers requires TCP/TLS connection to the TURN server. UDP relaying will be local to the servers. + // + static enum tnet_turn_transport_e __e_req_transport = tnet_turn_transport_udp; // We should create two TURN sessions: #1 UDP relay + #1 TCP relay + if ((ret = tnet_turn_session_create_4(candidate->socket, __e_req_transport, ice_server->str_server_addr, ice_server->u_server_port, ice_server->e_transport, &candidate->turn.ss))) { + continue; + } + // set TURN callback + if ((ret = tnet_turn_session_set_callback(candidate->turn.ss, _tnet_ice_ctx_turn_callback, self))) { + continue; + } + // set SSL certificates + if ((ret = tnet_turn_session_set_ssl_certs(candidate->turn.ss, self->ssl.path_priv, self->ssl.path_pub, self->ssl.path_ca, self->ssl.verify))) { + continue; + } + // WebProxy + if ((ret = tnet_turn_session_set_proxy_auto_detect(candidate->turn.ss, self->proxy.auto_detect))) { + continue; + } + if ((ret = tnet_turn_session_set_proxy_info(candidate->turn.ss, self->proxy.info))) { + continue; + } + // set TURN credentials + if ((ret = tnet_turn_session_set_cred(candidate->turn.ss, ice_server->str_username, ice_server->str_password))) { + continue; + } + // prepare() + if ((ret = tnet_turn_session_prepare(candidate->turn.ss))) { + continue; + } + // start() + if ((ret = tnet_turn_session_start(candidate->turn.ss))) { + continue; + } + // allocate() + if ((ret = tnet_turn_session_allocate(candidate->turn.ss))) { + continue; + } + ++host_addr_count; + } + } // tsk_list_foreach(item, self->candidates_local) { + + rto = self->RTO; + rc = self->Rc; + + for (i = 0; (i < rc && self->is_started && ((relay_addr_count_ok + relay_addr_count_nok) < host_addr_count));) { + if (!self->is_started || !self->is_active) { + TSK_DEBUG_INFO("ICE context stopped/cancelled while gathering TURN candidates"); + goto bail; + } + + u_t0 = tsk_time_now(); + tsk_condwait_timedwait(self->turn.condwait, rto); + u_t1 = tsk_time_now(); + if ((u_t1 - u_t0) >= rto) { + // timedwait() -> timedout + rto <<= 1; + ++i; + } + + // count the number of TURN sessions with alloc() = ok/nok and ignore ones without response + relay_addr_count_ok = 0; + tsk_list_foreach(item, candidates_local_copy) { + if (!(candidate = item->data) || !candidate->turn.ss) { + continue; + } + if ((ret = tnet_turn_session_get_state_alloc(candidate->turn.ss, &e_tunrn_state))) { + goto bail; + } + if (e_tunrn_state == tnet_stun_state_ok) { + ++relay_addr_count_ok; + } + else if (e_tunrn_state == tnet_stun_state_nok) { + TSK_OBJECT_SAFE_FREE(candidate->turn.ss); // delete the session + ++relay_addr_count_nok; + } + } + } + + // add/delete TURN candidates + tsk_list_foreach(item, candidates_local_copy) { + if (!(candidate = item->data) || !candidate->turn.ss) { + continue; + } + if ((ret = tnet_turn_session_get_state_alloc(candidate->turn.ss, &e_tunrn_state))) { + goto bail; + } + if (e_tunrn_state == tnet_stun_state_ok) { + static tsk_bool_t __b_ipv6; + char* foundation = tsk_null; + char* relay_addr = tsk_null; + tnet_port_t relay_port; + tnet_ice_candidate_t* new_cand = tsk_null; + struct tnet_socket_s* p_lcl_sock = tsk_null; + + if ((ret = tnet_turn_session_get_relayed_addr(candidate->turn.ss, &relay_addr, &relay_port, &__b_ipv6))) { + goto bail; + } + if (tsk_striequals(candidate->connection_addr, relay_addr) && candidate->port == relay_port) { + TSK_DEBUG_INFO("Skipping redundant candidate address=%s and port=%d", relay_addr, relay_port); + TSK_FREE(relay_addr); + continue; + } + if ((ret = tnet_turn_session_get_socket_local(candidate->turn.ss, &p_lcl_sock))) { + goto bail; + } + tsk_strcat_2(&foundation, "%s%s", TNET_ICE_CANDIDATE_TYPE_RELAY, (const char*)candidate->foundation); + new_cand = tnet_ice_candidate_create(tnet_ice_cand_type_relay, p_lcl_sock, candidate->is_ice_jingle, candidate->is_rtp, self->is_video, self->ufrag, self->pwd, foundation); + TSK_FREE(foundation); + TSK_OBJECT_SAFE_FREE(p_lcl_sock); + if (new_cand) { + tsk_list_lock(self->candidates_local); + new_cand->turn.ss = candidate->turn.ss, candidate->turn.ss = tsk_null; + new_cand->turn.relay_addr = relay_addr, relay_addr = tsk_null; + new_cand->turn.relay_port = relay_port; + tnet_ice_candidate_set_rflx_addr(new_cand, new_cand->turn.relay_addr, new_cand->turn.relay_port); + tsk_list_push_descending_data(self->candidates_local, (void**)&new_cand); + tsk_list_unlock(self->candidates_local); + ++relay_addr_count_added; + } + TSK_FREE(relay_addr); + } + else { + TSK_OBJECT_SAFE_FREE(candidate->turn.ss); + } + } + + // Try next TURN server + if (self->is_started && item_server && relay_addr_count_added == 0) { + goto next_server; + } + +bail: + if (self->is_started) { + if (ret == 0) { + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Success); + } + else { + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Failure); + } + } + TSK_OBJECT_SAFE_FREE(ice_servers); + TSK_OBJECT_SAFE_FREE(candidates_local_copy); + return ret; +} + +// GatheringRelayCandidates -> (Success) -> GatheringRelayCandidatesDone +static int _tnet_ice_ctx_fsm_GatheringRelayCandidates_2_GatheringRelayCandidatesDone_X_Success(va_list *app) +{ + tnet_ice_ctx_t* self = va_arg(*app, tnet_ice_ctx_t *); + if (self->is_started) { + // Relay candidates are the last ones -> gathering is competed + return _tnet_ice_ctx_fsm_act(self, _fsm_action_GatheringComplet); + } + else { + return -1; + } +} + +// GatheringReflexiveCandidates -> (Failure) -> Terminated +static int _tnet_ice_ctx_fsm_GatheringRelayCandidates_2_Terminated_X_Failure(va_list *app) +{ + tnet_ice_ctx_t* self = va_arg(*app, tnet_ice_ctx_t *); + return _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_gathering_relay_candidates_failed, "Gathering relay candidates failed"); +} + +// Any -> (Cancel) -> Started +static int _tnet_ice_ctx_fsm_Any_2_Started_X_Cancel(va_list *app) +{ + tnet_ice_ctx_t* self; + self = va_arg(*app, tnet_ice_ctx_t *); + + tsk_list_lock(self->candidates_remote); + tsk_list_clear_items(self->candidates_remote); + tsk_list_unlock(self->candidates_remote); + + tsk_list_lock(self->candidates_pairs); + tsk_list_clear_items(self->candidates_pairs); + tsk_list_unlock(self->candidates_pairs); + + TSK_OBJECT_SAFE_FREE(self->turn.ss_nominated_rtp); + TSK_OBJECT_SAFE_FREE(self->turn.ss_nominated_rtcp); + + // Do not clear local candidates because then will be used as fallback if the remote peer is an ICE-lite + // These candidates will be cleared before the next local gathering + // tsk_list_lock(self->candidates_local); + // tsk_list_clear_items(self->candidates_local); + // tsk_list_unlock(self->candidates_local); + + // restore "is_cancelled" until next cancel + // set "is_active" to false to allow ICE re-start + // self->is_cancelled = tsk_false; + // self->is_active = tsk_false; + + // alert user + _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_cancelled, "Cancelled"); + + return 0; + +} + +// Any -> (GatheringComplet) -> GatheringCompleted +static int _tnet_ice_ctx_fsm_Any_2_GatheringCompleted_X_GatheringComplet(va_list *app) +{ + int ret = 0; + tnet_ice_ctx_t* self; + tsk_bool_t has_remote_candidates; + + self = va_arg(*app, tnet_ice_ctx_t *); + + // alert user + _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_gathering_completed, "Gathering candidates completed"); + + if (self->is_started){ + tsk_list_lock(self->candidates_remote); + has_remote_candidates = !TSK_LIST_IS_EMPTY(self->candidates_remote); + tsk_list_unlock(self->candidates_remote); + + if (has_remote_candidates){ + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_ConnCheck); + } + } + else{ + return -1; + } + + return ret; +} + +// GatheringComplet -> (ConnCheck) -> ConnChecking +static int _tnet_ice_ctx_fsm_GatheringCompleted_2_ConnChecking_X_ConnCheck(va_list *app) +{ + // Implements: + // 5.8. Scheduling Checks +#if !defined(FD_SETSIZE) +#define FD_SETSIZE 64 +#endif + int ret, err; + const tsk_list_item_t *item; + tnet_ice_ctx_t* self; + tnet_fd_t fds[FD_SETSIZE] = { -1 }; + tnet_fd_t fds_turn[FD_SETSIZE] = { -1 }; + uint16_t fds_count = 0, fds_turn_count = 0, k; + tnet_fd_t fd_max = -1; + fd_set set; + const tnet_ice_pair_t *pair; + struct timeval tv; + static const long rto = 160; // milliseconds + struct sockaddr_storage remote_addr; + uint64_t time_start, time_curr = 0, time_end = 0, concheck_timeout = 0; + tsk_bool_t role_conflict, restart_conneck, check_rtcp, isset, got_hosts; + void* recvfrom_buff_ptr = tsk_null; + tsk_size_t recvfrom_buff_size = 0, tries_count = 0, tries_count_min = kIceConnCheckMinTriesMin; + enum tnet_stun_state_e e_state; + + self = va_arg(*app, tnet_ice_ctx_t *); + + self->is_connchecking = tsk_true; + + // "tries_count" and "tries_count_min" + // The connection checks to to the "relay", "prflx", "srflx" and "host" candidates are sent at the same time. + // Because the requests are sent at the same time it's possible to have success check for "relay" (or "srflx") candidates before the "host" candidates. + // "tries_count_min" is the minimum (if success check is not for "host" candidates) tries before giving up. + // The pairs are already sorted ("host"->"srflx"->"prflx", "relay") to make sure to choose the best candidates when there are more than one success conncheck. + +start_conneck: + role_conflict = tsk_false; + restart_conneck = tsk_false; + + tsk_list_lock(self->candidates_pairs); + tsk_list_clear_items(self->candidates_pairs); + tsk_list_unlock(self->candidates_pairs); + + TSK_OBJECT_SAFE_FREE(self->turn.ss_nominated_rtp); + TSK_OBJECT_SAFE_FREE(self->turn.ss_nominated_rtcp); + + if ((ret = _tnet_ice_ctx_build_pairs(self, self->candidates_local, self->candidates_remote, self->candidates_pairs, self->is_controlling, self->tie_breaker, self->is_ice_jingle, self->use_rtcpmux))) { + TSK_DEBUG_ERROR("_tnet_ice_ctx_build_pairs() failed"); + goto bail; + } + +#define _FD_ISSET(_fds, _fds_count, _fd, _isset) { uint16_t __i; *_isset = 0; for (__i = 0; __i < _fds_count; ++__i) { if (_fds[__i] == _fd) { *_isset = 1; break; } } } + + // load fds for both rtp and rtcp sockets / create TURN permissions + tsk_list_lock(self->candidates_pairs); + tsk_list_foreach(item, self->candidates_pairs){ + if (!(pair = item->data) || !pair->candidate_offer || !pair->candidate_offer->socket){ + continue; + } + + if ((fds_count < sizeof(fds) / sizeof(fds[0])) && pair->candidate_offer->socket) { + if (pair->candidate_offer->turn.ss && (ret = tnet_turn_session_get_state_createperm(pair->candidate_offer->turn.ss, pair->turn_peer_id, &e_state)) == 0) { + if (e_state == tnet_stun_state_none) { + ret = tnet_turn_session_createpermission(((tnet_ice_pair_t *)pair)->candidate_offer->turn.ss, pair->candidate_answer->connection_addr, pair->candidate_answer->port, &((tnet_ice_pair_t *)pair)->turn_peer_id); + if (ret) { + continue; + // goto bail; + } + } + fds_turn[fds_turn_count++] = pair->candidate_offer->socket->fd; + // When TURN is active the socket (host) is pulled in the TURN session and any incoming data will be forwarded to us. + // Do not add fd to the set + continue; + } + _FD_ISSET(fds, fds_count, pair->candidate_offer->socket->fd, &isset); // not in the set -> to avoid doubloon + if (!isset) { + _FD_ISSET(fds_turn, fds_turn_count, pair->candidate_offer->socket->fd, &isset); // not already managed by a TURN session + if (!isset) { + fds[fds_count++] = pair->candidate_offer->socket->fd; + if (pair->candidate_offer->socket->fd > fd_max) { + fd_max = pair->candidate_offer->socket->fd; + } + } + } + } + } + tsk_list_unlock(self->candidates_pairs); + + concheck_timeout = self->concheck_timeout; + time_start = time_curr = tsk_time_now(); + time_end = (time_start + concheck_timeout); + tries_count_min = fds_turn_count > 0 ? kIceConnCheckMinTriesMax : kIceConnCheckMinTriesMin; + + while (self->is_started && self->is_active && (time_curr < time_end) && !self->have_nominated_symetric) { + tv.tv_sec = 0; + tv.tv_usec = (rto * 1000); + + FD_ZERO(&set); + for (k = 0; k < fds_count; ++k) { + FD_SET(fds[k], &set); + } + + // set new current time here to avoid "continue" skips + // ignore already ellapsed time if new timeout value is defined + time_curr = tsk_time_now(); + if (self->concheck_timeout != concheck_timeout) { + concheck_timeout = self->concheck_timeout; + time_start = time_curr; + time_end = (time_start + concheck_timeout); + } + + // Send ConnCheck requests + // the pairs are already sorted by priority (from high to low) + if (!self->have_nominated_symetric) { + tsk_list_foreach(item, self->candidates_pairs) { + if (!(pair = item->data) || !pair->candidate_offer || !pair->candidate_offer->socket) { + continue; + } + switch (pair->state_offer) { + case tnet_ice_pair_state_failed: + case tnet_ice_pair_state_succeed: + continue; + default: break; + } + + ret = tnet_ice_pair_send_conncheck((tnet_ice_pair_t *)pair); + } + } + + if (fds_count == 0) { + tsk_thread_sleep(10); + goto check_nomination; + } + + if ((ret = select(fd_max + 1, &set, NULL, NULL, &tv)) < 0) { + TNET_PRINT_LAST_ERROR("select() failed"); + goto bail; + } + else if (ret == 0) { + // timeout + // TSK_DEBUG_INFO("STUN request timedout"); + goto check_nomination; //!\ continue == possible endless loop + } + else if (ret > 0) { + // there is data to read + for (k = 0; k < fds_count; ++k) { + tnet_fd_t fd = fds[k]; + unsigned int len = 0; + tsk_size_t read = 0; + + if (!FD_ISSET(fd, &set)) { + continue; + } + + // Check how many bytes are pending + if ((ret = tnet_ioctlt(fd, FIONREAD, &len)) < 0) { + continue; + } + + if (len == 0){ + // TSK_DEBUG_INFO("tnet_ioctlt() returent zero bytes"); + continue; + } + + // Receive pending data + if (recvfrom_buff_size < len){ + if (!(recvfrom_buff_ptr = tsk_realloc(recvfrom_buff_ptr, len))){ + recvfrom_buff_size = 0; + goto bail; + } + recvfrom_buff_size = len; + } + + // receive all messages + while (self->is_started && self->is_active && read < len && ret == 0) { + if ((ret = tnet_sockfd_recvfrom(fd, recvfrom_buff_ptr, recvfrom_buff_size, 0, (struct sockaddr *)&remote_addr)) < 0) { + err = tnet_geterrno(); + /* "EAGAIN" means no data to read. We must trust "EAGAIN" instead of "read" because pending data could be removed by the system + */ + /* "WSAECONNRESET" + The virtual circuit was reset by the remote side executing a hard or abortive close. The application should close the socket as it is no longer usable. On a UDP-datagram socket, this error would indicate that a previous send operation resulted in an ICMP "Port Unreachable" message. + */ + if (err == TNET_ERROR_EAGAIN || err == TNET_ERROR_CONNRESET) { + // TODO: remove "fd" from the list if "E_CONNRESET" + len = 0; + continue; + } + + TNET_PRINT_LAST_ERROR("Receiving STUN dgrams failed with errno=%d", err); + goto bail; + } + + read += ret; + + // recv() STUN message (request / response) + ret = tnet_ice_ctx_recv_stun_message(self, recvfrom_buff_ptr, (tsk_size_t)ret, fd, &remote_addr, &role_conflict); + if (ret == 0 && role_conflict) { + // A change in roles will require to recompute pair priorities + restart_conneck = tsk_true; + // do not break the loop -> read/process all pending STUN messages + } + } + } + } + + check_nomination: + // check whether we need to re-start connection checking + if (restart_conneck) { + goto start_conneck; + } + + check_rtcp = (self->use_rtcp && !self->use_rtcpmux); + if (!self->have_nominated_offer) { + self->have_nominated_offer = tnet_ice_pairs_have_nominated_offer(self->candidates_pairs, check_rtcp); + } + if (!self->have_nominated_answer) { + self->have_nominated_answer = tnet_ice_pairs_have_nominated_answer(self->candidates_pairs, check_rtcp); + } + if (self->have_nominated_offer && self->have_nominated_answer) { + self->have_nominated_symetric = tnet_ice_pairs_have_nominated_symetric_2(self->candidates_pairs, check_rtcp, &got_hosts); + self->have_nominated_symetric &= (got_hosts || ((tries_count++) >= tries_count_min)); + } + } // while (self->is_started... + + // "ret" could be "<>0" if last function used was "select()", "recvfrom()", "ioctlt()"...this is why we set the value to #0. + // if there was an error then, we'll jump to "bail:" and next code is skipped + ret = 0; + +bail: + // move to the next state depending on the conncheck result + if (self->is_started) { + if (ret == 0 && self->have_nominated_symetric) { + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Success); + } + else { + if (time_curr >= time_end) { + TSK_DEBUG_ERROR("ConnCheck timedout, have_nominated_symetric=%s, have_nominated_answer=%s, have_nominated_offer=%s", + self->have_nominated_symetric ? "yes" : "false", + self->have_nominated_answer ? "yes" : "false", + self->have_nominated_offer ? "yes" : "false"); + } + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_Failure); + } + } + + TSK_FREE(recvfrom_buff_ptr); + + self->is_connchecking = tsk_false; + + return ret; +} + +// ConnChecking -> (Success) -> ConnCheckingCompleted +static int _tnet_ice_ctx_fsm_ConnChecking_2_ConnCheckingCompleted_X_Success(va_list *app) +{ + tnet_ice_ctx_t* self = va_arg(*app, tnet_ice_ctx_t *); + const tnet_ice_pair_t *pair_offer, *pair_answer_src, *pair_answer_dest; + const tsk_list_item_t *item; + const tnet_ice_pair_t *pair; + const tnet_ice_candidate_t *candidate; + tsk_list_t* sessions = tsk_list_create(); // for lock-free TURN sessions destroying + int ret; + + // When destroying TURN sessions the transport is locked by shutdown() + // This function locks "self->candidates_pairs" + // TURN callback locks "self->candidates_pairs" + // TURN callback locks the transport + // => We must not lock the candidates when destroying the TURN session + // Test with WES8 if you want to reproduce the issue + + TSK_OBJECT_SAFE_FREE(self->turn.ss_nominated_rtp); + TSK_OBJECT_SAFE_FREE(self->turn.ss_nominated_rtcp); + + tsk_list_lock(self->candidates_pairs); + + // take a reference to the negotiated TURN sessions + ret = tnet_ice_pairs_get_nominated_symetric_pairs(self->candidates_pairs, TNET_ICE_CANDIDATE_COMPID_RTP, &pair_offer, &pair_answer_src, &pair_answer_dest); + if (ret == 0) { + if (pair_offer && pair_offer->candidate_offer && pair_offer->candidate_offer->type_e == tnet_ice_cand_type_relay && pair_offer->candidate_offer->turn.ss) { + self->turn.ss_nominated_rtp = tsk_object_ref(pair_offer->candidate_offer->turn.ss); + self->turn.peer_id_rtp = pair_offer->turn_peer_id; + TSK_DEBUG_INFO("ICE: nominated TURN peer id [RTP] = %ld", self->turn.peer_id_rtp); + } + TSK_DEBUG_INFO("ICE: nominated symetric RTP pairs: offer:%llu, answer-src:%llu, answser-dest:%llu", + pair_offer ? pair_offer->id : 0, pair_answer_src ? pair_answer_src->id : 0, pair_answer_dest ? pair_answer_dest->id : 0); + } + if (ret == 0 && pair_offer) { ((tnet_ice_pair_t *)pair_offer)->is_nominated = tsk_true; } // "is_nominated" is used do decide whether to include "USE-CANDIDATE" attribute when aggressive mode is disabled + + ret = tnet_ice_pairs_get_nominated_symetric_pairs(self->candidates_pairs, TNET_ICE_CANDIDATE_COMPID_RTCP, &pair_offer, &pair_answer_src, &pair_answer_dest); + if (ret == 0) { + if (pair_offer && pair_offer->candidate_offer && pair_offer->candidate_offer->type_e == tnet_ice_cand_type_relay && pair_offer->candidate_offer->turn.ss) { + self->turn.ss_nominated_rtcp = tsk_object_ref(pair_offer->candidate_offer->turn.ss); + self->turn.peer_id_rtcp = pair_offer->turn_peer_id; + TSK_DEBUG_INFO("ICE: nominated TURN peer id [RTCP] = %ld", self->turn.peer_id_rtp); + } + TSK_DEBUG_INFO("ICE: nominated symetric RTCP(use:%d, mux:%d) pairs: offer:%llu, answer-src:%llu, answser-dest:%llu", + self->use_rtcp ? 1 : 0, self->use_rtcpmux ? 1 : 0, + pair_offer ? pair_offer->id : 0, pair_answer_src ? pair_answer_src->id : 0, pair_answer_dest ? pair_answer_dest->id : 0); + } + if (ret == 0 && pair_offer) { ((tnet_ice_pair_t *)pair_offer)->is_nominated = tsk_true; } // "is_nominated" is used do decide whether to include "USE-CANDIDATE" attribute when aggressive mode is disabled + + // collect all useless TURN sessions (pairs) + tsk_list_foreach(item, self->candidates_pairs) { + if (!(pair = item->data) || !pair->candidate_offer || !pair->candidate_offer->turn.ss) { + continue; + } + if (pair->candidate_offer->turn.ss != self->turn.ss_nominated_rtp && pair->candidate_offer->turn.ss != self->turn.ss_nominated_rtcp) { + tsk_list_push_back_data(sessions, (void**)&pair->candidate_offer->turn.ss); + TSK_OBJECT_SAFE_FREE(pair->candidate_offer->turn.ss); + } + } + + tsk_list_unlock(self->candidates_pairs); + + // collect all useless TURN sessions (local candidates) + tsk_list_lock(self->candidates_local); + tsk_list_foreach(item, self->candidates_local) { + if (!(candidate = item->data) || !candidate->turn.ss) { + continue; + } + if (candidate->turn.ss != self->turn.ss_nominated_rtp && candidate->turn.ss != self->turn.ss_nominated_rtcp) { + tsk_list_push_back_data(sessions, (void**)&candidate->turn.ss); + TSK_OBJECT_SAFE_FREE(((tnet_ice_candidate_t*)candidate)->turn.ss); + } + } + tsk_list_unlock(self->candidates_local); + + // collect all useless TURN sessions (remote candidates) + tsk_list_lock(self->candidates_remote); + tsk_list_foreach(item, self->candidates_remote) { + if (!(candidate = item->data) || !candidate->turn.ss) { + continue; + } + if (candidate->turn.ss != self->turn.ss_nominated_rtp && candidate->turn.ss != self->turn.ss_nominated_rtcp) { + tsk_list_push_back_data(sessions, (void**)&candidate->turn.ss); + TSK_OBJECT_SAFE_FREE(((tnet_ice_candidate_t*)candidate)->turn.ss); + } + } + tsk_list_unlock(self->candidates_remote); + + // lock-free destruction + TSK_OBJECT_SAFE_FREE(sessions); + + return _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_conncheck_succeed, "ConnCheck succeed"); +} + +// ConnChecking -> (Failure) ->Terminated +static int _tnet_ice_ctx_fsm_ConnChecking_2_Terminated_X_Failure(va_list *app) +{ + tnet_ice_ctx_t* self = va_arg(*app, tnet_ice_ctx_t *); + return _tnet_ice_ctx_signal_async(self, tnet_ice_event_type_conncheck_failed, "ConnCheck failed"); +} + +// Any (AnyNotStarted) -> Terminated +static int _tnet_ice_ctx_fsm_Any_2_Terminated_X_AnyNotStarted(va_list *app) +{ + return 0; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +static int _tnet_ice_ctx_fsm_OnTerminated(tnet_ice_ctx_t* self) +{ + TSK_DEBUG_INFO("=== ICE CTX SM Terminated ==="); + + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter."); + return -1; + } + + // still started but no longer active + self->is_active = tsk_false; + + return 0; +} + +static tsk_bool_t _tnet_ice_ctx_fsm_cond_NotStarted(tnet_ice_ctx_t* self, const void* _any) +{ + return (!self || !self->is_started); +} + +static int _tnet_ice_ctx_restart(tnet_ice_ctx_t* self) +{ + int ret = 0; + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + ret = tsk_fsm_set_current_state(self->fsm, _fsm_state_Started); + ret = _tnet_ice_ctx_fsm_act(self, _fsm_action_GatherHostCandidates); + + self->is_active = (ret == 0); + return ret; +} + +static int _tnet_ice_ctx_recv_stun_message_for_pair(tnet_ice_ctx_t* self, const tnet_ice_pair_t* pair, const void* data, tsk_size_t size, tnet_fd_t local_fd, const struct sockaddr_storage* remote_addr, tsk_bool_t *role_conflict) +{ + tnet_stun_pkt_t* message; + int ret = 0; + if (!self || !role_conflict || !data || !size || local_fd < 0 || !remote_addr) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + *role_conflict = tsk_false; + + if (!TNET_STUN_BUFF_IS_STUN2(((uint8_t*)data), size)) { + if (self->rtp_callback) { + return self->rtp_callback(self->rtp_callback_data, data, size, local_fd, remote_addr); + } + TSK_DEBUG_INFO("Not STUN message"); + return 0; + } + + if (!self->is_active) { + TSK_DEBUG_INFO("ICE context not active yet"); + return 0; + } + + if ((ret = tnet_stun_pkt_read(data, size, &message)) == 0 && message) { + if (message->e_type == tnet_stun_pkt_type_binding_request) { + tsk_bool_t is_local_conncheck_started; + if (self->is_building_pairs) { + TSK_DEBUG_INFO("Incoming STUN binding request while building new ICE pairs... wait for %d milliseconds max", kIcePairsBuildingTimeMax); + tsk_condwait_timedwait(self->condwait_pairs, kIcePairsBuildingTimeMax); + if (self->is_building_pairs) { + TSK_DEBUG_WARN("%d milliseconds ellapsed and still building pairs", kIcePairsBuildingTimeMax); + } + if (!self->is_active) { + TSK_DEBUG_WARN("ICE context deactivated while waiting for ICE pairs to finish building"); + TSK_OBJECT_SAFE_FREE(message); + return 0; + } + } + is_local_conncheck_started = !TSK_LIST_IS_EMPTY(self->candidates_pairs); // if empty means local conncheck haven't started + if (!pair && is_local_conncheck_started) { + pair = tnet_ice_pairs_find_by_fd_and_addr(self->candidates_pairs, local_fd, remote_addr); + } + if (!pair && !self->have_nominated_symetric && is_local_conncheck_started){ // pair not found and we're still negotiating + // rfc 5245 - 7.1.3.2.1. Discovering Peer Reflexive Candidates + tnet_ice_pair_t* pair_peer = tnet_ice_pair_prflx_create(self->candidates_pairs, local_fd, remote_addr); + if (pair_peer) { + pair = pair_peer; // save memory address + tsk_list_push_descending_data(self->candidates_pairs, (void**)&pair_peer); + TSK_OBJECT_SAFE_FREE(pair_peer); + } + } + if (pair) { + short resp_code = 0; + char* resp_phrase = tsk_null; + // authenticate the request + tnet_ice_pair_auth_conncheck(pair, message, data, size, &resp_code, &resp_phrase); + if (resp_code > 0 && resp_phrase){ + if (resp_code >= 200 && resp_code <= 299){ + // Before sending the success response check that there are no role conflict + if (self->is_controlling){ // I'm ICE-CONTROLLING + const tnet_stun_attr_vdata_t* stun_att_ice_controlling; + if ((ret = tnet_stun_pkt_attr_find_first(message, tnet_stun_attr_type_ice_controlling, (const tnet_stun_attr_t**)&stun_att_ice_controlling)) == 0 && stun_att_ice_controlling){ + TSK_DEBUG_WARN("Role conflicts (SEND)"); + if (self->tie_breaker >= *((uint64_t*)stun_att_ice_controlling->p_data_ptr)) { + resp_code = kStunErrCodeIceConflict; + tsk_strupdate(&resp_phrase, "Role conflicts"); + } + else { + // switch to "controlled" role + self->is_controlling = tsk_false; + *role_conflict = tsk_true; + } + } + else; + } + else { // I'm ICE-CONTROLLED + const tnet_stun_attr_vdata_t* stun_att_ice_controlled; + if ((ret = tnet_stun_pkt_attr_find_first(message, tnet_stun_attr_type_ice_controlled, (const tnet_stun_attr_t**)&stun_att_ice_controlled)) == 0 && stun_att_ice_controlled) { + TSK_DEBUG_WARN("Role conflicts (SEND)"); + if (self->tie_breaker >= *((uint64_t*)stun_att_ice_controlled->p_data_ptr)) { + self->is_controlling = tsk_true; + *role_conflict = tsk_true; + } + else { + resp_code = kStunErrCodeIceConflict; + tsk_strupdate(&resp_phrase, "Role conflicts"); + } + } + } + } + ret = tnet_ice_pair_send_response((tnet_ice_pair_t *)pair, message, resp_code, resp_phrase, remote_addr); + // "keepalive": also send STUN-BINDING if we receive one in the nominated pair and conneck is finished + //!\ IMPORTANT: chrome requires this + //!\ We also need to continue sending connection checks as we don't really know if the remote party has finished checking + if ((self->is_ice_jingle || pair->is_nominated) && self->have_nominated_symetric) { + ret = tnet_ice_pair_send_conncheck((tnet_ice_pair_t *)pair); // "keepalive" + } + } + TSK_FREE(resp_phrase); + } + else { // if(pair == null) + if (!is_local_conncheck_started) { + TSK_DEBUG_INFO("ICE local conncheck haven't started yet"); + } + else { + TSK_DEBUG_ERROR("Cannot find ICE pair with local fd = %d", local_fd); + } + } + } + else if (TNET_STUN_PKT_IS_RESP(message)) { + if (pair || (pair = tnet_ice_pairs_find_by_response(self->candidates_pairs, message))) { + ret = tnet_ice_pair_recv_response(((tnet_ice_pair_t*)pair), message, self->is_connchecking); +#if 0 + if (TNET_STUN_PKT_RESP_IS_ERROR(message)) { + uint16_t u_code; + if ((ret = tnet_stun_pkt_get_errorcode(message, &u_code)) == 0 && u_code == kStunErrCodeIceConflict) { + // If this code is called this means that we have lower tie-breaker and we must toggle our role + TSK_DEBUG_WARN("Role conflicts (RECV)"); + self->is_controlling = !self->is_controlling; + *role_conflict = tsk_true; + } + } +#endif + } + } + } + TSK_OBJECT_SAFE_FREE(message); + + return ret; +} + +static int _tnet_ice_ctx_send_turn_raw(struct tnet_ice_ctx_s* self, struct tnet_turn_session_s* turn_ss, tnet_turn_peer_id_t turn_peer_id, const void* data, tsk_size_t size) +{ + if (!self || !turn_ss || !data || !size){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + // (self); + return tnet_turn_session_send_data(turn_ss, turn_peer_id, data, (uint16_t)size); +} + + +// build pairs as per RFC 5245 section "5.7.1. Forming Candidate Pairs" +static int _tnet_ice_ctx_build_pairs(struct tnet_ice_ctx_s* self, tnet_ice_candidates_L_t* local_candidates, tnet_ice_candidates_L_t* remote_candidates, tnet_ice_pairs_L_t* result_pairs, tsk_bool_t is_controlling, uint64_t tie_breaker, tsk_bool_t is_ice_jingle, tsk_bool_t is_rtcpmuxed) +{ + const tsk_list_item_t *item_local, *item_remote; + const tnet_ice_candidate_t *cand_local, *cand_remote; + tnet_ice_pair_t *pair; + enum tnet_turn_transport_e e_req_transport; + tnet_family_t addr_family_local, addr_family_remote; + + if (!self || TSK_LIST_IS_EMPTY(local_candidates) || TSK_LIST_IS_EMPTY(remote_candidates) || !result_pairs) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + self->is_building_pairs = tsk_true; + TSK_DEBUG_INFO("ICE: begin building pairs(is_rtcpmuxed=%d)", is_rtcpmuxed); + + tsk_list_clear_items(result_pairs); + + tsk_list_lock(local_candidates); + tsk_list_lock(remote_candidates); + tsk_list_lock(result_pairs); + + tsk_list_foreach(item_local, local_candidates) { + if (!(cand_local = item_local->data)) { + continue; + } + if (is_rtcpmuxed && cand_local->comp_id == TNET_ICE_CANDIDATE_COMPID_RTCP) { + continue; + } +#if 0 // TURN:FORCE + if (cand_local->type_e != tnet_ice_cand_type_relay) { + continue; + } +#endif + + tsk_list_foreach(item_remote, remote_candidates) { + if (!(cand_remote = item_remote->data)) { + continue; + } + // Hack for Chrome bug (candidate with port=zero) to avoid printing errors. + if (cand_remote->port == 0) { + TSK_DEBUG_INFO("Skipping remote ICE candidate with port = 0"); + continue; + } + + // CompIds(1=RTP, 2=RTCP) must match + if ((cand_remote->comp_id != cand_local->comp_id)){ + continue; + } + // IP versions must match. Cannot use IPv4 socket to send/recv to IPv6 address. + if (cand_local->socket) { + addr_family_local = TNET_SOCKET_TYPE_IS_IPV4(cand_local->socket->type) ? AF_INET : AF_INET6; + addr_family_remote = tnet_get_family(cand_remote->connection_addr, cand_remote->port); + if (addr_family_local != addr_family_remote) { + TSK_DEBUG_INFO("Address family mismatch:%d<->%d", addr_family_local, addr_family_remote); + continue; + } + } + if (cand_local->turn.ss) { + if (tnet_turn_session_get_req_transport(cand_local->turn.ss, &e_req_transport) != 0) { + continue; + } + if (e_req_transport == tnet_turn_transport_udp && !TNET_SOCKET_TYPE_IS_DGRAM(cand_remote->transport_e)) { + continue; + } + if (e_req_transport == tnet_turn_transport_tcp && !TNET_SOCKET_TYPE_IS_STREAM(cand_remote->transport_e)) { + continue; + } + } + else { + if (cand_remote->transport_e != cand_local->transport_e) { + continue; + } + } + + if ((pair = tnet_ice_pair_create(cand_local, cand_remote, is_controlling, tie_breaker, is_ice_jingle))) { + TSK_DEBUG_INFO("ICE Pair(%llu, %llu): [%s %u %u %s %d] -> [%s %u %u %s %d]", + pair->id, + pair->priority, + + cand_local->foundation, + cand_local->priority, + cand_local->comp_id, + cand_local->connection_addr, + cand_local->port, + + cand_remote->foundation, + cand_remote->priority, + cand_remote->comp_id, + cand_remote->connection_addr, + cand_remote->port); + tsk_list_push_descending_data(result_pairs, (void**)&pair); + } + } + } +#if 0 + tsk_list_foreach(item_local, result_pairs) { + if (!(pair = item_local->data)) { + continue; + } + + TSK_DEBUG_INFO("ICE Pair(%llu, %llu): [%s %u %s %d] -> [%s %u %s %d]", + pair->id, + pair->priority, + + pair->candidate_offer->foundation, + pair->candidate_offer->comp_id, + pair->candidate_offer->connection_addr, + pair->candidate_offer->port, + + pair->candidate_answer->foundation, + pair->candidate_answer->comp_id, + pair->candidate_answer->connection_addr, + pair->candidate_answer->port); + } +#endif + + tsk_list_unlock(local_candidates); + tsk_list_unlock(remote_candidates); + tsk_list_unlock(result_pairs); + + self->is_building_pairs = tsk_false; + tsk_condwait_broadcast(self->condwait_pairs); + TSK_DEBUG_INFO("ICE: end building pairs"); + + return 0; +} + + +static int _tnet_ice_ctx_fsm_act(tnet_ice_ctx_t* self, tsk_fsm_action_id action_id) +{ + tnet_ice_action_t *action = tsk_null; + tnet_ice_event_t* e = tsk_null; + static const char* phrase = "$action$"; + int ret = 0; + + if (!self || !self->fsm){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if (!(action = tnet_ice_action_create(action_id))){ + TSK_DEBUG_ERROR("Failed to create action"); + return -2; + } + + if (self->is_sync_mode) { + ret = tsk_fsm_act(self->fsm, action->id, self, action, self, action); + } + else { + if ((e = tnet_ice_event_create(self, tnet_ice_event_type_action, phrase, self->userdata))){ + tnet_ice_event_set_action(e, action); + TSK_RUNNABLE_ENQUEUE_OBJECT_SAFE(TSK_RUNNABLE(self), e); + goto bail; + } + else{ + TSK_DEBUG_ERROR("Failed to create ICE event"); + ret = -2; + goto bail; + } + } + +bail: + TSK_OBJECT_SAFE_FREE(e); + TSK_OBJECT_SAFE_FREE(action); + return ret; +} + +static int _tnet_ice_ctx_signal_async(tnet_ice_ctx_t* self, tnet_ice_event_type_t type, const char* phrase) +{ + tnet_ice_event_t* e; + if (!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if (self->is_silent_mode && type != tnet_ice_event_type_action) { // silent mode ON and not action to move the FSM + TSK_DEBUG_INFO("ICE silent mode ON...to not notify '%d:%s'", type, phrase); + return 0; + } + + if ((e = tnet_ice_event_create(self, type, phrase, self->userdata))){ + TSK_RUNNABLE_ENQUEUE_OBJECT_SAFE(TSK_RUNNABLE(self), e); + return 0; + } + else{ + TSK_DEBUG_ERROR("Failed to create ICE event"); + return -2; + } +} + +static int _tnet_ice_ctx_turn_callback(const struct tnet_turn_session_event_xs *e) +{ + tnet_ice_ctx_t *ctx = tsk_object_ref(TSK_OBJECT(e->pc_usr_data)); + struct tnet_turn_session_s* session = tsk_object_ref(TSK_OBJECT(e->pc_session)); + int ret = 0; + + if (!ctx) { + // the ICE context is being destroyed but TURN session not freed yet + goto bail; + } + + switch (e->e_type) { + case tnet_turn_session_event_type_alloc_ok: + case tnet_turn_session_event_type_refresh_ok: + case tnet_turn_session_event_type_chanbind_ok: + case tnet_turn_session_event_type_connect_ok: + default: + { + break; + } + + case tnet_turn_session_event_type_alloc_nok: + case tnet_turn_session_event_type_refresh_nok: + case tnet_turn_session_event_type_perm_nok: + case tnet_turn_session_event_type_chanbind_nok: + case tnet_turn_session_event_type_connect_nok: + { + // Do not raise error event if no nominated candidate because + // TURN error could be raised by the session when we're in "conncheck" state and this is a normal case. + if (ctx->is_active && ctx->is_started && ctx->turn.ss_nominated_rtp && ctx->turn.peer_id_rtp == e->u_peer_id) { + TSK_DEBUG_ERROR("TURN connection broken (peer-id=%ld)", e->u_peer_id); + if ((ret = _tnet_ice_ctx_signal_async(ctx, tnet_ice_event_type_turn_connection_broken, "TURN connection is broken"))) { + goto bail; + } + } + break; + } + + case tnet_turn_session_event_type_perm_ok: + { + enum tnet_turn_transport_e e_req_transport; + if ((ret = tnet_turn_session_get_req_transport(session, &e_req_transport))) { + goto bail; + } + + if (e_req_transport == tnet_turn_transport_tcp) { + // TCP-Connect: rfc6062 - 4.3. Initiating a Connection + if ((ret = tnet_turn_session_connect(session, e->u_peer_id))) { + goto bail; + } + } + else { + // Bind a channel (not required). If succeed, will be used to save bandwidth usage. + // TODO: should be done only if first "get_state(chanbind)==none". Not an issue, if it already exists then, will be refreshed. + if ((ret = tnet_turn_session_chanbind(session, e->u_peer_id))) { + goto bail; + } + } + break; + } + + case tnet_turn_session_event_type_recv_data: + { + tsk_bool_t role_conflict; + tnet_ice_pair_t* pair = tsk_null; + if (e->u_peer_id != kTurnPeerIdInvalid) { + const tsk_list_item_t *item; + tsk_list_lock(ctx->candidates_pairs); + tsk_list_foreach(item, ctx->candidates_pairs) { + if (((const tnet_ice_pair_t*)item->data)->turn_peer_id == e->u_peer_id) { + pair = tsk_object_ref((void*)item->data); + break; + } + } + tsk_list_unlock(ctx->candidates_pairs); + } + + ret = _tnet_ice_ctx_recv_stun_message_for_pair( + ctx, + pair, + e->data.pc_data_ptr, e->data.u_data_size, + e->pc_enet ? e->pc_enet->local_fd : TNET_INVALID_FD, + e->pc_enet ? &e->pc_enet->remote_addr : tsk_null, + &role_conflict); + TSK_OBJECT_SAFE_FREE(pair); + if (ret) { + goto bail; + } + + // rebuild candidates if role conflict + if (role_conflict) { + tsk_list_lock(ctx->candidates_pairs); + tsk_list_clear_items(ctx->candidates_pairs); + tsk_list_unlock(ctx->candidates_pairs); + + TSK_OBJECT_SAFE_FREE(ctx->turn.ss_nominated_rtp); + TSK_OBJECT_SAFE_FREE(ctx->turn.ss_nominated_rtcp); + + if ((ret = _tnet_ice_ctx_build_pairs(ctx, ctx->candidates_local, ctx->candidates_remote, ctx->candidates_pairs, ctx->is_controlling, ctx->tie_breaker, ctx->is_ice_jingle, ctx->use_rtcpmux))) { + TSK_DEBUG_ERROR("_tnet_ice_ctx_build_pairs() failed"); + goto bail; + } + } + + break; + } + } + + // alert() waiting threads + if ((ret = tsk_condwait_broadcast(ctx->turn.condwait))) { + goto bail; + } + +bail: + tsk_object_unref(ctx); + tsk_object_unref(session); + return ret; +} + +static void* TSK_STDCALL _tnet_ice_ctx_run(void* self) +{ + // No need to take ref(ctx) because this thread will be stopped by the dtor() before memory free. + tsk_list_item_t *curr; + tnet_ice_ctx_t *ctx = (tnet_ice_ctx_t *)(self); + tnet_ice_event_t *e; + + TSK_DEBUG_INFO("ICE CTX::run -- START"); + + TSK_RUNNABLE_RUN_BEGIN(ctx); + + // must because "ctx->callback(e);" could call a function trying to free "ctx" + // do not move before "TSK_RUNNABLE_RUN_BEGIN(ctx)", otherwise it'll be required to stop the "runnable" to have "ctx->refCount==0" + ctx = tsk_object_ref(ctx); + + if (ctx->is_started && (curr = TSK_RUNNABLE_POP_FIRST(ctx))) { + e = (tnet_ice_event_t*)curr->data; + switch (e->type) { + case tnet_ice_event_type_action: + { + if (e->action) { + tsk_fsm_act(ctx->fsm, e->action->id, ctx, e->action, ctx, e->action); + } + break; + } + default: + { + if (ctx->callback){ + ctx->callback(e); + } + break; + } + } + tsk_object_unref(curr); + } + + if (!(ctx = tsk_object_unref(ctx))) { + goto exit; + } + + TSK_RUNNABLE_RUN_END(ctx); + +exit: + if (ctx) { + tsk_list_clear_items(ctx->candidates_local); + tsk_list_clear_items(ctx->candidates_remote); + tsk_list_lock(ctx->candidates_pairs); // must + tsk_list_clear_items(ctx->candidates_pairs); + tsk_list_unlock(ctx->candidates_pairs); + } + + TSK_DEBUG_INFO("ICE CTX::run -- STOP"); + + return 0; +} + +static int _tnet_ice_ctx_servers_clear(struct tnet_ice_ctx_s* self) +{ + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + tsk_list_lock(self->servers); + tsk_list_clear_items(self->servers); + tsk_list_unlock(self->servers); + return 0; +} + +static int _tnet_ice_ctx_server_add(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, + enum tnet_socket_type_e e_transport, + const char* str_server_addr, uint16_t u_server_port, + const char* str_software, + const char* str_username, const char* str_password) +{ + struct tnet_ice_server_s* ice_server; + int ret = -1; + if (!self || !e_proto || !str_server_addr || !u_server_port) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + // TURN requires credentials + if ((e_proto & tnet_ice_server_proto_turn) == tnet_ice_server_proto_turn && (tsk_strnullORempty(str_username) || tsk_strnullORempty(str_password))) { + /* rfc5766 - 4. General Behavior + The server MUST demand that all requests from the client + be authenticated using this mechanism, or that a equally strong or + stronger mechanism for client authentication is used.*/ + TSK_DEBUG_ERROR("TURN requires credentials"); + return -1; + } + // Create and add the ICE server + tsk_list_lock(self->servers); + if (_tnet_ice_ctx_server_exists(self, e_proto, e_transport, str_server_addr, u_server_port)) { + TSK_DEBUG_WARN("ICE server (proto=%d, transport=%d, addr=%s, port=%hu) already exists", e_proto, e_transport, str_server_addr, u_server_port); + ret = 0; // Not an error + goto bail; + } + if (!(ice_server = tnet_ice_server_create(e_proto, e_transport, str_server_addr, u_server_port, str_software, str_username, str_password))) { + TSK_DEBUG_ERROR("Failed to create ICE server(proto=%d, transport=%d, addr=%s, port=%hu)", e_proto, e_transport, str_server_addr, u_server_port); + goto bail; + } + tsk_list_push_back_data(self->servers, (void**)&ice_server); + TSK_OBJECT_SAFE_FREE(ice_server); + + ret = 0; +bail: + tsk_list_unlock(self->servers); + return ret; +} + +static int _tnet_ice_ctx_server_remove(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, enum tnet_socket_type_e e_transport, const char* str_server_addr, uint16_t u_server_port) +{ + const struct tnet_ice_server_s* _pc_ice_srv; + const tsk_list_item_t *pc_item; + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + tsk_list_lock(self->servers); + tsk_list_foreach(pc_item, self->servers) { + if ((_pc_ice_srv = pc_item->data)) { + if (_pc_ice_srv->e_proto == e_proto && _pc_ice_srv->e_transport == e_transport && _pc_ice_srv->u_server_port == u_server_port && tsk_striequals(_pc_ice_srv->str_server_addr, str_server_addr)) { + tsk_list_remove_item(self->servers, (tsk_list_item_t *)pc_item); + break; + } + } + } + tsk_list_unlock(self->servers); + return 0; +} + +static const struct tnet_ice_server_s* _tnet_ice_ctx_server_find(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, enum tnet_socket_type_e e_transport, const char* str_server_addr, uint16_t u_server_port) +{ + const struct tnet_ice_server_s* pc_ice_srv = tsk_null; + const struct tnet_ice_server_s* _pc_ice_srv; + const tsk_list_item_t *pc_item; + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + tsk_list_lock(self->servers); + tsk_list_foreach(pc_item, self->servers) { + if ((_pc_ice_srv = pc_item->data)) { + if (_pc_ice_srv->e_proto == e_proto && _pc_ice_srv->e_transport == e_transport && _pc_ice_srv->u_server_port == u_server_port && tsk_striequals(_pc_ice_srv->str_server_addr, str_server_addr)) { + pc_ice_srv = _pc_ice_srv; + break; + } + } + } + tsk_list_unlock(self->servers); + return pc_ice_srv; +} + +static tsk_bool_t _tnet_ice_ctx_server_exists(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto, enum tnet_socket_type_e e_transport, const char* str_server_addr, uint16_t u_server_port) +{ + return _tnet_ice_ctx_server_find(self, e_proto, e_transport, str_server_addr, u_server_port) ? tsk_true : tsk_false; +} + +static tsk_size_t _tnet_ice_ctx_servers_count_by_proto(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto) +{ + tsk_size_t count = 0; + if (self) { + const struct tnet_ice_server_s* _pc_ice_srv; + const tsk_list_item_t *pc_item; + tsk_list_lock(self->servers); + tsk_list_foreach(pc_item, self->servers) { + if ((_pc_ice_srv = pc_item->data) && (_pc_ice_srv->e_proto & e_proto) == e_proto) { + ++count; + } + } + tsk_list_unlock(self->servers); + } + return count; +} + +// Up to the caller to free the returned list +static tnet_ice_servers_L_t* _tnet_ice_ctx_servers_copy(struct tnet_ice_ctx_s* self, enum tnet_ice_server_proto_e e_proto) +{ + tnet_ice_servers_L_t* copy = tsk_list_create(); + if (copy) { + const struct tnet_ice_server_s* _pc_ice_srv; + const tsk_list_item_t *pc_item; + tsk_list_lock(self->servers); + tsk_list_foreach(pc_item, self->servers) { + if ((_pc_ice_srv = pc_item->data) && (_pc_ice_srv->e_proto & e_proto) == e_proto) { + tnet_ice_server_t* srv = (tnet_ice_server_t*)tsk_object_ref(pc_item->data); + tsk_list_push_back_data(copy, (void**)&srv); + } + } + tsk_list_unlock(self->servers); + } + return copy; +} |