From b768f518f28ba2ea97c7370e108b7b3ae2a90966 Mon Sep 17 00:00:00 2001 From: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> Date: Thu, 30 Nov 2017 22:24:03 -0500 Subject: [PATCH] ice: refactor using pimpl idiom Continue the compilation firewall work. Also update code to use C++14 features. Rationale: prepare the change to a new transport abstraction over the project. Change-Id: I4d387bea8447f5dc251a6bd64ecb63b234bd1cdf Reviewed-by: Olivier Soldano <olivier.soldano@savoirfairelinux.com> --- src/ice_transport.cpp | 866 +++++++++++++++++++++++++----------------- src/ice_transport.h | 364 ++++++------------ 2 files changed, 629 insertions(+), 601 deletions(-) diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp index 750f4453df..7ab020daa1 100644 --- a/src/ice_transport.cpp +++ b/src/ice_transport.cpp @@ -28,6 +28,12 @@ #include <pjlib.h> #include <msgpack.hpp> +#include <map> +#include <atomic> +#include <queue> +#include <mutex> +#include <condition_variable> +#include <thread> #include <utility> #include <tuple> #include <algorithm> @@ -36,22 +42,135 @@ #include <thread> #include <cerrno> -#define TRY(ret) do { \ +#define TRY(ret) do { \ if ((ret) != PJ_SUCCESS) \ - throw std::runtime_error(#ret " failed"); \ + throw std::runtime_error(#ret " failed"); \ } while (0) namespace ring { static constexpr unsigned STUN_MAX_PACKET_SIZE {8192}; -static constexpr uint16_t IPV6_HEADER_SIZE = 40; // Size in bytes of IPV6 packet header -static constexpr uint16_t IPV4_HEADER_SIZE = 20; // Size in bytes of IPV4 packet header +static constexpr uint16_t IPV6_HEADER_SIZE = 40; ///< Size in bytes of IPV6 packet header +static constexpr uint16_t IPV4_HEADER_SIZE = 20; ///< Size in bytes of IPV4 packet header +static constexpr int MAX_CANDIDATES {32}; +static constexpr char NEW_LINE = '\n'; ///< New line character used for (de)serialisation -// TODO: C++14 ? remove me and use std::min -template< class T > -static constexpr const T& min( const T& a, const T& b ) { - return (b < a) ? b : a; -} +//============================================================================== + +namespace +{ + +struct IceSTransDeleter +{ + void operator ()(pj_ice_strans* ptr) { + pj_ice_strans_stop_ice(ptr); + pj_ice_strans_destroy(ptr); + } +}; + +} // namespace <anonymous> + +//============================================================================== + +class IceTransport::Impl +{ +public: + Impl(const char* name, int component_count, bool master, const IceTransportOptions& options); + ~Impl(); + + void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, + pj_status_t status); + + void onReceiveData(unsigned comp_id, void *pkt, pj_size_t size); + + /** + * Set/change transport role as initiator. + * Should be called before start method. + */ + bool setInitiatorSession(); + + /** + * Set/change transport role as slave. + * Should be called before start method. + */ + bool setSlaveSession(); + + bool createIceSession(pj_ice_sess_role role); + + void getUFragPwd(); + + void getDefaultCanditates(); + + // Non-mutex protected of public versions + bool _isInitialized() const; + bool _isStarted() const; + bool _isRunning() const; + bool _isFailed() const; + + IpAddr getLocalAddress(unsigned comp_id) const; + IpAddr getRemoteAddress(unsigned comp_id) const; + + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; + IceTransportCompleteCb on_initdone_cb_; + IceTransportCompleteCb on_negodone_cb_; + std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_; + unsigned component_count_; + pj_ice_sess_cand cand_[MAX_CANDIDATES] {}; + std::string local_ufrag_; + std::string local_pwd_; + pj_sockaddr remoteAddr_; + std::condition_variable iceCV_ {}; + mutable std::mutex iceMutex_ {}; + pj_ice_strans_cfg config_; + std::string last_errmsg_; + + struct Packet { + Packet(void *pkt, pj_size_t size) + : data {std::make_unique<char[]>(size)}, datalen {size} { + std::copy_n(reinterpret_cast<char*>(pkt), size, data.get()); + } + std::unique_ptr<char[]> data; + size_t datalen; + }; + + struct ComponentIO { + std::mutex mutex; + std::condition_variable cv; + std::deque<Packet> queue; + IceRecvCb cb; + }; + + std::vector<ComponentIO> compIO_; + + std::atomic_bool initiatorSession_ {true}; + + /** + * Returns the IP of each candidate for a given component in the ICE session + */ + std::vector<IpAddr> getLocalCandidatesAddr(unsigned comp_id) const; + + /** + * Adds a reflective candidate to ICE session + * Must be called before negotiation + */ + void addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr); + + /** + * Creates UPnP port mappings and adds ICE candidates based on those mappings + */ + void selectUPnPIceCandidates(); + + std::unique_ptr<upnp::Controller> upnp_; + + bool onlyIPv4Private_ {true}; + + // IO/Timer events are handled by following thread + std::thread thread_; + std::atomic_bool threadTerminateFlags_ {false}; + void handleEvents(unsigned max_msec); +}; + +//============================================================================== /** * Add stun/turn servers or default host as candidates @@ -128,41 +247,10 @@ add_turn_server(pj_pool_t& pool, pj_ice_strans_cfg& cfg, const TurnServerInfo& i RING_DBG("[ice] added turn server '%s', port %d", pj_strbuf(&turn.server), turn.port); } -//################################################################################################## - -IceTransport::Packet::Packet(void *pkt, pj_size_t size) - : data(new char[size]), datalen(size) -{ - std::copy_n(reinterpret_cast<char*>(pkt), size, data.get()); -} - -void -IceTransport::cb_on_rx_data(pj_ice_strans* ice_st, - unsigned comp_id, - void *pkt, pj_size_t size, - const pj_sockaddr_t* /*src_addr*/, - unsigned /*src_addr_len*/) -{ - if (auto tr = static_cast<IceTransport*>(pj_ice_strans_get_user_data(ice_st))) - tr->onReceiveData(comp_id, pkt, size); - else - RING_WARN("null IceTransport"); -} - -void -IceTransport::cb_on_ice_complete(pj_ice_strans* ice_st, - pj_ice_strans_op op, - pj_status_t status) -{ - if (auto tr = static_cast<IceTransport*>(pj_ice_strans_get_user_data(ice_st))) - tr->onComplete(ice_st, op, status); - else - RING_WARN("null IceTransport"); -} - +//============================================================================== -IceTransport::IceTransport(const char* name, int component_count, bool master, - const IceTransportOptions& options) +IceTransport::Impl::Impl(const char* name, int component_count, bool master, + const IceTransportOptions& options) : pool_(nullptr, pj_pool_release) , on_initdone_cb_(options.onInitDone) , on_negodone_cb_(options.onNegoDone) @@ -184,8 +272,23 @@ IceTransport::IceTransport(const char* name, int component_count, bool master, pj_ice_strans_cb icecb; pj_bzero(&icecb, sizeof(icecb)); - icecb.on_rx_data = cb_on_rx_data; - icecb.on_ice_complete = cb_on_ice_complete; + + icecb.on_rx_data = \ + [] (pj_ice_strans* ice_st, unsigned comp_id, void *pkt, pj_size_t size, + const pj_sockaddr_t* /*src_addr*/, unsigned /*src_addr_len*/) { + if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) + tr->onReceiveData(comp_id, pkt, size); + else + RING_WARN("null IceTransport"); + }; + + icecb.on_ice_complete = \ + [] (pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status) { + if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) + tr->onComplete(ice_st, op, status); + else + RING_WARN("null IceTransport"); + }; // Add STUN servers for (auto& server : options.stunServers) @@ -195,7 +298,7 @@ IceTransport::IceTransport(const char* name, int component_count, bool master, for (auto& server : options.turnServers) add_turn_server(*pool_, config_, server); - static constexpr auto IOQUEUE_MAX_HANDLES = min(PJ_IOQUEUE_MAX_HANDLES, 64); + static constexpr auto IOQUEUE_MAX_HANDLES = std::min(PJ_IOQUEUE_MAX_HANDLES, 64); TRY( pj_timer_heap_create(pool_.get(), 100, &config_.stun_cfg.timer_heap) ); TRY( pj_ioqueue_create(pool_.get(), IOQUEUE_MAX_HANDLES, &config_.stun_cfg.ioqueue) ); @@ -216,7 +319,7 @@ IceTransport::IceTransport(const char* name, int component_count, bool master, }); } -IceTransport::~IceTransport() +IceTransport::Impl::~Impl() { sip_utils::register_thread(); @@ -233,8 +336,46 @@ IceTransport::~IceTransport() pj_timer_heap_destroy(config_.stun_cfg.timer_heap); } +bool +IceTransport::Impl::_isInitialized() const +{ + if (auto icest = icest_.get()) { + auto state = pj_ice_strans_get_state(icest); + return state >= PJ_ICE_STRANS_STATE_SESS_READY and state != PJ_ICE_STRANS_STATE_FAILED; + } + return false; +} + +bool +IceTransport::Impl::_isStarted() const +{ + if (auto icest = icest_.get()) { + auto state = pj_ice_strans_get_state(icest); + return state >= PJ_ICE_STRANS_STATE_NEGO and state != PJ_ICE_STRANS_STATE_FAILED; + } + return false; +} + +bool +IceTransport::Impl::_isRunning() const +{ + if (auto icest = icest_.get()) { + auto state = pj_ice_strans_get_state(icest); + return state >= PJ_ICE_STRANS_STATE_RUNNING and state != PJ_ICE_STRANS_STATE_FAILED; + } + return false; +} + +bool +IceTransport::Impl::_isFailed() const +{ + if (auto icest = icest_.get()) + return pj_ice_strans_get_state(icest) == PJ_ICE_STRANS_STATE_FAILED; + return false; +} + void -IceTransport::handleEvents(unsigned max_msec) +IceTransport::Impl::handleEvents(unsigned max_msec) { // By tests, never seen more than two events per 500ms static constexpr auto MAX_NET_EVENTS = 2; @@ -277,8 +418,7 @@ IceTransport::handleEvents(unsigned max_msec) } void -IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, - pj_status_t status) +IceTransport::Impl::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status) { const char *opname = op == PJ_ICE_STRANS_OP_INIT ? "initialization" : @@ -308,7 +448,7 @@ IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, } if (op == PJ_ICE_STRANS_OP_INIT and on_initdone_cb_) - on_initdone_cb_(*this, done); + on_initdone_cb_(done); else if (op == PJ_ICE_STRANS_OP_NEGOTIATION) { if (done) { // Dump of connection pairs @@ -329,95 +469,326 @@ IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, RING_DBG("[ice:%p] connection pairs (local <-> remote):\n%s", this, out.str().c_str()); } if (on_negodone_cb_) - on_negodone_cb_(*this, done); + on_negodone_cb_(done); } // Unlock waitForXXX APIs iceCV_.notify_all(); } +bool +IceTransport::Impl::setInitiatorSession() +{ + RING_DBG("ICE as master"); + initiatorSession_ = true; + if (_isInitialized()) { + auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLING); + if (status != PJ_SUCCESS) { + last_errmsg_ = sip_utils::sip_strerror(status); + RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str()); + return false; + } + return true; + } + return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING); +} + +bool +IceTransport::Impl::setSlaveSession() +{ + RING_DBG("ICE as slave"); + initiatorSession_ = false; + if (_isInitialized()) { + auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLED); + if (status != PJ_SUCCESS) { + last_errmsg_ = sip_utils::sip_strerror(status); + RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str()); + return false; + } + return true; + } + return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED); +} + +IpAddr +IceTransport::Impl::getLocalAddress(unsigned comp_id) const +{ + // Return the local IP of negotiated connection pair + if (_isRunning()) { + if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1)) + return sess->lcand->addr; + else + return {}; // disabled component + } else + RING_WARN("[ice:%p] bad call: non-negotiated transport", this); + + // Return the default IP (could be not nominated and valid after negotiation) + if (_isInitialized()) + return cand_[comp_id].addr; + + RING_ERR("[ice:%p] bad call: non-initialized transport", this); + return {}; +} + +IpAddr +IceTransport::Impl::getRemoteAddress(unsigned comp_id) const +{ + // Return the remote IP of negotiated connection pair + if (_isRunning()) { + if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1)) + return sess->rcand->addr; + else + return {}; // disabled component + } else + RING_WARN("[ice:%p] bad call: non-negotiated transport", this); + + RING_ERR("[ice:%p] bad call: non-negotiated transport", this); + return {}; +} + void -IceTransport::getUFragPwd() +IceTransport::Impl::getUFragPwd() { pj_str_t local_ufrag, local_pwd; - pj_ice_strans_get_ufrag_pwd(icest_.get(), &local_ufrag, &local_pwd, NULL, NULL); + pj_ice_strans_get_ufrag_pwd(icest_.get(), &local_ufrag, &local_pwd, nullptr, nullptr); local_ufrag_.assign(local_ufrag.ptr, local_ufrag.slen); local_pwd_.assign(local_pwd.ptr, local_pwd.slen); } -std::string -IceTransport::getLastErrMsg() const +void +IceTransport::Impl::getDefaultCanditates() +{ + for (unsigned i=0; i < component_count_; ++i) + pj_ice_strans_get_def_cand(icest_.get(), i+1, &cand_[i]); +} + +bool +IceTransport::Impl::createIceSession(pj_ice_sess_role role) +{ + if (pj_ice_strans_init_ice(icest_.get(), role, nullptr, nullptr) != PJ_SUCCESS) { + RING_ERR("[ice:%p] pj_ice_strans_init_ice() failed", this); + return false; + } + + // Fetch some information on local configuration + getUFragPwd(); + getDefaultCanditates(); + RING_DBG("[ice:%p] (local) ufrag=%s, pwd=%s", this, local_ufrag_.c_str(), local_pwd_.c_str()); + return true; +} + +std::vector<IpAddr> +IceTransport::Impl::getLocalCandidatesAddr(unsigned comp_id) const +{ + std::vector<IpAddr> cand_addrs; + pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)]; + unsigned cand_cnt = PJ_ARRAY_SIZE(cand); + + if (pj_ice_strans_enum_cands(icest_.get(), comp_id, &cand_cnt, cand) != PJ_SUCCESS) { + RING_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this); + return cand_addrs; + } + + for (unsigned i=0; i<cand_cnt; ++i) + cand_addrs.push_back(cand[i].addr); + + return cand_addrs; +} + +void +IceTransport::Impl::addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr) +{ + // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK + // WARNING: following implementation is a HACK of PJNATH !! + // ice_strans doesn't have any API that permit to inject ICE any kind of candidates. + // So, the hack consists in accessing hidden ICE session using a patched PJPNATH + // library with a new API exposing this session (pj_ice_strans_get_ice_sess). + // Then call pj_ice_sess_add_cand() with a carfully forged candidate: + // the transport_id field uses an index in ICE transport STUN servers array + // corresponding to a STUN server with the same address familly. + // This implies we hope they'll not be modification of transport_id meaning in future + // and no conflics with the borrowed STUN config. + // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK + + // borrowed from pjproject/pjnath/ice_strans.c, modified to be C++11'ized. + static auto CREATE_TP_ID = [](pj_uint8_t type, pj_uint8_t idx) { + return (pj_uint8_t)((type << 6) | idx); + }; + static constexpr int SRFLX_PREF = 65535; + static constexpr int TP_STUN = 1; + + // find a compatible STUN host with same address familly, normally all system enabled + // host addresses are represented, so we expect to always found this host + int idx = -1; + auto af = addr.getFamily(); + if (af == AF_UNSPEC) { + RING_ERR("[ice:%p] Unable to add reflective IP %s: unknown addess familly", this, + addr.toString().c_str()); + return; + } + + for (unsigned i=0; i < config_.stun_tp_cnt; ++i) { + if (config_.stun_tp[i].af == af) { + idx = i; + break; + } + } + if (idx < 0) { + RING_ERR("[ice:%p] Unable to add reflective IP %s: no suitable local STUN host found", this, + addr.toString().c_str()); + return; + } + + pj_ice_sess_cand cand; + + cand.type = PJ_ICE_CAND_TYPE_SRFLX; + cand.status = PJ_EPENDING; // not used + cand.comp_id = comp_id; + cand.transport_id = CREATE_TP_ID(TP_STUN, idx); // HACK!! + cand.local_pref = SRFLX_PREF; // reflective + /* cand.foundation = ? */ + /* cand.prio = calculated by ice session */ + /* make base and addr the same since we're not going through a server */ + pj_sockaddr_cp(&cand.base_addr, base.pjPtr()); + pj_sockaddr_cp(&cand.addr, addr.pjPtr()); + pj_sockaddr_cp(&cand.rel_addr, &cand.base_addr); + pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr); + + auto ret = pj_ice_sess_add_cand(pj_ice_strans_get_ice_sess(icest_.get()), + cand.comp_id, + cand.transport_id, + cand.type, + cand.local_pref, + &cand.foundation, + &cand.addr, + &cand.base_addr, + &cand.rel_addr, + pj_sockaddr_get_len(&cand.addr), + NULL); + + if (ret != PJ_SUCCESS) { + last_errmsg_ = sip_utils::sip_strerror(ret); + RING_ERR("[ice:%p] pj_ice_sess_add_cand failed with error %d: %s", this, ret, + last_errmsg_.c_str()); + RING_ERR("[ice:%p] failed to add candidate for comp_id=%d : %s : %s", this, comp_id, + base.toString().c_str(), addr.toString().c_str()); + } else { + RING_DBG("[ice:%p] succeed to add candidate for comp_id=%d : %s : %s", this, comp_id, + base.toString().c_str(), addr.toString().c_str()); + } +} + +void +IceTransport::Impl::selectUPnPIceCandidates() +{ + /* use upnp to open ports and add the proper candidates */ + if (upnp_) { + /* for every component, get the candidate(s) + * create a port mapping either with that port, or with an available port + * add candidate with that port and public IP + */ + if (auto publicIP = upnp_->getExternalIP()) { + /* comp_id start at 1 */ + for (unsigned comp_id = 1; comp_id <= component_count_; ++comp_id) { + RING_DBG("[ice:%p] UPnP: Opening port(s) for ICE comp %d and adding candidate with public IP", + this, comp_id); + auto candidates = getLocalCandidatesAddr(comp_id); + for (IpAddr addr : candidates) { + auto localIP = upnp_->getLocalIP(); + localIP.setPort(addr.getPort()); + if (addr != localIP) + continue; + uint16_t port = addr.getPort(); + uint16_t port_used; + if (upnp_->addAnyMapping(port, upnp::PortType::UDP, true, &port_used)) { + publicIP.setPort(port_used); + addReflectiveCandidate(comp_id, addr, publicIP); + } else + RING_WARN("[ice:%p] UPnP: Could not create a port mapping for the ICE candide", this); + } + } + } else { + RING_WARN("[ice:%p] UPnP: Could not determine public IP for ICE candidates", this); + } + } +} + +void +IceTransport::Impl::onReceiveData(unsigned comp_id, void *pkt, pj_size_t size) +{ + if (!comp_id or comp_id > component_count_) { + RING_ERR("rx: invalid comp_id (%u)", comp_id); + return; + } + if (!size) + return; + auto& io = compIO_[comp_id-1]; + std::lock_guard<std::mutex> lk(io.mutex); + if (io.cb) { + io.cb((uint8_t*)pkt, size); + } else { + io.queue.emplace_back(pkt, size); + io.cv.notify_one(); + } +} + +//============================================================================== + +IceTransport::IceTransport(const char* name, int component_count, bool master, + const IceTransportOptions& options) + : pimpl_ {std::make_unique<Impl>(name, component_count, master, options)} +{} + +bool +IceTransport::isInitialized() const { - return last_errmsg_; + std::lock_guard<std::mutex> lk(pimpl_->iceMutex_); + return pimpl_->_isInitialized(); } -void -IceTransport::getDefaultCanditates() +bool +IceTransport::isStarted() const { - for (unsigned i=0; i < component_count_; ++i) - pj_ice_strans_get_def_cand(icest_.get(), i+1, &cand_[i]); + std::lock_guard<std::mutex> lk {pimpl_->iceMutex_}; + return pimpl_->_isStarted(); } bool -IceTransport::createIceSession(pj_ice_sess_role role) +IceTransport::isRunning() const { - if (pj_ice_strans_init_ice(icest_.get(), role, nullptr, nullptr) != PJ_SUCCESS) { - RING_ERR("[ice:%p] pj_ice_strans_init_ice() failed", this); - return false; - } - - // Fetch some information on local configuration - getUFragPwd(); - getDefaultCanditates(); - RING_DBG("[ice:%p] (local) ufrag=%s, pwd=%s", this, local_ufrag_.c_str(), local_pwd_.c_str()); - return true; + std::lock_guard<std::mutex> lk {pimpl_->iceMutex_}; + return pimpl_->_isRunning(); } bool -IceTransport::setInitiatorSession() +IceTransport::isFailed() const { - RING_DBG("ICE as master"); - initiatorSession_ = true; - if (isInitialized()) { - auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLING); - if (status != PJ_SUCCESS) { - last_errmsg_ = sip_utils::sip_strerror(status); - RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str()); - return false; - } - return true; - } - return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING); + std::lock_guard<std::mutex> lk {pimpl_->iceMutex_}; + return pimpl_->_isFailed(); } -bool -IceTransport::setSlaveSession() +unsigned +IceTransport::getComponentCount() const { - RING_DBG("ICE as slave"); - initiatorSession_ = false; - if (isInitialized()) { - auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLED); - if (status != PJ_SUCCESS) { - last_errmsg_ = sip_utils::sip_strerror(status); - RING_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str()); - return false; - } - return true; - } - return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED); + return pimpl_->component_count_; +} + +std::string +IceTransport::getLastErrMsg() const +{ + return pimpl_->last_errmsg_; } bool IceTransport::isInitiator() const { if (isInitialized()) - return pj_ice_strans_get_role(icest_.get()) == PJ_ICE_SESS_ROLE_CONTROLLING; - return initiatorSession_; + return pj_ice_strans_get_role(pimpl_->icest_.get()) == PJ_ICE_SESS_ROLE_CONTROLLING; + return pimpl_->initiatorSession_; } bool -IceTransport::start(const Attribute& rem_attrs, - const std::vector<IceCandidate>& rem_candidates) +IceTransport::start(const Attribute& rem_attrs, const std::vector<IceCandidate>& rem_candidates) { if (not isInitialized()) { RING_ERR("[ice:%p] not initialized transport", this); @@ -432,14 +803,14 @@ IceTransport::start(const Attribute& rem_attrs, pj_str_t ufrag, pwd; RING_DBG("[ice:%p] negotiation starting (%zu remote candidates)", this, rem_candidates.size()); - auto status = pj_ice_strans_start_ice(icest_.get(), + auto status = pj_ice_strans_start_ice(pimpl_->icest_.get(), pj_cstr(&ufrag, rem_attrs.ufrag.c_str()), pj_cstr(&pwd, rem_attrs.pwd.c_str()), rem_candidates.size(), rem_candidates.data()); if (status != PJ_SUCCESS) { - last_errmsg_ = sip_utils::sip_strerror(status); - RING_ERR("[ice:%p] start failed: %s", this, last_errmsg_.c_str()); + pimpl_->last_errmsg_ = sip_utils::sip_strerror(status); + RING_ERR("[ice:%p] start failed: %s", this, pimpl_->last_errmsg_.c_str()); return false; } return true; @@ -482,12 +853,12 @@ IceTransport::start(const std::vector<uint8_t>& rem_data) return false; } - if (rem_pwd.empty() or rem_pwd.empty() or rem_candidates.empty()) { + if (rem_ufrag.empty() or rem_pwd.empty() or rem_candidates.empty()) { RING_ERR("[ice:%p] invalid remote attributes", this); return false; } - if (onlyIPv4Private_) + if (pimpl_->onlyIPv4Private_) RING_WARN("[ice:%p] no public IPv4 found, your connection may fail!", this); return start({rem_ufrag, rem_pwd}, rem_candidates); @@ -497,104 +868,42 @@ bool IceTransport::stop() { if (isStarted()) { - auto status = pj_ice_strans_stop_ice(icest_.get()); + auto status = pj_ice_strans_stop_ice(pimpl_->icest_.get()); if (status != PJ_SUCCESS) { - last_errmsg_ = sip_utils::sip_strerror(status); - RING_ERR("ICE stop failed: %s", last_errmsg_.c_str()); + pimpl_->last_errmsg_ = sip_utils::sip_strerror(status); + RING_ERR("ICE stop failed: %s", pimpl_->last_errmsg_.c_str()); return false; } } return true; } -bool -IceTransport::_isInitialized() const -{ - if (auto icest = icest_.get()) { - auto state = pj_ice_strans_get_state(icest); - return state >= PJ_ICE_STRANS_STATE_SESS_READY and state != PJ_ICE_STRANS_STATE_FAILED; - } - return false; -} - -bool -IceTransport::_isStarted() const -{ - if (auto icest = icest_.get()) { - auto state = pj_ice_strans_get_state(icest); - return state >= PJ_ICE_STRANS_STATE_NEGO and state != PJ_ICE_STRANS_STATE_FAILED; - } - return false; -} - -bool -IceTransport::_isRunning() const -{ - if (auto icest = icest_.get()) { - auto state = pj_ice_strans_get_state(icest); - return state >= PJ_ICE_STRANS_STATE_RUNNING and state != PJ_ICE_STRANS_STATE_FAILED; - } - return false; -} - -bool -IceTransport::_isFailed() const -{ - if (auto icest = icest_.get()) - return pj_ice_strans_get_state(icest) == PJ_ICE_STRANS_STATE_FAILED; - return false; -} - IpAddr IceTransport::getLocalAddress(unsigned comp_id) const { - // Return the local IP of negotiated connection pair - if (isRunning()) { - if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1)) - return sess->lcand->addr; - else - return {}; // disabled component - } else - RING_WARN("[ice:%p] bad call: non-negotiated transport", this); - - // Return the default IP (could be not nominated and valid after negotiation) - if (isInitialized()) - return cand_[comp_id].addr; - - RING_ERR("[ice:%p] bad call: non-initialized transport", this); - return {}; + return pimpl_->getLocalAddress(comp_id); } IpAddr IceTransport::getRemoteAddress(unsigned comp_id) const { - // Return the remote IP of negotiated connection pair - if (isRunning()) { - if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1)) - return sess->rcand->addr; - else - return {}; // disabled component - } else - RING_WARN("[ice:%p] bad call: non-negotiated transport", this); - - RING_ERR("[ice:%p] bad call: non-negotiated transport", this); - return {}; + return pimpl_->getRemoteAddress(comp_id); } const IceTransport::Attribute IceTransport::getLocalAttributes() const { - return {local_ufrag_, local_pwd_}; + return {pimpl_->local_ufrag_, pimpl_->local_pwd_}; } std::vector<std::string> IceTransport::getLocalCandidates(unsigned comp_id) const { std::vector<std::string> res; - pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)]; + pj_ice_sess_cand cand[PJ_ARRAY_SIZE(pimpl_->cand_)]; unsigned cand_cnt = PJ_ARRAY_SIZE(cand); - if (pj_ice_strans_enum_cands(icest_.get(), comp_id+1, &cand_cnt, cand) != PJ_SUCCESS) { + if (pj_ice_strans_enum_cands(pimpl_->icest_.get(), comp_id+1, &cand_cnt, cand) != PJ_SUCCESS) { RING_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this); return res; } @@ -615,24 +924,6 @@ IceTransport::getLocalCandidates(unsigned comp_id) const return res; } -std::vector<IpAddr> -IceTransport::getLocalCandidatesAddr(unsigned comp_id) const -{ - std::vector<IpAddr> cand_addrs; - pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)]; - unsigned cand_cnt = PJ_ARRAY_SIZE(cand); - - if (pj_ice_strans_enum_cands(icest_.get(), comp_id, &cand_cnt, cand) != PJ_SUCCESS) { - RING_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this); - return cand_addrs; - } - - for (unsigned i=0; i<cand_cnt; ++i) - cand_addrs.push_back(cand[i].addr); - - return cand_addrs; -} - bool IceTransport::registerPublicIP(unsigned compId, const IpAddr& publicIP) { @@ -649,136 +940,18 @@ IceTransport::registerPublicIP(unsigned compId, const IpAddr& publicIP) // negotiation, only to exchanged candidates between peers. auto localIP = ip_utils::getLocalAddr(publicIP.getFamily()); auto pubIP = publicIP; - for (const auto& addr : getLocalCandidatesAddr(compId)) { + for (const auto& addr : pimpl_->getLocalCandidatesAddr(compId)) { auto port = addr.getPort(); localIP.setPort(port); if (addr != localIP) continue; pubIP.setPort(port); - addReflectiveCandidate(compId, addr, pubIP); + pimpl_->addReflectiveCandidate(compId, addr, pubIP); return true; } return false; } -void -IceTransport::addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr) -{ - // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK - // WARNING: following implementation is a HACK of PJNATH !! - // ice_strans doesn't have any API that permit to inject ICE any kind of candidates. - // So, the hack consists in accessing hidden ICE session using a patched PJPNATH - // library with a new API exposing this session (pj_ice_strans_get_ice_sess). - // Then call pj_ice_sess_add_cand() with a carfully forged candidate: - // the transport_id field uses an index in ICE transport STUN servers array - // corresponding to a STUN server with the same address familly. - // This implies we hope they'll not be modification of transport_id meaning in future - // and no conflics with the borrowed STUN config. - // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK - - // borrowed from pjproject/pjnath/ice_strans.c, modified to be C++11'ized. - static auto CREATE_TP_ID = [](pj_uint8_t type, pj_uint8_t idx) { - return (pj_uint8_t)((type << 6) | idx); - }; - static constexpr int SRFLX_PREF = 65535; - static constexpr int TP_STUN = 1; - - // find a compatible STUN host with same address familly, normally all system enabled - // host addresses are represented, so we expect to always found this host - int idx = -1; - auto af = addr.getFamily(); - if (af == AF_UNSPEC) { - RING_ERR("[ice:%p] Unable to add reflective IP %s: unknown addess familly", this, - addr.toString().c_str()); - return; - } - - for (unsigned i=0; i < config_.stun_tp_cnt; ++i) { - if (config_.stun_tp[i].af == af) { - idx = i; - break; - } - } - if (idx < 0) { - RING_ERR("[ice:%p] Unable to add reflective IP %s: no suitable local STUN host found", this, - addr.toString().c_str()); - return; - } - - pj_ice_sess_cand cand; - - cand.type = PJ_ICE_CAND_TYPE_SRFLX; - cand.status = PJ_EPENDING; // not used - cand.comp_id = comp_id; - cand.transport_id = CREATE_TP_ID(TP_STUN, idx); // HACK!! - cand.local_pref = SRFLX_PREF; // reflective - /* cand.foundation = ? */ - /* cand.prio = calculated by ice session */ - /* make base and addr the same since we're not going through a server */ - pj_sockaddr_cp(&cand.base_addr, base.pjPtr()); - pj_sockaddr_cp(&cand.addr, addr.pjPtr()); - pj_sockaddr_cp(&cand.rel_addr, &cand.base_addr); - pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr); - - auto ret = pj_ice_sess_add_cand(pj_ice_strans_get_ice_sess(icest_.get()), - cand.comp_id, - cand.transport_id, - cand.type, - cand.local_pref, - &cand.foundation, - &cand.addr, - &cand.base_addr, - &cand.rel_addr, - pj_sockaddr_get_len(&cand.addr), - NULL); - - if (ret != PJ_SUCCESS) { - last_errmsg_ = sip_utils::sip_strerror(ret); - RING_ERR("[ice:%p] pj_ice_sess_add_cand failed with error %d: %s", this, ret, - last_errmsg_.c_str()); - RING_ERR("[ice:%p] failed to add candidate for comp_id=%d : %s : %s", this, comp_id, - base.toString().c_str(), addr.toString().c_str()); - } else { - RING_DBG("[ice:%p] succeed to add candidate for comp_id=%d : %s : %s", this, comp_id, - base.toString().c_str(), addr.toString().c_str()); - } -} - -void -IceTransport::selectUPnPIceCandidates() -{ - /* use upnp to open ports and add the proper candidates */ - if (upnp_) { - /* for every component, get the candidate(s) - * create a port mapping either with that port, or with an available port - * add candidate with that port and public IP - */ - if (auto publicIP = upnp_->getExternalIP()) { - /* comp_id start at 1 */ - for (unsigned comp_id = 1; comp_id <= component_count_; ++comp_id) { - RING_DBG("[ice:%p] UPnP: Opening port(s) for ICE comp %d and adding candidate with public IP", - this, comp_id); - auto candidates = getLocalCandidatesAddr(comp_id); - for (IpAddr addr : candidates) { - auto localIP = upnp_->getLocalIP(); - localIP.setPort(addr.getPort()); - if (addr != localIP) - continue; - uint16_t port = addr.getPort(); - uint16_t port_used; - if (upnp_->addAnyMapping(port, upnp::PortType::UDP, true, &port_used)) { - publicIP.setPort(port_used); - addReflectiveCandidate(comp_id, addr, publicIP); - } else - RING_WARN("[ice:%p] UPnP: Could not create a port mapping for the ICE candide", this); - } - } - } else { - RING_WARN("[ice:%p] UPnP: Could not determine public IP for ICE candidates", this); - } - } -} - std::vector<uint8_t> IceTransport::packIceMsg() const { @@ -789,34 +962,15 @@ IceTransport::packIceMsg() const std::stringstream ss; msgpack::pack(ss, ICE_MSG_VERSION); - msgpack::pack(ss, std::make_pair(local_ufrag_, local_pwd_)); - msgpack::pack(ss, static_cast<uint8_t>(component_count_)); - for (unsigned i=0; i<component_count_; i++) + msgpack::pack(ss, std::make_pair(pimpl_->local_ufrag_, pimpl_->local_pwd_)); + msgpack::pack(ss, static_cast<uint8_t>(pimpl_->component_count_)); + for (unsigned i=0; i<pimpl_->component_count_; i++) msgpack::pack(ss, getLocalCandidates(i)); auto str(ss.str()); return std::vector<uint8_t>(str.begin(), str.end()); } -void -IceTransport::onReceiveData(unsigned comp_id, void *pkt, pj_size_t size) -{ - if (!comp_id or comp_id > component_count_) { - RING_ERR("rx: invalid comp_id (%u)", comp_id); - return; - } - if (!size) - return; - auto& io = compIO_[comp_id-1]; - std::lock_guard<std::mutex> lk(io.mutex); - if (io.cb) { - io.cb((uint8_t*)pkt, size); - } else { - io.queue.emplace_back(pkt, size); - io.cv.notify_one(); - } -} - bool IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand) { @@ -859,7 +1013,7 @@ IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand) af = pj_AF_INET6(); else { af = pj_AF_INET(); - onlyIPv4Private_ &= IpAddr(ipaddr).isPrivate(); + pimpl_->onlyIPv4Private_ &= IpAddr(ipaddr).isPrivate(); } tmpaddr = pj_str(ipaddr); @@ -871,7 +1025,7 @@ IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand) } pj_sockaddr_set_port(&cand.addr, (pj_uint16_t)port); - pj_strdup2(pool_.get(), &cand.foundation, foundation); + pj_strdup2(pimpl_->pool_.get(), &cand.foundation, foundation); return true; } @@ -880,7 +1034,7 @@ ssize_t IceTransport::recv(int comp_id, unsigned char* buf, size_t len) { sip_utils::register_thread(); - auto& io = compIO_[comp_id]; + auto& io = pimpl_->compIO_[comp_id]; std::lock_guard<std::mutex> lk(io.mutex); if (io.queue.empty()) @@ -897,7 +1051,7 @@ IceTransport::recv(int comp_id, unsigned char* buf, size_t len) void IceTransport::setOnRecv(unsigned comp_id, IceRecvCb cb) { - auto& io = compIO_[comp_id]; + auto& io = pimpl_->compIO_[comp_id]; std::lock_guard<std::mutex> lk(io.mutex); io.cb = cb; @@ -919,13 +1073,13 @@ IceTransport::send(int comp_id, const unsigned char* buf, size_t len) errno = EINVAL; return -1; } - auto status = pj_ice_strans_sendto(icest_.get(), comp_id+1, buf, len, remote.pjPtr(), remote.getLength()); + auto status = pj_ice_strans_sendto(pimpl_->icest_.get(), comp_id+1, buf, len, remote.pjPtr(), remote.getLength()); if (status != PJ_SUCCESS) { if (status == PJ_EBUSY) { errno = EAGAIN; } else { - last_errmsg_ = sip_utils::sip_strerror(status); - RING_ERR("[ice:%p] ice send failed: %s", this, last_errmsg_.c_str()); + pimpl_->last_errmsg_ = sip_utils::sip_strerror(status); + RING_ERR("[ice:%p] ice send failed: %s", this, pimpl_->last_errmsg_.c_str()); errno = EIO; } return -1; @@ -937,31 +1091,31 @@ IceTransport::send(int comp_id, const unsigned char* buf, size_t len) int IceTransport::waitForInitialization(unsigned timeout) { - std::unique_lock<std::mutex> lk(iceMutex_); - if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout), - [this]{ return _isInitialized() or _isFailed(); })) { + std::unique_lock<std::mutex> lk(pimpl_->iceMutex_); + if (!pimpl_->iceCV_.wait_for(lk, std::chrono::seconds(timeout), + [this]{ return pimpl_->_isInitialized() or pimpl_->_isFailed(); })) { RING_WARN("[ice:%p] waitForInitialization: timeout", this); return -1; } - return not _isFailed(); + return not pimpl_->_isFailed(); } int IceTransport::waitForNegotiation(unsigned timeout) { - std::unique_lock<std::mutex> lk(iceMutex_); - if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout), - [this]{ return _isRunning() or _isFailed(); })) { + std::unique_lock<std::mutex> lk(pimpl_->iceMutex_); + if (!pimpl_->iceCV_.wait_for(lk, std::chrono::seconds(timeout), + [this]{ return pimpl_->_isRunning() or pimpl_->_isFailed(); })) { RING_WARN("[ice:%p] waitForIceNegotiation: timeout", this); return -1; } - return not _isFailed(); + return not pimpl_->_isFailed(); } ssize_t IceTransport::waitForData(int comp_id, unsigned int timeout) { - auto& io = compIO_[comp_id]; + auto& io = pimpl_->compIO_[comp_id]; std::unique_lock<std::mutex> lk(io.mutex); if (!io.cv.wait_for(lk, std::chrono::milliseconds(timeout), [this, &io]{ return !io.queue.empty() or !isRunning(); })) { @@ -972,7 +1126,7 @@ IceTransport::waitForData(int comp_id, unsigned int timeout) return io.queue.front().datalen; } -//################################################################################################## +//============================================================================== IceTransportFactory::IceTransportFactory() : cp_() @@ -1020,6 +1174,8 @@ IceTransportFactory::createTransport(const char* name, int component_count, } } +//============================================================================== + void IceSocket::close() { diff --git a/src/ice_transport.h b/src/ice_transport.h index 9103e29db8..74d0cf714e 100644 --- a/src/ice_transport.h +++ b/src/ice_transport.h @@ -18,8 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef ICE_TRANSPORT_H -#define ICE_TRANSPORT_H +#pragma once #include "ice_socket.h" #include "ip_utils.h" @@ -28,15 +27,9 @@ #include <pjlib.h> #include <pjlib-util.h> -#include <map> #include <functional> #include <memory> -#include <atomic> #include <vector> -#include <queue> -#include <mutex> -#include <condition_variable> -#include <thread> namespace ring { @@ -46,7 +39,7 @@ class Controller; class IceTransport; -using IceTransportCompleteCb = std::function<void(IceTransport&, bool)>; +using IceTransportCompleteCb = std::function<void(bool)>; using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>; using IceCandidate = pj_ice_sess_cand; @@ -77,254 +70,133 @@ struct IceTransportOptions { }; class IceTransport { - public: - using Attribute = struct { - std::string ufrag; - std::string pwd; - }; - - /** - * Constructor - */ - IceTransport(const char* name, int component_count, bool master, - const IceTransportOptions& options = {}); - - /** - * Destructor - */ - ~IceTransport(); - - /** - * Set/change transport role as initiator. - * Should be called before start method. - */ - bool setInitiatorSession(); - - /** - * Set/change transport role as slave. - * Should be called before start method. - */ - bool setSlaveSession(); - - /** - * Get current state - */ - bool isInitiator() const; - - /** - * Start tranport negociation between local candidates and given remote - * to find the right candidate pair. - * This function doesn't block, the callback on_negodone_cb will be called - * with the negotiation result when operation is really done. - * Return false if negotiation cannot be started else true. - */ - bool start(const Attribute& rem_attrs, - const std::vector<IceCandidate>& rem_candidates); - bool start(const std::vector<uint8_t>& attrs_candidates); - - /** - * Stop a started or completed transport. - */ - bool stop(); - - /** - * Returns true if ICE transport has been initialized - * [mutex protected] - */ - bool isInitialized() const { - std::lock_guard<std::mutex> lk(iceMutex_); - return _isInitialized(); - } - - /** - * Returns true if ICE negotiation has been started - * [mutex protected] - */ - bool isStarted() const { - std::lock_guard<std::mutex> lk(iceMutex_); - return _isStarted(); - } - - /** - * Returns true if ICE negotiation has completed with success - * [mutex protected] - */ - bool isRunning() const { - std::lock_guard<std::mutex> lk(iceMutex_); - return _isRunning(); - } - - /** - * Returns true if ICE transport is in failure state - * [mutex protected] - */ - bool isFailed() const { - std::lock_guard<std::mutex> lk(iceMutex_); - return _isFailed(); - } - - IpAddr getLocalAddress(unsigned comp_id) const; - - IpAddr getRemoteAddress(unsigned comp_id) const; - - std::string getLastErrMsg() const; - - IpAddr getDefaultLocalAddress() const { - return getLocalAddress(0); - } - - bool registerPublicIP(unsigned compId, const IpAddr& publicIP); - - /** - * Return ICE session attributes - */ - const Attribute getLocalAttributes() const; - - /** - * Return ICE session attributes - */ - std::vector<std::string> getLocalCandidates(unsigned comp_id) const; - - /** - * Returns serialized ICE attributes and candidates. - */ - std::vector<uint8_t> packIceMsg() const; - - bool getCandidateFromSDP(const std::string& line, IceCandidate& cand); - - // I/O methods - - void setOnRecv(unsigned comp_id, IceRecvCb cb); - - ssize_t recv(int comp_id, unsigned char* buf, size_t len); - - ssize_t send(int comp_id, const unsigned char* buf, size_t len); - - int waitForInitialization(unsigned timeout); - - int waitForNegotiation(unsigned timeout); - - ssize_t waitForData(int comp_id, unsigned int timeout); - - unsigned getComponentCount() const {return component_count_;} - - private: - static constexpr int MAX_CANDIDATES {32}; - - // New line character used for (de)serialisation - static constexpr char NEW_LINE = '\n'; - - static void cb_on_rx_data(pj_ice_strans *ice_st, - unsigned comp_id, - void *pkt, pj_size_t size, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len); - - static void cb_on_ice_complete(pj_ice_strans *ice_st, - pj_ice_strans_op op, - pj_status_t status); - - struct IceSTransDeleter { - void operator ()(pj_ice_strans* ptr) { - pj_ice_strans_stop_ice(ptr); - pj_ice_strans_destroy(ptr); - } - }; - - void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, - pj_status_t status); - - void onReceiveData(unsigned comp_id, void *pkt, pj_size_t size); - - bool createIceSession(pj_ice_sess_role role); - - void getUFragPwd(); - - void getDefaultCanditates(); - - // Non-mutex protected of public versions - bool _isInitialized() const; - bool _isStarted() const; - bool _isRunning() const; - bool _isFailed() const; +public: + using Attribute = struct { + std::string ufrag; + std::string pwd; + }; - std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; - IceTransportCompleteCb on_initdone_cb_; - IceTransportCompleteCb on_negodone_cb_; - std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_; - unsigned component_count_; - pj_ice_sess_cand cand_[MAX_CANDIDATES] {}; - std::string local_ufrag_; - std::string local_pwd_; - pj_sockaddr remoteAddr_; - std::condition_variable iceCV_ {}; - mutable std::mutex iceMutex_ {}; - pj_ice_strans_cfg config_; - std::string last_errmsg_; + /** + * Constructor + */ + IceTransport(const char* name, int component_count, bool master, + const IceTransportOptions& options = {}); - struct Packet { - Packet(void *pkt, pj_size_t size); - std::unique_ptr<char[]> data; - size_t datalen; - }; - struct ComponentIO { - std::mutex mutex; - std::condition_variable cv; - std::deque<Packet> queue; - IceRecvCb cb; - }; - std::vector<ComponentIO> compIO_; + /** + * Get current state + */ + bool isInitiator() const; - std::atomic_bool initiatorSession_ {true}; + /** + * Start tranport negociation between local candidates and given remote + * to find the right candidate pair. + * This function doesn't block, the callback on_negodone_cb will be called + * with the negotiation result when operation is really done. + * Return false if negotiation cannot be started else true. + */ + bool start(const Attribute& rem_attrs, + const std::vector<IceCandidate>& rem_candidates); + bool start(const std::vector<uint8_t>& attrs_candidates); - /** - * Returns the IP of each candidate for a given component in the ICE session - */ - std::vector<IpAddr> getLocalCandidatesAddr(unsigned comp_id) const; + /** + * Stop a started or completed transport. + */ + bool stop(); - /** - * Adds a reflective candidate to ICE session - * Must be called before negotiation - */ - void addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr); + /** + * Returns true if ICE transport has been initialized + * [mutex protected] + */ + bool isInitialized() const; - /** - * Creates UPnP port mappings and adds ICE candidates based on those mappings - */ - void selectUPnPIceCandidates(); + /** + * Returns true if ICE negotiation has been started + * [mutex protected] + */ + bool isStarted() const; - std::unique_ptr<upnp::Controller> upnp_; + /** + * Returns true if ICE negotiation has completed with success + * [mutex protected] + */ + bool isRunning() const; - bool onlyIPv4Private_ {true}; - - // IO/Timer events are handled by following thread - std::thread thread_; - std::atomic_bool threadTerminateFlags_ {false}; - void handleEvents(unsigned max_msec); + /** + * Returns true if ICE transport is in failure state + * [mutex protected] + */ + bool isFailed() const; + + IpAddr getLocalAddress(unsigned comp_id) const; + + IpAddr getRemoteAddress(unsigned comp_id) const; + + std::string getLastErrMsg() const; + + IpAddr getDefaultLocalAddress() const { + return getLocalAddress(0); + } + + bool registerPublicIP(unsigned compId, const IpAddr& publicIP); + + /** + * Return ICE session attributes + */ + const Attribute getLocalAttributes() const; + + /** + * Return ICE session attributes + */ + std::vector<std::string> getLocalCandidates(unsigned comp_id) const; + + /** + * Returns serialized ICE attributes and candidates. + */ + std::vector<uint8_t> packIceMsg() const; + + bool getCandidateFromSDP(const std::string& line, IceCandidate& cand); + + // I/O methods + + void setOnRecv(unsigned comp_id, IceRecvCb cb); + + ssize_t recv(int comp_id, unsigned char* buf, size_t len); + + ssize_t send(int comp_id, const unsigned char* buf, size_t len); + + int waitForInitialization(unsigned timeout); + + int waitForNegotiation(unsigned timeout); + + ssize_t waitForData(int comp_id, unsigned int timeout); + + unsigned getComponentCount() const; + +private: + class Impl; + std::unique_ptr<Impl> pimpl_; }; class IceTransportFactory { - public: - IceTransportFactory(); - ~IceTransportFactory(); - - std::shared_ptr<IceTransport> createTransport(const char* name, - int component_count, - bool master, - const IceTransportOptions& options = {}); - - /** - * PJSIP specifics - */ - pj_ice_strans_cfg getIceCfg() const { return ice_cfg_; } - pj_pool_factory* getPoolFactory() { return &cp_.factory; } - - private: - pj_caching_pool cp_; - std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; - pj_ice_strans_cfg ice_cfg_; +public: + IceTransportFactory(); + ~IceTransportFactory(); + + std::shared_ptr<IceTransport> createTransport(const char* name, + int component_count, + bool master, + const IceTransportOptions& options = {}); + + /** + * PJSIP specifics + */ + pj_ice_strans_cfg getIceCfg() const { return ice_cfg_; } + pj_pool_factory* getPoolFactory() { return &cp_.factory; } + +private: + pj_caching_pool cp_; + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; + pj_ice_strans_cfg ice_cfg_; }; }; - -#endif /* ICE_TRANSPORT_H */ -- GitLab