ice_transport.cpp 55.7 KB
Newer Older
1
/*
2
 *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3
 *
4 5 6 7 8 9
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
Guillaume Roguez's avatar
Guillaume Roguez committed
10
 *
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#include "ice_transport.h"
#include "ice_socket.h"
#include "logger.h"
#include "sip/sip_utils.h"
#include "manager.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
26
#include "upnp/upnp_control.h"
27
#include "transport/peer_channel.h"
28
#include "dring/callmanager_interface.h"
29 30

#include <pjlib.h>
31

32 33 34 35 36 37
#include <map>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
38
#include <utility>
Guillaume Roguez's avatar
Guillaume Roguez committed
39
#include <tuple>
40 41
#include <algorithm>
#include <sstream>
42 43
#include <chrono>
#include <thread>
44
#include <cerrno>
45

46 47
#include "pj/limits.h"

Sébastien Blin's avatar
Sébastien Blin committed
48 49 50 51
#define TRY(ret) \
    do { \
        if ((ret) != PJ_SUCCESS) \
            throw std::runtime_error(#ret " failed"); \
52 53
    } while (0)

Adrien Béraud's avatar
Adrien Béraud committed
54
namespace jami {
55

Guillaume Roguez's avatar
Guillaume Roguez committed
56
static constexpr unsigned STUN_MAX_PACKET_SIZE {8192};
57 58 59
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};
60
static constexpr int MAX_DESTRUCTION_TIMEOUT {3};
Guillaume Roguez's avatar
Guillaume Roguez committed
61

62 63
//==============================================================================

64
using MutexGuard = std::lock_guard<std::mutex>;
65
using MutexLock = std::unique_lock<std::mutex>;
66

Sébastien Blin's avatar
Sébastien Blin committed
67
namespace {
68 69 70

struct IceSTransDeleter
{
Sébastien Blin's avatar
Sébastien Blin committed
71 72
    void operator()(pj_ice_strans* ptr)
    {
73 74 75 76 77
        pj_ice_strans_stop_ice(ptr);
        pj_ice_strans_destroy(ptr);
    }
};

Sébastien Blin's avatar
Sébastien Blin committed
78
} // namespace
79 80 81 82 83 84 85 86 87

//==============================================================================

class IceTransport::Impl
{
public:
    Impl(const char* name, int component_count, bool master, const IceTransportOptions& options);
    ~Impl();

Sébastien Blin's avatar
Sébastien Blin committed
88
    void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status);
89

Sébastien Blin's avatar
Sébastien Blin committed
90
    void onReceiveData(unsigned comp_id, void* pkt, pj_size_t size);
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

    /**
     * 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;

119 120 121 122
    std::unique_ptr<pj_pool_t, std::function<void(pj_pool_t*)>> pool_ {};
    IceTransportCompleteCb on_initdone_cb_ {};
    IceTransportCompleteCb on_negodone_cb_ {};
    IceRecvInfo on_recv_cb_ {};
123
    mutable std::mutex iceMutex_ {};
124
    std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_;
125
    unsigned component_count_ {};
126
    pj_ice_sess_cand cand_[MAX_CANDIDATES] {};
127 128 129
    std::string local_ufrag_ {};
    std::string local_pwd_ {};
    pj_sockaddr remoteAddr_ {};
130
    std::condition_variable iceCV_ {};
131 132
    pj_ice_strans_cfg config_ {};
    std::string last_errmsg_ {};
133

134 135
    std::atomic_bool is_stopped_ {false};

Sébastien Blin's avatar
Sébastien Blin committed
136 137 138 139 140
    struct Packet
    {
        Packet(void* pkt, pj_size_t size)
            : data {reinterpret_cast<char*>(pkt), reinterpret_cast<char*>(pkt) + size}
        {}
141
        std::vector<char> data {};
142 143
    };

144
    std::vector<PeerChannel> peerChannels_ {};
145

Sébastien Blin's avatar
Sébastien Blin committed
146 147
    struct ComponentIO
    {
148 149 150 151 152 153
        std::mutex mutex;
        std::condition_variable cv;
        std::deque<Packet> queue;
        IceRecvCb cb;
    };

154
    std::vector<ComponentIO> compIO_ {};
155 156 157 158 159 160

    std::atomic_bool initiatorSession_ {true};

    /**
     * Returns the IP of each candidate for a given component in the ICE session
     */
Sébastien Blin's avatar
Sébastien Blin committed
161 162
    struct LocalCandidate
    {
Sébastien Blin's avatar
Sébastien Blin committed
163 164 165 166
        IpAddr addr;
        pj_ice_cand_transport transport;
    };
    std::vector<LocalCandidate> getLocalICECandidates(unsigned comp_id) const;
167 168 169 170 171

    /**
     * Adds a reflective candidate to ICE session
     * Must be called before negotiation
     */
Sébastien Blin's avatar
Sébastien Blin committed
172 173 174
    void addReflectiveCandidate(int comp_id,
                                const IpAddr& base,
                                const IpAddr& addr,
Sébastien Blin's avatar
Sébastien Blin committed
175
                                const pj_ice_cand_transport& transport);
176 177 178 179 180 181

    /**
     * Creates UPnP port mappings and adds ICE candidates based on those mappings
     */
    void selectUPnPIceCandidates();

182 183 184
    void setDefaultRemoteAddress(int comp_id, const IpAddr& addr);
    const IpAddr& getDefaultRemoteAddress(int comp_id) const;

185 186
    std::unique_ptr<upnp::Controller> upnp_ {};
    std::mutex upnpMutex_ {};
187 188 189 190

    bool onlyIPv4Private_ {true};

    // IO/Timer events are handled by following thread
191
    std::thread thread_ {};
192
    std::atomic_bool threadTerminateFlags_ {false};
193
    bool handleEvents(unsigned max_msec);
194 195

    // Wait data on components
196
    pj_ssize_t lastSentLen_ {};
197
    std::condition_variable waitDataCv_ = {};
198

Sébastien Blin's avatar
Sébastien Blin committed
199
    std::atomic_bool destroying_ {false};
200
    onShutdownCb scb {};
201 202

    // Default remote adresses
203
    std::vector<IpAddr> iceDefaultRemoteAddr_ {};
204 205 206
};

//==============================================================================
207

Guillaume Roguez's avatar
Guillaume Roguez committed
208 209 210 211 212 213 214 215 216 217 218 219
/**
 * Add stun/turn servers or default host as candidates
 */
static void
add_stun_server(pj_ice_strans_cfg& cfg, int af)
{
    if (cfg.stun_tp_cnt >= PJ_ICE_MAX_STUN)
        throw std::runtime_error("Too many STUN servers");
    auto& stun = cfg.stun_tp[cfg.stun_tp_cnt++];

    pj_ice_strans_stun_cfg_default(&stun);
    stun.cfg.max_pkt_size = STUN_MAX_PACKET_SIZE;
220 221
    stun.af = af;
    stun.conn_type = cfg.stun.conn_type;
Guillaume Roguez's avatar
Guillaume Roguez committed
222

Sébastien Blin's avatar
Sébastien Blin committed
223
    JAMI_DBG("[ice (%s)] added host stun server", (cfg.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"));
Guillaume Roguez's avatar
Guillaume Roguez committed
224 225 226
}

static void
227
add_stun_server(pj_pool_t& pool, pj_ice_strans_cfg& cfg, const StunServerInfo& info)
Guillaume Roguez's avatar
Guillaume Roguez committed
228 229 230 231
{
    if (cfg.stun_tp_cnt >= PJ_ICE_MAX_STUN)
        throw std::runtime_error("Too many STUN servers");

232
    IpAddr ip {info.uri};
Guillaume Roguez's avatar
Guillaume Roguez committed
233 234 235 236

    // Given URI cannot be DNS resolved or not IPv4 or IPv6?
    // This prevents a crash into PJSIP when ip.toString() is called.
    if (ip.getFamily() == AF_UNSPEC) {
Sébastien Blin's avatar
Sébastien Blin committed
237 238 239
        JAMI_DBG("[ice (%s)] STUN server '%s' not used, unresolvable address",
                 (cfg.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
                 info.uri.c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
240 241 242 243 244 245 246
        return;
    }

    auto& stun = cfg.stun_tp[cfg.stun_tp_cnt++];
    pj_ice_strans_stun_cfg_default(&stun);
    pj_strdup2_with_null(&pool, &stun.server, ip.toString().c_str());
    stun.af = ip.getFamily();
247 248
    if (!(stun.port = ip.getPort()))
        stun.port = PJ_STUN_PORT;
Guillaume Roguez's avatar
Guillaume Roguez committed
249
    stun.cfg.max_pkt_size = STUN_MAX_PACKET_SIZE;
250
    stun.conn_type = cfg.stun.conn_type;
Guillaume Roguez's avatar
Guillaume Roguez committed
251

Sébastien Blin's avatar
Sébastien Blin committed
252 253 254 255
    JAMI_DBG("[ice (%s)] added stun server '%s', port %u",
             (cfg.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
             pj_strbuf(&stun.server),
             stun.port);
Guillaume Roguez's avatar
Guillaume Roguez committed
256 257 258
}

static void
259
add_turn_server(pj_pool_t& pool, pj_ice_strans_cfg& cfg, const TurnServerInfo& info)
Guillaume Roguez's avatar
Guillaume Roguez committed
260 261 262
{
    if (cfg.turn_tp_cnt >= PJ_ICE_MAX_TURN)
        throw std::runtime_error("Too many TURN servers");
263 264

    IpAddr ip {info.uri};
Guillaume Roguez's avatar
Guillaume Roguez committed
265 266 267

    // Same comment as add_stun_server()
    if (ip.getFamily() == AF_UNSPEC) {
Sébastien Blin's avatar
Sébastien Blin committed
268 269 270
        JAMI_DBG("[ice (%s)] TURN server '%s' not used, unresolvable address",
                 (cfg.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
                 info.uri.c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
271 272 273 274 275 276 277
        return;
    }

    auto& turn = cfg.turn_tp[cfg.turn_tp_cnt++];
    pj_ice_strans_turn_cfg_default(&turn);
    pj_strdup2_with_null(&pool, &turn.server, ip.toString().c_str());
    turn.af = ip.getFamily();
278 279
    if (!(turn.port = ip.getPort()))
        turn.port = PJ_STUN_PORT;
Guillaume Roguez's avatar
Guillaume Roguez committed
280
    turn.cfg.max_pkt_size = STUN_MAX_PACKET_SIZE;
281
    turn.conn_type = cfg.turn.conn_type;
Guillaume Roguez's avatar
Guillaume Roguez committed
282 283

    // Authorization (only static plain password supported yet)
284
    if (not info.password.empty()) {
285
        turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
Guillaume Roguez's avatar
Guillaume Roguez committed
286
        turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
Sébastien Blin's avatar
Sébastien Blin committed
287 288 289 290 291 292 293 294 295
        pj_strset(&turn.auth_cred.data.static_cred.realm,
                  (char*) info.realm.c_str(),
                  info.realm.size());
        pj_strset(&turn.auth_cred.data.static_cred.username,
                  (char*) info.username.c_str(),
                  info.username.size());
        pj_strset(&turn.auth_cred.data.static_cred.data,
                  (char*) info.password.c_str(),
                  info.password.size());
Guillaume Roguez's avatar
Guillaume Roguez committed
296 297
    }

Sébastien Blin's avatar
Sébastien Blin committed
298 299 300 301
    JAMI_DBG("[ice (%s)] added turn server '%s', port %u",
             (cfg.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
             pj_strbuf(&turn.server),
             turn.port);
Guillaume Roguez's avatar
Guillaume Roguez committed
302 303
}

304
//==============================================================================
305

Sébastien Blin's avatar
Sébastien Blin committed
306 307 308
IceTransport::Impl::Impl(const char* name,
                         int component_count,
                         bool master,
309
                         const IceTransportOptions& options)
Sébastien Blin's avatar
Sébastien Blin committed
310 311 312 313 314
    : pool_(nullptr,
            [](pj_pool_t* pool) {
                sip_utils::register_thread();
                pj_pool_release(pool);
            })
315 316
    , on_initdone_cb_(options.onInitDone)
    , on_negodone_cb_(options.onNegoDone)
317
    , on_recv_cb_(options.onRecvReady)
318 319
    , component_count_(component_count)
    , compIO_(component_count)
Guillaume Roguez's avatar
Guillaume Roguez committed
320
    , initiatorSession_(master)
321
    , thread_()
322
    , iceDefaultRemoteAddr_(component_count)
323
{
324
    sip_utils::register_thread();
325
    if (options.upnpEnable)
326
        upnp_.reset(new upnp::Controller(false));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
327

Sébastien Blin's avatar
Sébastien Blin committed
328
    auto& iceTransportFactory = Manager::instance().getIceTransportFactory();
329
    config_ = iceTransportFactory.getIceCfg(); // config copy
330
    if (options.tcpEnable) {
331
        config_.protocol = PJ_ICE_TP_TCP;
Sébastien Blin's avatar
Sébastien Blin committed
332 333
        config_.stun.conn_type = PJ_STUN_TP_TCP;
        config_.turn.conn_type = PJ_TURN_TP_TCP;
334
    } else {
335
        config_.protocol = PJ_ICE_TP_UDP;
Sébastien Blin's avatar
Sébastien Blin committed
336 337
        config_.stun.conn_type = PJ_STUN_TP_UDP;
        config_.turn.conn_type = PJ_TURN_TP_UDP;
338 339 340 341 342 343 344 345 346 347 348 349 350
    }

    if (options.aggressive) {
        config_.opt.aggressive = PJ_TRUE;
    } else {
        config_.opt.aggressive = PJ_FALSE;
    }

    peerChannels_.resize(component_count_ + 1);

    // Add local hosts (IPv4, IPv6) as stun candidates
    add_stun_server(config_, pj_AF_INET6());
    add_stun_server(config_, pj_AF_INET());
351

Sébastien Blin's avatar
Sébastien Blin committed
352 353
    pool_.reset(
        pj_pool_create(iceTransportFactory.getPoolFactory(), "IceTransport.pool", 512, 512, NULL));
354 355 356 357
    if (not pool_)
        throw std::runtime_error("pj_pool_create() failed");

    pj_ice_strans_cb icecb;
358
    pj_bzero(&icecb, sizeof(icecb));
359

Sébastien Blin's avatar
Sébastien Blin committed
360 361 362 363 364 365
    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*/) {
366 367 368
        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
            tr->onReceiveData(comp_id, pkt, size);
        else
Adrien Béraud's avatar
Adrien Béraud committed
369
            JAMI_WARN("null IceTransport");
370 371
    };

Sébastien Blin's avatar
Sébastien Blin committed
372
    icecb.on_ice_complete = [](pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status) {
373 374 375
        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
            tr->onComplete(ice_st, op, status);
        else
Adrien Béraud's avatar
Adrien Béraud committed
376
            JAMI_WARN("null IceTransport");
377
    };
378

jrun's avatar
jrun committed
379
    icecb.on_data_sent = [](pj_ice_strans* ice_st, pj_ssize_t size) {
380
        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) {
381 382
            std::lock_guard<std::mutex> lk(tr->iceMutex_);
            tr->lastSentLen_ += size;
383 384 385 386 387
            tr->waitDataCv_.notify_all();
        } else
            JAMI_WARN("null IceTransport");
    };

388 389
    icecb.on_destroy = [](pj_ice_strans* ice_st) {
        if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) {
Sébastien Blin's avatar
Sébastien Blin committed
390 391
            tr->destroying_ = true;
            tr->waitDataCv_.notify_all();
392 393 394 395 396 397 398
            if (tr->scb)
                tr->scb();
        } else {
            JAMI_WARN("null IceTransport");
        }
    };

399 400 401
    // Add STUN servers
    for (auto& server : options.stunServers)
        add_stun_server(*pool_, config_, server);
402

403 404 405
    // Add TURN servers
    for (auto& server : options.turnServers)
        add_turn_server(*pool_, config_, server);
406

407
    static constexpr auto IOQUEUE_MAX_HANDLES = std::min(PJ_IOQUEUE_MAX_HANDLES, 64);
Sébastien Blin's avatar
Sébastien Blin committed
408 409
    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));
410

411
    pj_ice_strans* icest = nullptr;
Sébastien Blin's avatar
Sébastien Blin committed
412
    pj_status_t status = pj_ice_strans_create(name, &config_, component_count, this, &icecb, &icest);
413 414 415 416 417 418

    if (status != PJ_SUCCESS || icest == nullptr) {
        throw std::runtime_error("pj_ice_strans_create() failed");
    }

    // Must be created after any potential failure
Sébastien Blin's avatar
Sébastien Blin committed
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    thread_ = std::thread([this] {
        sip_utils::register_thread();
        while (not threadTerminateFlags_) {
            // NOTE: handleEvents can return false in this case
            // but here we don't care if there is event or not.
            handleEvents(500); // limit polling to 500ms
        }
        // NOTE: This last handleEvents is necessary to close TURN socket.
        // Because when destroying the TURN session pjproject creates a pj_timer
        // to postpone the TURN destruction. This timer is only called if we poll
        // the event queue.
        auto started_destruction = std::chrono::system_clock::now();
        while (handleEvents(500)) {
            if (std::chrono::system_clock::now() - started_destruction
                > std::chrono::seconds(MAX_DESTRUCTION_TIMEOUT)) {
                // If the transport is not closed after 3 seconds, avoid blocking
                break;
436
            }
Sébastien Blin's avatar
Sébastien Blin committed
437 438
        }
    });
439 440 441

    // Init to invalid adresses
    iceDefaultRemoteAddr_.reserve(component_count);
442 443
}

444
IceTransport::Impl::~Impl()
445
{
Adrien Béraud's avatar
Adrien Béraud committed
446
    JAMI_DBG("[ice:%p] destroying", this);
447
    sip_utils::register_thread();
448
    threadTerminateFlags_ = true;
449 450
    iceCV_.notify_all();

451 452 453
    if (thread_.joinable())
        thread_.join();

454 455 456 457 458
    {
        std::lock_guard<std::mutex> lk {iceMutex_};
        icest_.reset(); // must be done before ioqueue/timer destruction
    }

459 460 461 462 463 464 465
    if (config_.stun_cfg.ioqueue)
        pj_ioqueue_destroy(config_.stun_cfg.ioqueue);

    if (config_.stun_cfg.timer_heap)
        pj_timer_heap_destroy(config_.stun_cfg.timer_heap);
}

466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
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;
}

504
bool
505
IceTransport::Impl::handleEvents(unsigned max_msec)
506 507 508 509
{
    // By tests, never seen more than two events per 500ms
    static constexpr auto MAX_NET_EVENTS = 2;

510 511
    pj_time_val max_timeout = {0, 0};
    pj_time_val timeout = {0, 0};
512 513 514 515 516 517
    unsigned net_event_count = 0;

    max_timeout.msec = max_msec;

    timeout.sec = timeout.msec = 0;
    pj_timer_heap_poll(config_.stun_cfg.timer_heap, &timeout);
518
    auto ret = timeout.msec != PJ_MAXINT32;
519 520 521 522 523 524 525 526 527 528 529 530

    // timeout limitation
    if (timeout.msec >= 1000)
        timeout.msec = 999;
    if (PJ_TIME_VAL_GT(timeout, max_timeout))
        timeout = max_timeout;

    do {
        auto n_events = pj_ioqueue_poll(config_.stun_cfg.ioqueue, &timeout);

        // timeout
        if (not n_events)
531
            return ret;
532 533 534 535 536

        // error
        if (n_events < 0) {
            const auto err = pj_get_os_error();
            // Kept as debug as some errors are "normal" in regular context
537
            last_errmsg_ = sip_utils::sip_strerror(err);
Adrien Béraud's avatar
Adrien Béraud committed
538
            JAMI_DBG("[ice:%p] ioqueue error %d: %s", this, err, last_errmsg_.c_str());
539
            std::this_thread::sleep_for(std::chrono::milliseconds(PJ_TIME_VAL_MSEC(timeout)));
540
            return ret;
541 542 543 544 545
        }

        net_event_count += n_events;
        timeout.sec = timeout.msec = 0;
    } while (net_event_count < MAX_NET_EVENTS);
546
    return ret;
547
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
548

549
void
550
IceTransport::Impl::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status)
551
{
Sébastien Blin's avatar
Sébastien Blin committed
552 553 554
    const char* opname = op == PJ_ICE_STRANS_OP_INIT
                             ? "initialization"
                             : op == PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op";
555 556

    const bool done = status == PJ_SUCCESS;
557
    if (done) {
Sébastien Blin's avatar
Sébastien Blin committed
558 559 560 561 562
        JAMI_DBG("[ice:%p] %s %s success",
                 this,
                 (config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
                 opname);
    } else {
563
        last_errmsg_ = sip_utils::sip_strerror(status);
Sébastien Blin's avatar
Sébastien Blin committed
564 565 566 567 568
        JAMI_ERR("[ice:%p] %s %s failed: %s",
                 this,
                 (config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
                 opname,
                 last_errmsg_.c_str());
569
    }
570

571
    {
572
        std::lock_guard<std::mutex> lk(iceMutex_);
Guillaume Roguez's avatar
Guillaume Roguez committed
573 574 575 576 577 578 579 580 581 582
        if (!icest_.get())
            icest_.reset(ice_st);
    }

    if (done and op == PJ_ICE_STRANS_OP_INIT) {
        if (initiatorSession_)
            setInitiatorSession();
        else
            setSlaveSession();
        selectUPnPIceCandidates();
583
    }
584 585

    if (op == PJ_ICE_STRANS_OP_INIT and on_initdone_cb_)
586
        on_initdone_cb_(done);
587 588 589 590
    else if (op == PJ_ICE_STRANS_OP_NEGOTIATION) {
        if (done) {
            // Dump of connection pairs
            std::stringstream out;
Sébastien Blin's avatar
Sébastien Blin committed
591
            for (unsigned i = 0; i < component_count_; ++i) {
592 593
                auto laddr = getLocalAddress(i);
                auto raddr = getRemoteAddress(i);
594
                if (laddr and raddr) {
Sébastien Blin's avatar
Sébastien Blin committed
595 596
                    out << " [" << i << "] " << laddr.toString(true, true) << " <-> "
                        << raddr.toString(true, true) << '\n';
597 598 599
                } else {
                    out << " [" << i << "] disabled\n";
                }
600
            }
Sébastien Blin's avatar
Sébastien Blin committed
601 602 603 604
            JAMI_DBG("[ice:%p] %s connection pairs (local <-> remote):\n%s",
                     this,
                     (config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
                     out.str().c_str());
605 606
        }
        if (on_negodone_cb_)
607
            on_negodone_cb_(done);
608
    }
609

Guillaume Roguez's avatar
Guillaume Roguez committed
610
    // Unlock waitForXXX APIs
611
    iceCV_.notify_all();
612 613
}

614 615 616
bool
IceTransport::Impl::setInitiatorSession()
{
Sébastien Blin's avatar
Sébastien Blin committed
617
    JAMI_DBG("[ice:%p] as master", this);
618 619 620 621 622
    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);
Adrien Béraud's avatar
Adrien Béraud committed
623
            JAMI_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str());
624 625 626 627 628 629 630 631 632 633
            return false;
        }
        return true;
    }
    return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING);
}

bool
IceTransport::Impl::setSlaveSession()
{
Sébastien Blin's avatar
Sébastien Blin committed
634
    JAMI_DBG("[ice:%p] as slave", this);
635 636 637 638 639
    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);
Adrien Béraud's avatar
Adrien Béraud committed
640
            JAMI_ERR("[ice:%p] role change failed: %s", this, last_errmsg_.c_str());
641 642 643 644 645 646 647 648 649 650 651 652
            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()) {
Sébastien Blin's avatar
Sébastien Blin committed
653
        if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id + 1))
654 655 656 657
            return sess->lcand->addr;
        else
            return {}; // disabled component
    } else
Adrien Béraud's avatar
Adrien Béraud committed
658
        JAMI_WARN("[ice:%p] bad call: non-negotiated transport", this);
659 660 661 662 663

    // Return the default IP (could be not nominated and valid after negotiation)
    if (_isInitialized())
        return cand_[comp_id].addr;

Adrien Béraud's avatar
Adrien Béraud committed
664
    JAMI_ERR("[ice:%p] bad call: non-initialized transport", this);
665 666 667 668 669 670 671 672
    return {};
}

IpAddr
IceTransport::Impl::getRemoteAddress(unsigned comp_id) const
{
    // Return the remote IP of negotiated connection pair
    if (_isRunning()) {
Sébastien Blin's avatar
Sébastien Blin committed
673
        if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id + 1))
674 675 676 677
            return sess->rcand->addr;
        else
            return {}; // disabled component
    } else
Adrien Béraud's avatar
Adrien Béraud committed
678
        JAMI_WARN("[ice:%p] bad call: non-negotiated transport", this);
679

Adrien Béraud's avatar
Adrien Béraud committed
680
    JAMI_ERR("[ice:%p] bad call: non-negotiated transport", this);
681 682 683
    return {};
}

684
void
685
IceTransport::Impl::getUFragPwd()
686 687
{
    pj_str_t local_ufrag, local_pwd;
688
    pj_ice_strans_get_ufrag_pwd(icest_.get(), &local_ufrag, &local_pwd, nullptr, nullptr);
689 690 691 692
    local_ufrag_.assign(local_ufrag.ptr, local_ufrag.slen);
    local_pwd_.assign(local_pwd.ptr, local_pwd.slen);
}

693 694 695
void
IceTransport::Impl::getDefaultCanditates()
{
Sébastien Blin's avatar
Sébastien Blin committed
696 697
    for (unsigned i = 0; i < component_count_; ++i)
        pj_ice_strans_get_def_cand(icest_.get(), i + 1, &cand_[i]);
698 699 700 701 702 703
}

bool
IceTransport::Impl::createIceSession(pj_ice_sess_role role)
{
    if (pj_ice_strans_init_ice(icest_.get(), role, nullptr, nullptr) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
704
        JAMI_ERR("[ice:%p] pj_ice_strans_init_ice() failed", this);
705 706 707 708 709 710
        return false;
    }

    // Fetch some information on local configuration
    getUFragPwd();
    getDefaultCanditates();
Adrien Béraud's avatar
Adrien Béraud committed
711
    JAMI_DBG("[ice:%p] (local) ufrag=%s, pwd=%s", this, local_ufrag_.c_str(), local_pwd_.c_str());
712 713 714
    return true;
}

Sébastien Blin's avatar
Sébastien Blin committed
715 716
std::vector<IceTransport::Impl::LocalCandidate>
IceTransport::Impl::getLocalICECandidates(unsigned comp_id) const
717
{
Sébastien Blin's avatar
Sébastien Blin committed
718
    std::vector<LocalCandidate> cand_addrs;
719 720 721 722
    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) {
Adrien Béraud's avatar
Adrien Béraud committed
723
        JAMI_ERR("[ice:%p] pj_ice_strans_enum_cands() failed", this);
724 725 726
        return cand_addrs;
    }

Sébastien Blin's avatar
Sébastien Blin committed
727 728
    for (unsigned i = 0; i < cand_cnt; ++i) {
        cand_addrs.push_back({cand[i].addr, cand[i].transport});
Sébastien Blin's avatar
Sébastien Blin committed
729
    }
730 731 732 733

    return cand_addrs;
}

Sébastien Blin's avatar
Sébastien Blin committed
734 735 736 737 738 739
void
IceTransport::Impl::addReflectiveCandidate(int comp_id,
                                           const IpAddr& base,
                                           const IpAddr& addr,
                                           const pj_ice_cand_transport& transport)
{
740 741
    // 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 !!
Sébastien Blin's avatar
Sébastien Blin committed
742 743 744 745 746 747 748 749 750
    // 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.
751
    // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK
Adrien Béraud's avatar
Adrien Béraud committed
752
    sip_utils::register_thread();
753 754 755 756 757 758

    // 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;
759
    static constexpr int TP_STUN = 1;
760

Sébastien Blin's avatar
Sébastien Blin committed
761 762 763
    // 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
764 765 766
    int idx = -1;
    auto af = addr.getFamily();
    if (af == AF_UNSPEC) {
Sébastien Blin's avatar
Sébastien Blin committed
767
        JAMI_ERR("[ice:%p] Unable to add reflective IP %s: unknown addess familly",
Sébastien Blin's avatar
Sébastien Blin committed
768 769
                 this,
                 addr.toString().c_str());
770 771 772
        return;
    }

Sébastien Blin's avatar
Sébastien Blin committed
773
    for (unsigned i = 0; i < config_.stun_tp_cnt; ++i) {
774
        if (config_.stun_tp[i].af == af) {
775 776
            idx = i;
            break;
777 778 779
        }
    }
    if (idx < 0) {
Sébastien Blin's avatar
Sébastien Blin committed
780
        JAMI_ERR("[ice:%p] Unable to add reflective IP %s: no suitable local STUN "
Sébastien Blin's avatar
Sébastien Blin committed
781 782 783
                 "host found",
                 this,
                 addr.toString().c_str());
784 785 786 787 788
        return;
    }

    pj_ice_sess_cand cand;

789 790 791
    cand.type = PJ_ICE_CAND_TYPE_SRFLX;
    cand.status = PJ_EPENDING; // not used
    cand.comp_id = comp_id;
792
    cand.transport_id = CREATE_TP_ID(TP_STUN, idx); // HACK!!
793 794
    cand.local_pref = SRFLX_PREF;                   // reflective
    cand.transport = transport;
795 796 797 798 799 800
    /* 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);
Sébastien Blin's avatar
Sébastien Blin committed
801
    pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr);
802

803
    std::lock_guard<std::mutex> lk(iceMutex_);
Sébastien Blin's avatar
Sébastien Blin committed
804 805 806 807 808 809 810 811 812 813 814 815 816 817
    if (!icest_)
        return;
    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,
                                    cand.transport);
818 819 820

    if (ret != PJ_SUCCESS) {
        last_errmsg_ = sip_utils::sip_strerror(ret);
Sébastien Blin's avatar
Sébastien Blin committed
821 822 823 824 825 826 827 828 829
        JAMI_ERR("[ice:%p] pj_ice_sess_add_cand failed with error %d: %s",
                 this,
                 ret,
                 last_errmsg_.c_str());
        JAMI_ERR("[ice:%p] failed to add candidate for comp_id=%d : %s : %s",
                 this,
                 comp_id,
                 base.toString().c_str(),
                 addr.toString().c_str());
830
    } else {
Sébastien Blin's avatar
Sébastien Blin committed
831 832 833 834 835
        JAMI_DBG("[ice:%p] succeed to add candidate for comp_id=%d : %s : %s",
                 this,
                 comp_id,
                 base.toString().c_str(),
                 addr.toString().c_str());
836 837 838 839 840 841
    }
}

void
IceTransport::Impl::selectUPnPIceCandidates()
{
842 843 844 845
    // 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
    std::lock_guard<std::mutex> lk(upnpMutex_);
846
    if (upnp_) {
847 848
        auto publicIp = upnp_->getExternalIP();
        if (not publicIp) {
849
            JAMI_WARN("[ice:%p] Could not determine public IP for ICE candidates", this);
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
            return;
        }
        auto localIp = upnp_->getLocalIP();
        if (not localIp) {
            JAMI_WARN("[ice:%p] Could not determine local IP for ICE candidates", this);
            return;
        }
        // Use local list to store needed ports with their corresponding port type.
        auto upnpIceCntr = 0;
        for (unsigned comp_id = 1; comp_id <= component_count_; ++comp_id) {
            auto candidates = getLocalICECandidates(comp_id);
            for (const auto& candidate : candidates) {
                if (candidate.transport == PJ_CAND_TCP_ACTIVE)
                    continue; // We don't need to map port 9.
                localIp.setPort(candidate.addr.getPort());
                if (candidate.addr != localIp)
                    continue;
                uint16_t port = candidate.addr.getPort();
Sébastien Blin's avatar
Sébastien Blin committed
868 869
                auto portType = candidate.transport == PJ_CAND_UDP ? upnp::PortType::UDP
                                                                   : upnp::PortType::TCP;
870 871
                // Request port
                upnpIceCntr++;
Sébastien Blin's avatar
Sébastien Blin committed
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902
                JAMI_DBG("[ice:%p] UPnP: Trying to open port %d for ICE comp %d/%d and adding "
                         "candidate with public IP",
                         this,
                         port,
                         upnpIceCntr,
                         component_count_);
                upnp_->requestMappingAdd(
                    [this, candidate, comp_id](uint16_t port_used, bool success) {
                        if (upnp_) {
                            std::lock_guard<std::mutex> lk(upnpMutex_);
                            auto publicIp = upnp_->getExternalIP();
                            if (not publicIp) {
                                JAMI_WARN(
                                    "[ice:%p] Could not determine public IP for ICE candidates",
                                    this);
                                return;
                            }
                            auto localIp = upnp_->getLocalIP();
                            if (not localIp) {
                                JAMI_WARN(
                                    "[ice:%p] Could not determine local IP for ICE candidates",
                                    this);
                                return;
                            }
                            if (success and port_used == candidate.addr.getPort()) {
                                publicIp.setPort(port_used);
                                addReflectiveCandidate(comp_id,
                                                       candidate.addr,
                                                       publicIp,
                                                       candidate.transport);
                            }
903
                        }
Sébastien Blin's avatar
Sébastien Blin committed
904 905 906 907
                    },
                    port,
                    portType,
                    true);
908
            }
909 910 911 912
        }
    }
}

913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
void
IceTransport::Impl::setDefaultRemoteAddress(int comp_id, const IpAddr& addr)
{
    // Component ID must be valid.
    assert(comp_id < component_count_);

    iceDefaultRemoteAddr_[comp_id] = addr;
    // The port does not matter. Set it 0 to avoid confusion.
    iceDefaultRemoteAddr_[comp_id].setPort(0);
}

const IpAddr&
IceTransport::Impl::getDefaultRemoteAddress(int comp_id) const
{
    // Component ID must be valid.
    assert(comp_id < component_count_);

    return iceDefaultRemoteAddr_[comp_id];
}
932

933
void
Sébastien Blin's avatar
Sébastien Blin committed
934
IceTransport::Impl::onReceiveData(unsigned comp_id, void* pkt, pj_size_t size)
935 936
{
    if (!comp_id or comp_id > component_count_) {
Adrien Béraud's avatar
Adrien Béraud committed
937
        JAMI_ERR("rx: invalid comp_id (%u)", comp_id);
938 939 940 941
        return;
    }
    if (!size)
        return;
Sébastien Blin's avatar
Sébastien Blin committed
942
    auto& io = compIO_[comp_id - 1];
943 944
    std::unique_lock<std::mutex> lk(io.mutex);
    if (on_recv_cb_) {
945
        on_recv_cb_();
946
    }
947

948
    if (io.cb) {
Sébastien Blin's avatar
Sébastien Blin committed
949
        io.cb((uint8_t*) pkt, size);
950
    } else {
951
        std::error_code ec;
Sébastien Blin's avatar
Sébastien Blin committed
952
        auto err = peerChannels_.at(comp_id - 1).write((char*) pkt, size, ec);
953 954 955
        if (err < 0) {
            JAMI_ERR("[ice:%p] rx: channel is closed", this);
        }
956 957 958 959 960
    }
}

//==============================================================================

Sébastien Blin's avatar
Sébastien Blin committed
961 962 963
IceTransport::IceTransport(const char* name,
                           int component_count,
                           bool master,
964 965 966 967
                           const IceTransportOptions& options)
    : pimpl_ {std::make_unique<Impl>(name, component_count, master, options)}
{}

Sébastien Blin's avatar
Sébastien Blin committed
968
IceTransport::~IceTransport() {}
969

970 971
bool
IceTransport::isInitialized() const
972
{
973 974
    std::lock_guard<std::mutex> lk(pimpl_->iceMutex_);
    return pimpl_->_isInitialized();
975 976
}

977 978
bool
IceTransport::isStarted() const
979
{
980 981
    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
    return pimpl_->_isStarted();
982 983 984
}

bool
985
IceTransport::isRunning() const
986
{
987 988
    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
    return pimpl_->_isRunning();
989 990
}

991 992 993 994 995 996 997
bool
IceTransport::isStopped() const
{
    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
    return pimpl_->is_stopped_;
}

998
bool
999
IceTransport::isFailed() const
1000
{
1001 1002
    std::lock_guard<std::mutex> lk {pimpl_->iceMutex_};
    return pimpl_->_isFailed();
1003 1004
}

1005 1006
unsigned
IceTransport::getComponentCount() const
1007
{
1008 1009 1010
    return pimpl_->component_count_;
}

1011 1012 1013 1014 1015 1016 1017
bool
IceTransport::setSlaveSession()
{
    return pimpl_->setSlaveSession();
}
bool
IceTransport::setInitiatorSession()
1018
{