tls_session.cpp 48.1 KB
Newer Older
1
/*
2
 *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
3 4 5
 *
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
6
 *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 *  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.
 *
 *  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.
 */

23
#include <ip_utils.h>       // DO NOT CHANGE ORDER OF THIS INCLUDE OR MINGWIN FAILS TO BUILD
24 25 26

#include "tls_session.h"

27
#include "threadloop.h"
28 29
#include "logger.h"
#include "noncopyable.h"
30
#include "compiler_intrinsics.h"
31
#include "manager.h"
32
#include "certstore.h"
33
#include "array_size.h"
34
#include "diffie-hellman.h"
35
#include "scheduled_executor.h"
36

37
#include <gnutls/gnutls.h>
38 39 40
#include <gnutls/dtls.h>
#include <gnutls/abstract.h>

41 42 43 44 45 46 47 48
#include <list>
#include <mutex>
#include <condition_variable>
#include <utility>
#include <map>
#include <atomic>
#include <iterator>
#include <stdexcept>
49 50 51
#include <algorithm>
#include <cstring> // std::memset

52 53 54
#include <cstdlib>
#include <unistd.h>

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

57 58 59 60
static constexpr const char* DTLS_CERT_PRIORITY_STRING {"SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"};
static constexpr const char* DTLS_FULL_PRIORITY_STRING {"SECURE192:-KX-ALL:+ANON-ECDH:+ANON-DH:+SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"};
static constexpr const char* TLS_CERT_PRIORITY_STRING {"SECURE192:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"};
static constexpr const char* TLS_FULL_PRIORITY_STRING {"SECURE192:-KX-ALL:+ANON-ECDH:+ANON-DH:+SECURE192:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"};
61
static constexpr uint32_t RX_MAX_SIZE {64*1024}; // 64k = max size of a UDP packet
62
static constexpr std::size_t INPUT_MAX_SIZE {1000}; // Maximum number of packets to store before dropping (pkt size = DTLS_MTU)
63 64
static constexpr ssize_t FLOOD_THRESHOLD {4*1024};
static constexpr auto FLOOD_PAUSE = std::chrono::milliseconds(100); // Time to wait after an invalid cookie packet (anti flood attack)
65
static constexpr size_t HANDSHAKE_MAX_RETRY {64};
66
static constexpr auto DTLS_RETRANSMIT_TIMEOUT = std::chrono::milliseconds(1000); // Delay between two handshake request on DTLS
67
static constexpr auto COOKIE_TIMEOUT = std::chrono::seconds(10); // Time to wait for a cookie packet from client
68
static constexpr int MIN_MTU {512 - 20 - 8}; // minimal payload size of a DTLS packet carried by an IPv4 packet
69
static constexpr uint8_t HEARTBEAT_TRIES = 1; // Number of tries at each heartbeat ping send
70
static constexpr auto HEARTBEAT_RETRANS_TIMEOUT = std::chrono::milliseconds(700); // gnutls heartbeat retransmission timeout for each ping (in milliseconds)
71
static constexpr auto HEARTBEAT_TOTAL_TIMEOUT = HEARTBEAT_RETRANS_TIMEOUT * HEARTBEAT_TRIES; // gnutls heartbeat time limit for heartbeat procedure (in milliseconds)
72
static constexpr int MISS_ORDERING_LIMIT = 32; // maximal accepted distance of out-of-order packet (note: must be a signed type)
73
static constexpr auto RX_OOO_TIMEOUT = std::chrono::milliseconds(1500);
74
static constexpr int ASYMETRIC_TRANSPORT_MTU_OFFSET = 20; // when client, if your local IP is IPV4 and server is IPV6; you must reduce your MTU to avoid packet too big error on server side. the offset is the difference in size of IP headers
75

76 77 78 79 80 81 82
// Helper to cast any duration into an integer number of milliseconds
template <class Rep, class Period>
static std::chrono::milliseconds::rep
duration2ms(std::chrono::duration<Rep, Period> d)
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(d).count();
}
83

84 85 86 87 88 89 90 91 92
static inline uint64_t
array2uint(const std::array<uint8_t, 8>& a)
{
    uint64_t res = 0;
    for (int i=0; i < 8; ++i)
        res = (res << 8) + a[i];
    return res;
}

93 94 95 96 97
//==============================================================================

namespace {

class TlsCertificateCredendials
98 99 100 101 102 103
{
    using T = gnutls_certificate_credentials_t;
public:
    TlsCertificateCredendials() {
        int ret = gnutls_certificate_allocate_credentials(&creds_);
        if (ret < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
104
            JAMI_ERR("gnutls_certificate_allocate_credentials() failed with ret=%d", ret);
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
            throw std::bad_alloc();
        }
    }

    ~TlsCertificateCredendials() {
        gnutls_certificate_free_credentials(creds_);
    }

    operator T() { return creds_; }

private:
    NON_COPYABLE(TlsCertificateCredendials);
    T creds_;
};

120
class TlsAnonymousClientCredendials
121 122 123 124 125 126
{
    using T = gnutls_anon_client_credentials_t;
public:
    TlsAnonymousClientCredendials() {
        int ret = gnutls_anon_allocate_client_credentials(&creds_);
        if (ret < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
127
            JAMI_ERR("gnutls_anon_allocate_client_credentials() failed with ret=%d", ret);
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
            throw std::bad_alloc();
        }
    }

    ~TlsAnonymousClientCredendials() {
        gnutls_anon_free_client_credentials(creds_);
    }

    operator T() { return creds_; }

private:
    NON_COPYABLE(TlsAnonymousClientCredendials);
    T creds_;
};

143
class TlsAnonymousServerCredendials
144 145 146 147 148 149
{
    using T = gnutls_anon_server_credentials_t;
public:
    TlsAnonymousServerCredendials() {
        int ret = gnutls_anon_allocate_server_credentials(&creds_);
        if (ret < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
150
            JAMI_ERR("gnutls_anon_allocate_server_credentials() failed with ret=%d", ret);
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
            throw std::bad_alloc();
        }
    }

    ~TlsAnonymousServerCredendials() {
        gnutls_anon_free_server_credentials(creds_);
    }

    operator T() { return creds_; }

private:
    NON_COPYABLE(TlsAnonymousServerCredendials);
    T creds_;
};

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
} // namespace <anonymous>

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

class TlsSession::TlsSessionImpl
{
public:
    using clock = std::chrono::steady_clock;
    using StateHandler = std::function<TlsSessionState(TlsSessionState state)>;

    // Constants (ctor init.)
    const bool isServer_;
    const TlsParams params_;
    const TlsSessionCallbacks callbacks_;
    const bool anonymous_;

182 183
    TlsSessionImpl(SocketType& transport, const TlsParams& params,
                   const TlsSessionCallbacks& cbs, bool anonymous);
184 185 186 187 188

    ~TlsSessionImpl();

    const char* typeName() const;

189 190
    SocketType& transport_;

191 192 193 194
    // State protectors
    std::mutex stateMutex_;
    std::condition_variable stateCondition_;

195 196 197 198 199 200 201 202 203
    // State machine
    TlsSessionState handleStateSetup(TlsSessionState state);
    TlsSessionState handleStateCookie(TlsSessionState state);
    TlsSessionState handleStateHandshake(TlsSessionState state);
    TlsSessionState handleStateMtuDiscovery(TlsSessionState state);
    TlsSessionState handleStateEstablished(TlsSessionState state);
    TlsSessionState handleStateShutdown(TlsSessionState state);
    std::map<TlsSessionState, StateHandler> fsmHandlers_ {};
    std::atomic<TlsSessionState> state_ {TlsSessionState::SETUP};
204
    std::atomic<TlsSessionState> newState_ {TlsSessionState::NONE};
205
    std::atomic<int> maxPayload_ {-1};
206 207 208 209

    // IO GnuTLS <-> ICE
    std::mutex rxMutex_ {};
    std::condition_variable rxCv_ {};
210
    std::list<std::vector<ValueType>> rxQueue_ {};
211 212 213

    std::mutex reorderBufMutex_;
    bool flushProcessing_ {false}; ///< protect against recursive call to flushRxQueue
214
    std::vector<ValueType> rawPktBuf_; ///< gnutls incoming packet buffer
215 216 217 218
    uint64_t baseSeq_ {0}; ///< sequence number of first application data packet received
    uint64_t lastRxSeq_ {0}; ///< last received and valid packet sequence number
    uint64_t gapOffset_ {0}; ///< offset of first byte not received yet
    clock::time_point lastReadTime_;
219
    std::map<uint64_t, std::vector<ValueType>> reorderBuffer_ {};
220

221
    std::size_t send(const ValueType*, std::size_t, std::error_code&);
222 223 224
    ssize_t sendRaw(const void*, size_t);
    ssize_t sendRawVec(const giovec_t*, int);
    ssize_t recvRaw(void*, size_t);
225
    int waitForRawData(std::chrono::milliseconds);
226 227

    bool initFromRecordState(int offset=0);
228
    void handleDataPacket(std::vector<ValueType>&&, uint64_t);
229 230 231 232 233 234 235 236 237 238 239 240 241
    void flushRxQueue();

    // Statistics
    std::atomic<std::size_t> stRxRawPacketCnt_ {0};
    std::atomic<std::size_t> stRxRawBytesCnt_ {0};
    std::atomic<std::size_t> stRxRawPacketDropCnt_ {0};
    std::atomic<std::size_t> stTxRawPacketCnt_ {0};
    std::atomic<std::size_t> stTxRawBytesCnt_ {0};
    void dump_io_stats() const;

    std::unique_ptr<TlsAnonymousClientCredendials> cacred_; // ctor init.
    std::unique_ptr<TlsAnonymousServerCredendials> sacred_; // ctor init.
    std::unique_ptr<TlsCertificateCredendials> xcred_; // ctor init.
242
    std::mutex sessionMutex_;
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    gnutls_session_t session_ {nullptr};
    gnutls_datum_t cookie_key_ {nullptr, 0};
    gnutls_dtls_prestate_st prestate_ {};
    ssize_t cookie_count_ {0};

    TlsSessionState setupClient();
    TlsSessionState setupServer();
    void initAnonymous();
    void initCredentials();
    bool commonSessionInit();

    // FSM thread (TLS states)
    ThreadLoop thread_; // ctor init.
    bool setup();
    void process();
    void cleanup();

260 261
    ScheduledExecutor scheduler_;

262
    // Path mtu discovery
263 264 265
    std::array<int, 3> MTUS_;
    int mtuProbe_;
    int hbPingRecved_ {0};
266 267 268 269
    bool pmtudOver_ {false};
    void pathMtuHeartbeat();
};

270
TlsSession::TlsSessionImpl::TlsSessionImpl(SocketType& transport,
271 272 273
                                           const TlsParams& params,
                                           const TlsSessionCallbacks& cbs,
                                           bool anonymous)
274
    : isServer_(not transport.isInitiator())
275 276
    , params_(params)
    , callbacks_(cbs)
277
    , anonymous_(anonymous)
278
    , transport_ { transport }
279 280
    , cacred_(nullptr)
    , sacred_(nullptr)
281 282 283 284 285
    , xcred_(nullptr)
    , thread_([this] { return setup(); },
              [this] { process(); },
              [this] { cleanup(); })
{
286 287 288 289 290 291 292 293 294 295 296 297 298 299
    if (not transport_.isReliable()) {
        transport_.setOnRecv([this](const ValueType* buf, size_t len) {
                std::lock_guard<std::mutex> lk {rxMutex_};
                if (rxQueue_.size() == INPUT_MAX_SIZE) {
                    rxQueue_.pop_front(); // drop oldest packet if input buffer is full
                    ++stRxRawPacketDropCnt_;
                }
                rxQueue_.emplace_back(buf, buf+len);
                ++stRxRawPacketCnt_;
                stRxRawBytesCnt_ += len;
                rxCv_.notify_one();
                return len;
            });
    }
300 301 302 303 304

    // Run FSM into dedicated thread
    thread_.start();
}

305
TlsSession::TlsSessionImpl::~TlsSessionImpl()
306
{
307 308 309
    state_ = TlsSessionState::SHUTDOWN;
    stateCondition_.notify_all();
    rxCv_.notify_all();
310
    thread_.join();
311 312
    if (not transport_.isReliable())
        transport_.setOnRecv(nullptr);
313 314 315
}

const char*
316
TlsSession::TlsSessionImpl::typeName() const
317 318 319 320 321
{
    return isServer_ ? "server" : "client";
}

void
322
TlsSession::TlsSessionImpl::dump_io_stats() const
323
{
Adrien Béraud's avatar
Adrien Béraud committed
324
    JAMI_DBG("[TLS] RxRawPkt=%zu (%zu bytes) - TxRawPkt=%zu (%zu bytes)",
325 326
             stRxRawPacketCnt_.load(), stRxRawBytesCnt_.load(),
             stTxRawPacketCnt_.load(), stTxRawBytesCnt_.load());
327 328 329
}

TlsSessionState
330
TlsSession::TlsSessionImpl::setupClient()
331
{
332 333 334 335
    int ret;

    if (not transport_.isReliable()) {
        ret = gnutls_init(&session_, GNUTLS_CLIENT | GNUTLS_DATAGRAM);
336
        // uncoment to reactivate PMTUD
Adrien Béraud's avatar
Adrien Béraud committed
337
        // JAMI_DBG("[TLS] set heartbeat reception for retrocompatibility check on server");
338
        // gnutls_heartbeat_enable(session_,GNUTLS_HB_PEER_ALLOWED_TO_SEND);
339 340 341
    } else {
        ret = gnutls_init(&session_, GNUTLS_CLIENT);
    }
342

343
    if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
344
        JAMI_ERR("[TLS] session init failed: %s", gnutls_strerror(ret));
345 346 347 348 349 350 351 352 353 354 355
        return TlsSessionState::SHUTDOWN;
    }

    if (not commonSessionInit()) {
        return TlsSessionState::SHUTDOWN;
    }

    return TlsSessionState::HANDSHAKE;
}

TlsSessionState
356
TlsSession::TlsSessionImpl::setupServer()
357
{
358 359 360 361 362
    int ret;

    if (not transport_.isReliable()) {
        ret = gnutls_init(&session_, GNUTLS_SERVER | GNUTLS_DATAGRAM);

363
        // uncoment to reactivate PMTUD
Adrien Béraud's avatar
Adrien Béraud committed
364
        // JAMI_DBG("[TLS] set heartbeat reception");
365
        // gnutls_heartbeat_enable(session_, GNUTLS_HB_PEER_ALLOWED_TO_SEND);
366 367 368 369 370 371 372

        gnutls_dtls_prestate_set(session_, &prestate_);
    } else {
        ret = gnutls_init(&session_, GNUTLS_SERVER);
    }

    if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
373
        JAMI_ERR("[TLS] session init failed: %s", gnutls_strerror(ret));
374 375 376 377 378 379 380 381 382
        return TlsSessionState::SHUTDOWN;
    }

    gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE);

    if (not commonSessionInit())
        return TlsSessionState::SHUTDOWN;

    return TlsSessionState::HANDSHAKE;
383 384
}

385
void
386
TlsSession::TlsSessionImpl::initAnonymous()
387 388 389 390 391 392 393 394 395 396 397 398
{
    // credentials for handshaking and transmission
    if (isServer_)
        sacred_.reset(new TlsAnonymousServerCredendials());
    else
        cacred_.reset(new TlsAnonymousClientCredendials());

    // Setup DH-params for anonymous authentification
    if (isServer_) {
        if (const auto& dh_params = params_.dh_params.get().get())
            gnutls_anon_set_server_dh_params(*sacred_, dh_params);
        else
Adrien Béraud's avatar
Adrien Béraud committed
399
            JAMI_WARN("[TLS] DH params unavailable"); // YOMGUI: need to stop?
400 401 402
    }
}

403
void
404
TlsSession::TlsSessionImpl::initCredentials()
405 406 407 408 409 410 411 412
{
    int ret;

    // credentials for handshaking and transmission
    xcred_.reset(new TlsCertificateCredendials());

    if (callbacks_.verifyCertificate)
        gnutls_certificate_set_verify_function(*xcred_, [](gnutls_session_t session) -> int {
413
                auto this_ = reinterpret_cast<TlsSessionImpl*>(gnutls_session_get_ptr(session));
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
                return this_->callbacks_.verifyCertificate(session);
            });

    // Load user-given CA list
    if (not params_.ca_list.empty()) {
        // Try PEM format first
        ret = gnutls_certificate_set_x509_trust_file(*xcred_, params_.ca_list.c_str(),
                                                     GNUTLS_X509_FMT_PEM);

        // Then DER format
        if (ret < 0)
            ret = gnutls_certificate_set_x509_trust_file(*xcred_, params_.ca_list.c_str(),
                                                         GNUTLS_X509_FMT_DER);
        if (ret < 0)
            throw std::runtime_error("can't load CA " + params_.ca_list + ": "
                                     + std::string(gnutls_strerror(ret)));

Adrien Béraud's avatar
Adrien Béraud committed
431
        JAMI_DBG("[TLS] CA list %s loadev", params_.ca_list.c_str());
432
    }
433 434 435 436 437
    if (params_.peer_ca) {
        auto chain = params_.peer_ca->getChainWithRevocations();
        auto ret = gnutls_certificate_set_x509_trust(*xcred_, chain.first.data(), chain.first.size());
        if (not chain.second.empty())
            gnutls_certificate_set_x509_crl(*xcred_, chain.second.data(), chain.second.size());
Adrien Béraud's avatar
Adrien Béraud committed
438
        JAMI_DBG("[TLS] Peer CA list %lu (%lu CRLs): %d", chain.first.size(), chain.second.size(), ret);
439
    }
440 441 442

    // Load user-given identity (key and passwd)
    if (params_.cert) {
443 444 445 446 447 448 449 450 451
        std::vector<gnutls_x509_crt_t> certs;
        certs.reserve(3);
        auto crt = params_.cert;
        while (crt) {
            certs.emplace_back(crt->cert);
            crt = crt->issuer;
        }

        ret = gnutls_certificate_set_x509_key(*xcred_, certs.data(), certs.size(), params_.cert_key->x509_key);
452 453 454 455
        if (ret < 0)
            throw std::runtime_error("can't load certificate: "
                                     + std::string(gnutls_strerror(ret)));

Adrien Béraud's avatar
Adrien Béraud committed
456
        JAMI_DBG("[TLS] User identity loaded");
457 458 459 460
    }

    // Setup DH-params (server only, may block on dh_params.get())
    if (isServer_) {
Adrien Béraud's avatar
Adrien Béraud committed
461 462
        if (const auto& dh_params = params_.dh_params.get().get())
            gnutls_certificate_set_dh_params(*xcred_, dh_params);
463
        else
Adrien Béraud's avatar
Adrien Béraud committed
464
            JAMI_WARN("[TLS] DH params unavailable"); // YOMGUI: need to stop?
465 466 467 468
    }
}

bool
469
TlsSession::TlsSessionImpl::commonSessionInit()
470 471 472
{
    int ret;

473 474
    if (anonymous_) {
        // Force anonymous connection, see handleStateHandshake how we handle failures
475 476 477
        ret = gnutls_priority_set_direct(session_,
                                         transport_.isReliable() ? TLS_FULL_PRIORITY_STRING : DTLS_FULL_PRIORITY_STRING,
                                         nullptr);
478
        if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
479
            JAMI_ERR("[TLS] TLS priority set failed: %s", gnutls_strerror(ret));
480 481
            return false;
        }
482

483 484 485 486 487
        // Add anonymous credentials
        if (isServer_)
            ret = gnutls_credentials_set(session_, GNUTLS_CRD_ANON, *sacred_);
        else
            ret = gnutls_credentials_set(session_, GNUTLS_CRD_ANON, *cacred_);
488

489
        if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
490
            JAMI_ERR("[TLS] anonymous credential set failed: %s", gnutls_strerror(ret));
491 492 493 494
            return false;
        }
    } else {
        // Use a classic non-encrypted CERTIFICATE exchange method (less anonymous)
495 496 497
        ret = gnutls_priority_set_direct(session_,
                                         transport_.isReliable() ? TLS_CERT_PRIORITY_STRING : DTLS_CERT_PRIORITY_STRING,
                                         nullptr);
498
        if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
499
            JAMI_ERR("[TLS] TLS priority set failed: %s", gnutls_strerror(ret));
500 501
            return false;
        }
502 503
    }

504
    // Add certificate credentials
505 506
    ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, *xcred_);
    if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
507
        JAMI_ERR("[TLS] certificate credential set failed: %s", gnutls_strerror(ret));
508 509
        return false;
    }
510
    gnutls_certificate_send_x509_rdn_sequence(session_, 0);
511

512 513 514 515 516
    if (not transport_.isReliable()) {
        // DTLS hanshake timeouts
        auto re_tx_timeout = duration2ms(DTLS_RETRANSMIT_TIMEOUT);
        gnutls_dtls_set_timeouts(session_, re_tx_timeout,
                                 std::max(duration2ms(params_.timeout), re_tx_timeout));
517

518 519 520
        // gnutls DTLS mtu = maximum payload size given by transport
        gnutls_dtls_set_mtu(session_, transport_.maxPayload());
    }
521

522 523 524 525 526 527
    // Stuff for transport callbacks
    gnutls_session_set_ptr(session_, this);
    gnutls_transport_set_ptr(session_, this);
    gnutls_transport_set_vec_push_function(session_,
                                           [](gnutls_transport_ptr_t t, const giovec_t* iov,
                                              int iovcnt) -> ssize_t {
528
                                               auto this_ = reinterpret_cast<TlsSessionImpl*>(t);
529 530 531 532
                                               return this_->sendRawVec(iov, iovcnt);
                                           });
    gnutls_transport_set_pull_function(session_,
                                       [](gnutls_transport_ptr_t t, void* d, size_t s) -> ssize_t {
533
                                           auto this_ = reinterpret_cast<TlsSessionImpl*>(t);
534 535 536 537
                                           return this_->recvRaw(d, s);
                                       });
    gnutls_transport_set_pull_timeout_function(session_,
                                               [](gnutls_transport_ptr_t t, unsigned ms) -> int {
538
                                                   auto this_ = reinterpret_cast<TlsSessionImpl*>(t);
539
                                                   return this_->waitForRawData(std::chrono::milliseconds(ms));
540 541 542 543 544
                                               });

    return true;
}

545 546
std::size_t
TlsSession::TlsSessionImpl::send(const ValueType* tx_data, std::size_t tx_size, std::error_code& ec)
547
{
548 549 550 551
    if (state_ != TlsSessionState::ESTABLISHED) {
        ec = std::error_code(GNUTLS_E_INVALID_SESSION, std::system_category());
        return 0;
    }
552

553 554 555 556 557 558 559 560 561
    std::size_t total_written = 0;
    std::size_t max_tx_sz;

    if (transport_.isReliable())
        max_tx_sz = tx_size;
    else
        max_tx_sz = gnutls_dtls_get_data_mtu(session_);

    // Split incoming data into chunck suitable for the underlying transport
562 563
    while (total_written < tx_size) {
        auto chunck_sz = std::min(max_tx_sz, tx_size - total_written);
564
        auto data_seq = tx_data + total_written;
565
        ssize_t nwritten;
566 567
        do {
            nwritten = gnutls_record_send(session_, data_seq, chunck_sz);
568
        } while ((nwritten == GNUTLS_E_INTERRUPTED and state_ != TlsSessionState::SHUTDOWN) or nwritten == GNUTLS_E_AGAIN);
569 570 571 572 573
        if (nwritten <= 0) {
            /* Normally we would have to retry record_send but our internal
             * state has not changed, so we have to ask for more data first.
             * We will just try again later, although this should never happen.
             */
Adrien Béraud's avatar
Adrien Béraud committed
574
            JAMI_ERR() << "[TLS] send failed (only " << total_written << " bytes sent): "
575 576 577
                       << gnutls_strerror(nwritten);
            ec = std::error_code(nwritten, std::system_category());
            return 0;
578 579 580 581
        }

        total_written += nwritten;
    }
582 583

    ec.clear();
584 585 586 587 588 589
    return total_written;
}

// Called by GNUTLS to send encrypted packet to low-level transport.
// Should return a positive number indicating the bytes sent, and -1 on error.
ssize_t
590
TlsSession::TlsSessionImpl::sendRaw(const void* buf, size_t size)
591
{
592
    std::error_code ec;
593 594 595 596 597 598 599 600 601 602 603
    unsigned retry_count = 0;
    do {
        auto n = transport_.write(reinterpret_cast<const ValueType*>(buf), size, ec);
        if (!ec) {
            // log only on success
            ++stTxRawPacketCnt_;
            stTxRawBytesCnt_ += n;
            return n;
        }

        if (ec.value() == EAGAIN) {
Adrien Béraud's avatar
Adrien Béraud committed
604
            JAMI_WARN() << "[TLS] EAGAIN from transport, retry#" << ++retry_count;
605 606
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            if (retry_count == 100) {
Adrien Béraud's avatar
Adrien Béraud committed
607
                JAMI_ERR() << "[TLS] excessive retry detected, aborting";
608 609 610 611
                ec.assign(EIO, std::system_category());
            }
        }
    } while (ec.value() == EAGAIN);
612 613

    // Must be called to pass errno value to GnuTLS on Windows (cf. GnuTLS doc)
614
    gnutls_transport_set_errno(session_, ec.value());
Adrien Béraud's avatar
Adrien Béraud committed
615
    JAMI_ERR() << "[TLS] transport failure on tx: errno = " << ec.value();
616
    return -1;
617 618 619 620 621
}

// Called by GNUTLS to send encrypted packet to low-level transport.
// Should return a positive number indicating the bytes sent, and -1 on error.
ssize_t
622
TlsSession::TlsSessionImpl::sendRawVec(const giovec_t* iov, int iovcnt)
623 624 625 626 627 628
{
    ssize_t sent = 0;
    for (int i=0; i<iovcnt; ++i) {
        const giovec_t& dat = iov[i];
        ssize_t ret = sendRaw(dat.iov_base, dat.iov_len);
        if (ret < 0)
629
            return -1;
630 631 632 633 634 635 636 637 638 639
        sent += ret;
    }
    return sent;
}

// Called by GNUTLS to receive encrypted packet from low-level transport.
// Should return 0 on connection termination,
// a positive number indicating the number of bytes received,
// and -1 on error.
ssize_t
640
TlsSession::TlsSessionImpl::recvRaw(void* buf, size_t size)
641
{
642 643 644 645 646 647
    if (transport_.isReliable()) {
        std::error_code ec;
        auto count = transport_.read(reinterpret_cast<ValueType*>(buf), size, ec);
        if (!ec)
            return count;
        gnutls_transport_set_errno(session_, ec.value());
648 649 650
        return -1;
    }

651
    std::lock_guard<std::mutex> lk {rxMutex_};
652 653 654 655 656
    if (rxQueue_.empty()) {
        gnutls_transport_set_errno(session_, EAGAIN);
        return -1;
    }

657 658
    const auto& pkt = rxQueue_.front();
    const std::size_t count = std::min(pkt.size(), size);
659
    std::copy_n(pkt.begin(), count, reinterpret_cast<ValueType*>(buf));
660 661 662 663 664 665
    rxQueue_.pop_front();
    return count;
}

// Called by GNUTLS to wait for encrypted packet from low-level transport.
// 'timeout' is in milliseconds.
666
// Should return 0 on timeout, a positive number if data are available for read, or -1 on error.
667
int
668
TlsSession::TlsSessionImpl::waitForRawData(std::chrono::milliseconds timeout)
669
{
670
    if (transport_.isReliable()) {
671
        std::error_code ec;
672 673
        auto err = transport_.waitForData(timeout, ec);
        if (err <= 0) {
674 675 676 677 678
            // shutdown?
            if (state_ == TlsSessionState::SHUTDOWN) {
                gnutls_transport_set_errno(session_, EINTR);
                return -1;
            }
679 680 681 682
            if (ec) {
                gnutls_transport_set_errno(session_, ec.value());
                return -1;
            }
683 684 685 686
            return 0;
        }
        return 1;
    }
687

688 689
    // non-reliable uses callback installed with setOnRecv()
    std::unique_lock<std::mutex> lk {rxMutex_};
690
    rxCv_.wait_for(lk, timeout, [this]{ return !rxQueue_.empty() or state_ == TlsSessionState::SHUTDOWN; });
691 692 693 694
    if (state_ == TlsSessionState::SHUTDOWN) {
        gnutls_transport_set_errno(session_, EINTR);
        return -1;
    }
695
    if (rxQueue_.empty()) {
696
        JAMI_ERR("[TLS] waitForRawData: timeout after %ld ms", timeout.count());
697 698
        return 0;
    }
699 700 701 702 703 704 705 706
    return 1;
}

bool
TlsSession::TlsSessionImpl::initFromRecordState(int offset)
{
    std::array<uint8_t, 8> seq;
    if (gnutls_record_get_state(session_, 1, nullptr, nullptr, nullptr, &seq[0]) != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
707
        JAMI_ERR("[TLS] Fatal-error Unable to read initial state");
708 709
        return false;
    }
710

711 712 713
    baseSeq_ = array2uint(seq) + offset;
    gapOffset_ = baseSeq_;
    lastRxSeq_ = baseSeq_ - 1;
Adrien Béraud's avatar
Adrien Béraud committed
714
    JAMI_DBG("[TLS] Initial sequence number: %lx", baseSeq_);
715
    return true;
716 717 718
}

bool
719
TlsSession::TlsSessionImpl::setup()
720 721 722 723 724
{
    // Setup FSM
    fsmHandlers_[TlsSessionState::SETUP] = [this](TlsSessionState s){ return handleStateSetup(s); };
    fsmHandlers_[TlsSessionState::COOKIE] = [this](TlsSessionState s){ return handleStateCookie(s); };
    fsmHandlers_[TlsSessionState::HANDSHAKE] = [this](TlsSessionState s){ return handleStateHandshake(s); };
725
    fsmHandlers_[TlsSessionState::MTU_DISCOVERY] = [this](TlsSessionState s){ return handleStateMtuDiscovery(s); };
726 727 728 729 730 731 732
    fsmHandlers_[TlsSessionState::ESTABLISHED] = [this](TlsSessionState s){ return handleStateEstablished(s); };
    fsmHandlers_[TlsSessionState::SHUTDOWN] = [this](TlsSessionState s){ return handleStateShutdown(s); };

    return true;
}

void
733
TlsSession::TlsSessionImpl::cleanup()
734 735
{
    state_ = TlsSessionState::SHUTDOWN; // be sure to block any user operations
736
    stateCondition_.notify_all();
737

738 739 740 741 742 743 744 745 746 747 748 749 750
    // This will stop current read of the ice_transport.cpp
    transport_.shutdown();

    {
        std::lock_guard<std::mutex> lk(sessionMutex_);
        if (session_) {
            if (transport_.isReliable())
                gnutls_bye(session_, GNUTLS_SHUT_RDWR);
            else
                gnutls_bye(session_, GNUTLS_SHUT_WR); // not wait for a peer answer
            gnutls_deinit(session_);
            session_ = nullptr;
        }
751 752 753 754 755 756 757
    }

    if (cookie_key_.data)
        gnutls_free(cookie_key_.data);
}

TlsSessionState
758
TlsSession::TlsSessionImpl::handleStateSetup(UNUSED TlsSessionState state)
759
{
Adrien Béraud's avatar
Adrien Béraud committed
760
    JAMI_DBG("[TLS] Start %s session", typeName());
761 762

    try {
763 764
        if (anonymous_)
            initAnonymous();
765 766
        initCredentials();
    } catch (const std::exception& e) {
Adrien Béraud's avatar
Adrien Béraud committed
767
        JAMI_ERR("[TLS] authentifications init failed: %s", e.what());
768 769 770
        return TlsSessionState::SHUTDOWN;
    }

771
    if (not isServer_)
772
        return setupClient();
773 774 775 776 777 778 779

    // Extra step for DTLS-like transports
    if (not transport_.isReliable()) {
        gnutls_key_generate(&cookie_key_, GNUTLS_COOKIE_KEY_SIZE);
        return TlsSessionState::COOKIE;
    }
    return setupServer();
780 781 782
}

TlsSessionState
783
TlsSession::TlsSessionImpl::handleStateCookie(TlsSessionState state)
784
{
Adrien Béraud's avatar
Adrien Béraud committed
785
    JAMI_DBG("[TLS] SYN cookie");
786 787 788 789

    std::size_t count;
    {
        // block until rx packet or shutdown
790 791
        std::unique_lock<std::mutex> lk {rxMutex_};
        if (!rxCv_.wait_for(lk, COOKIE_TIMEOUT,
792 793
                            [this]{ return !rxQueue_.empty()
                                    or state_ == TlsSessionState::SHUTDOWN; })) {
Adrien Béraud's avatar
Adrien Béraud committed
794
            JAMI_ERR("[TLS] SYN cookie failed: timeout");
795 796 797
            return TlsSessionState::SHUTDOWN;
        }
        // Shutdown state?
798 799 800 801 802 803 804 805 806 807 808 809
        if (rxQueue_.empty())
            return TlsSessionState::SHUTDOWN;
        count = rxQueue_.front().size();
    }

    // Total bytes rx during cookie checking (see flood protection below)
    cookie_count_ += count;

    int ret;

    // Peek and verify front packet
    {
810
        std::lock_guard<std::mutex> lk {rxMutex_};
811 812 813 814 815 816 817 818 819 820 821
        auto& pkt = rxQueue_.front();
        std::memset(&prestate_, 0, sizeof(prestate_));
        ret = gnutls_dtls_cookie_verify(&cookie_key_, nullptr, 0,
                                        pkt.data(), pkt.size(), &prestate_);
    }

    if (ret < 0) {
        gnutls_dtls_cookie_send(&cookie_key_, nullptr, 0, &prestate_,
                                this,
                                [](gnutls_transport_ptr_t t, const void* d,
                                   size_t s) -> ssize_t {
822
                                    auto this_ = reinterpret_cast<TlsSessionImpl*>(t);
823 824 825 826 827
                                    return this_->sendRaw(d, s);
                                });

        // Drop front packet
        {
828
            std::lock_guard<std::mutex> lk {rxMutex_};
829 830 831 832 833 834 835
            rxQueue_.pop_front();
        }

        // Cookie may be sent on multiple network packets
        // So we retry until we get a valid cookie.
        // To protect against a flood attack we delay each retry after FLOOD_THRESHOLD rx bytes.
        if (cookie_count_ >= FLOOD_THRESHOLD) {
Adrien Béraud's avatar
Adrien Béraud committed
836
            JAMI_WARN("[TLS] flood threshold reach (retry in %zds)",
837 838 839 840 841 842 843
                      std::chrono::duration_cast<std::chrono::seconds>(FLOOD_PAUSE).count());
            dump_io_stats();
            std::this_thread::sleep_for(FLOOD_PAUSE); // flood attack protection
        }
        return state;
    }

Adrien Béraud's avatar
Adrien Béraud committed
844
    JAMI_DBG("[TLS] cookie ok");
845

846
    return setupServer();
847 848 849
}

TlsSessionState
850
TlsSession::TlsSessionImpl::handleStateHandshake(TlsSessionState state)
851
{
852 853 854
    int ret;
    size_t retry_count = 0;
    do {
Adrien Béraud's avatar
Adrien Béraud committed
855
        JAMI_DBG("[TLS] handshake");
856 857 858 859
        ret = gnutls_handshake(session_);
    } while ((ret == GNUTLS_E_INTERRUPTED   or
              ret == GNUTLS_E_AGAIN       ) and
              ++retry_count < HANDSHAKE_MAX_RETRY);
860

861 862
    // Stop on fatal error
    if (gnutls_error_is_fatal(ret)) {
Adrien Béraud's avatar
Adrien Béraud committed
863
        JAMI_ERR("[TLS] handshake failed: %s", gnutls_strerror(ret));
864 865
        return TlsSessionState::SHUTDOWN;
    }
866

867 868 869
    // Continue handshaking on non-fatal error
    if (ret != GNUTLS_E_SUCCESS) {
        // TODO: handle GNUTLS_E_LARGE_PACKET (MTU must be lowered)
870
        if (ret != GNUTLS_E_AGAIN)
Adrien Béraud's avatar
Adrien Béraud committed
871
            JAMI_DBG("[TLS] non-fatal handshake error: %s", gnutls_strerror(ret));
872
        return state;
873 874
    }

875
    // Safe-Renegotiation status shall always be true to prevent MiM attack
876 877 878
    // Following https://www.gnutls.org/manual/html_node/Safe-renegotiation.html
    // "Unlike TLS 1.2, the server is not allowed to change identities"
    // So, we don't have to check the status if we are the client
879
#if GNUTLS_VERSION_NUMBER >= 0x030605
880 881 882 883
    bool isTLS1_3 = gnutls_protocol_get_version(session_) == GNUTLS_TLS1_3;
    if (!isTLS1_3 || (isTLS1_3 && isServer_)) {
#endif
        if (!gnutls_safe_renegotiation_status(session_)) {
Adrien Béraud's avatar
Adrien Béraud committed
884
            JAMI_ERR("[TLS] server identity changed! MiM attack?");
885 886
            return TlsSessionState::SHUTDOWN;
        }
887
#if GNUTLS_VERSION_NUMBER >= 0x030605
888
    }
889
#endif
890

891
    auto desc = gnutls_session_get_desc(session_);
Adrien Béraud's avatar
Adrien Béraud committed
892
    JAMI_DBG("[TLS] session established: %s", desc);
893 894
    gnutls_free(desc);

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
895
    // Anonymous connection? rehandshake immediately with certificate authentification forced
896 897
    auto cred = gnutls_auth_get_type(session_);
    if (cred == GNUTLS_CRD_ANON) {
Adrien Béraud's avatar
Adrien Béraud committed
898
        JAMI_DBG("[TLS] renogotiate with certificate authentification");
899 900

        // Re-setup TLS algorithms priority list with only certificate based cipher suites
901 902 903
        ret = gnutls_priority_set_direct(session_,
                                         transport_.isReliable() ? TLS_CERT_PRIORITY_STRING : DTLS_CERT_PRIORITY_STRING,
                                         nullptr);
904
        if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
905
            JAMI_ERR("[TLS] session TLS cert-only priority set failed: %s", gnutls_strerror(ret));
906 907 908 909 910 911 912
            return TlsSessionState::SHUTDOWN;
        }

        // remove anon credentials and re-enable certificate ones
        gnutls_credentials_clear(session_);
        ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, *xcred_);
        if (ret != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
913
            JAMI_ERR("[TLS] session credential set failed: %s", gnutls_strerror(ret));
914 915 916 917 918 919
            return TlsSessionState::SHUTDOWN;
        }

        return state; // handshake

    } else if (cred != GNUTLS_CRD_CERTIFICATE) {
Adrien Béraud's avatar
Adrien Béraud committed
920
        JAMI_ERR("[TLS] spurious session credential (%u)", cred);
921 922 923 924 925 926 927 928 929 930 931
        return TlsSessionState::SHUTDOWN;
    }

    // Aware about certificates updates
    if (callbacks_.onCertificatesUpdate) {
        unsigned int remote_count;
        auto local = gnutls_certificate_get_ours(session_);
        auto remote = gnutls_certificate_get_peers(session_, &remote_count);
        callbacks_.onCertificatesUpdate(local, remote, remote_count);
    }

932
    return transport_.isReliable() ? TlsSessionState::ESTABLISHED : TlsSessionState::MTU_DISCOVERY;
933 934
}

935
TlsSessionState
936
TlsSession::TlsSessionImpl::handleStateMtuDiscovery(UNUSED TlsSessionState state)
937
{
938 939 940
    mtuProbe_ = transport_.maxPayload();
    assert(mtuProbe_ >= MIN_MTU);
    MTUS_ = {MIN_MTU, std::max((mtuProbe_ + MIN_MTU)/2, MIN_MTU), mtuProbe_};
941

942
    // retrocompatibility check
943 944
    if (gnutls_heartbeat_allowed(session_, GNUTLS_HB_LOCAL_ALLOWED_TO_SEND) == 1) {
        if (!isServer_) {
945
            pathMtuHeartbeat();
946
            if (state_ == TlsSessionState::SHUTDOWN) {
Adrien Béraud's avatar
Adrien Béraud committed
947
                JAMI_ERR("[TLS] session destroyed while performing PMTUD, shuting down");
948 949
                return TlsSessionState::SHUTDOWN;
            }
950 951 952
            pmtudOver_ = true;
        }
    } else {
Adrien Béraud's avatar
Adrien Béraud committed
953
        JAMI_ERR() << "[TLS] PEER HEARTBEAT DISABLED: using transport MTU value " << mtuProbe_;
954 955
        pmtudOver_ = true;
    }
956 957

    gnutls_dtls_set_mtu(session_, mtuProbe_);
958
    maxPayload_ = gnutls_dtls_get_data_mtu(session_);
959

960
    if (pmtudOver_) {
Adrien Béraud's avatar
Adrien Béraud committed
961
        JAMI_DBG() << "[TLS] maxPayload: " << maxPayload_.load();
962 963 964 965
        if (!initFromRecordState())
            return TlsSessionState::SHUTDOWN;
    }

966
    return TlsSessionState::ESTABLISHED;
967 968
}

969 970 971 972 973 974 975 976 977 978
/*
 * Path MTU discovery heuristic
 * heuristic description:
 * The two members of the current tls connection will exchange dtls heartbeat messages
 * of increasing size until the heartbeat times out which will be considered as a packet
 * drop from the network due to the size of the packet. (one retry to test for a buffer issue)
 * when timeout happens or all the values have been tested, the mtu will be returned.
 * In case of unexpected error the first (and minimal) value of the mtu array
 */
void
979
TlsSession::TlsSessionImpl::pathMtuHeartbeat()
980
{
Adrien Béraud's avatar
Adrien Béraud committed
981
    JAMI_DBG() << "[TLS] PMTUD: starting probing with " << HEARTBEAT_RETRANS_TIMEOUT.count()
982 983 984 985 986 987 988
               << "ms of retransmission timeout";

    gnutls_heartbeat_set_timeouts(session_,
                                  HEARTBEAT_RETRANS_TIMEOUT.count(),
                                  HEARTBEAT_TOTAL_TIMEOUT.count());

    int errno_send = GNUTLS_E_SUCCESS;
989 990 991 992 993 994 995
    int mtuOffset = 0;

    // when the remote (server) has a IPV6 interface selected by ICE, and local (client) has a IPV4 selected,
    // the path MTU discovery triggers errors for packets too big on server side because of different IP headers overhead.
    // Hence we have to signal to the TLS session to reduce the MTU on client size accordingly.
    if (transport_.localAddr().isIpv4() and transport_.remoteAddr().isIpv6()) {
        mtuOffset = ASYMETRIC_TRANSPORT_MTU_OFFSET;
Adrien Béraud's avatar
Adrien Béraud committed
996
        JAMI_WARN() << "[TLS] local/remote IP protocol version not alike, use an MTU offset of "
997 998 999
                    << ASYMETRIC_TRANSPORT_MTU_OFFSET << " bytes to compensate";
    }

1000
    mtuProbe_ = MTUS_[0];
1001

1002 1003 1004
    for (auto mtu: MTUS_) {
        gnutls_dtls_set_mtu(session_, mtu);
        auto data_mtu = gnutls_dtls_get_data_mtu(session_);
Adrien Béraud's avatar
Adrien Béraud committed
1005
        JAMI_DBG() << "[TLS] PMTUD: mtu " << mtu
1006
                   << ", payload " << data_mtu;
1007 1008
        auto bytesToSend = data_mtu - mtuOffset - 3; // want to know why -3? ask gnutls!

1009
        do {
1010
            errno_send = gnutls_heartbeat_ping(session_, bytesToSend, HEARTBEAT_TRIES, GNUTLS_HEARTBEAT_WAIT);
1011
        } while (errno_send == GNUTLS_E_AGAIN || (errno_send == GNUTLS_E_INTERRUPTED && state_ != TlsSessionState::SHUTDOWN));
1012

1013
        if (errno_send != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
1014
            JAMI_DBG() << "[TLS] PMTUD: mtu " << mtu << " [FAILED]";
1015
            break;
1016 1017
        }

1018
        mtuProbe_ = mtu;
Adrien Béraud's avatar
Adrien Béraud committed
1019
        JAMI_DBG() << "[TLS] PMTUD: mtu " << mtu << " [OK]";
1020 1021
    }

1022 1023
    if (errno_send == GNUTLS_E_TIMEDOUT) { // timeout is considered as a packet loss, then the good mtu is the precedent
        if (mtuProbe_ == MTUS_[0]) {
Adrien Béraud's avatar
Adrien Béraud committed
1024
            JAMI_WARN() << "[TLS] PMTUD: no response on first ping, using minimal MTU value "
1025 1026
                        << mtuProbe_;
        } else {
Adrien Béraud's avatar
Adrien Béraud committed
1027
            JAMI_WARN() << "[TLS] PMTUD: timed out, using last working mtu "
1028 1029 1030
                        << mtuProbe_;
        }
    } else if (errno_send != GNUTLS_E_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
1031
        JAMI_ERR() << "[TLS] PMTUD: failed with gnutls error '"
1032 1033
                   << gnutls_strerror(errno_send) << '\'';
    } else {
Adrien Béraud's avatar
Adrien Béraud committed
1034
        JAMI_DBG() << "[TLS] PMTUD: reached maximal value";
1035
    }
1036 1037
}

1038
void
1039
TlsSession::TlsSessionImpl::handleDataPacket(std::vector<ValueType>&& buf, uint64_t pkt_seq)
1040 1041 1042 1043 1044 1045 1046 1047
{
    // Check for a valid seq. num. delta
    int64_t seq_delta = pkt_seq - lastRxSeq_;
    if (seq_delta > 0) {
        lastRxSeq_ = pkt_seq;
    } else {
        // too old?
        if (seq_delta <= -MISS_ORDERING_LIMIT) {
Adrien Béraud's avatar
Adrien Béraud committed
1048
            JAMI_WARN("[TLS] drop old pkt: 0x%lx", pkt_seq);
1049 1050 1051 1052 1053 1054
            return;
        }

        // No duplicate check as DTLS prevents that for us (replay protection)

        // accept Out-Of-Order pkt - will be reordered by queue flush operation
Adrien Béraud's avatar
Adrien Béraud committed
1055
        JAMI_WARN("[TLS] OOO pkt: 0x%lx", pkt_seq);
1056 1057
    }

1058 1059 1060 1061 1062 1063 1064 1065 1066
    {
        std::lock_guard<std::mutex> lk {reorderBufMutex_};
        if (reorderBuffer_.empty())
            lastReadTime_ = clock::now();
        reorderBuffer_.emplace(pkt_seq, std::move(buf));
    }

    // Try to flush right now as a new packet is available
    flushRxQueue();
1067
    scheduler_.scheduleIn([this]{ flushRxQueue(); }, RX_OOO_TIMEOUT);
1068 1069 1070 1071 1072
}

///
/// Reorder and push received packet to upper layer
///
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
1073
/// \note This method must be called continuously, faster than RX_OOO_TIMEOUT
1074 1075
///
void
1076
TlsSession::TlsSessionImpl::flushRxQueue()
1077
{
1078 1079 1080 1081 1082 1083 1084 1085 1086
    // RAII bool swap
    class GuardedBoolSwap {
    public:
        explicit GuardedBoolSwap(bool& var) : var_ {var} { var_ = !var_; }
        ~GuardedBoolSwap() { var_ = !var_; }
    private:
        bool& var_;
    };

1087 1088 1089 1090
    std::unique_lock<std::mutex> lk {reorderBufMutex_};
    if (reorderBuffer_.empty())
        return;

1091 1092 1093 1094 1095 1096
    // Prevent re-entrant access as the callbacks_.onRxData() is called in unprotected region
    if (flushProcessing_)
        return;

    GuardedBoolSwap swap_flush_processing {flushProcessing_};

1097 1098
    auto now = clock::now();

1099 1100 1101
    auto item = std::begin(reorderBuffer_);
    auto next_offset = item->first;

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
1102
    // Wait for next continuous packet until timeout
1103
    if ((now - lastReadTime_) >= RX_OOO_TIMEOUT) {
1104
        // OOO packet timeout - consider waited packets as lost
1105
        if (auto lost = next_offset - gapOffset_)
Adrien Béraud's avatar
Adrien Béraud committed
1106
            JAMI_WARN("[TLS] %lu lost since 0x%lx", lost, gapOffset_);
1107
        else
Adrien Béraud's avatar
Adrien Béraud committed
1108
            JAMI_WARN("[TLS] slow flush");
1109 1110 1111 1112 1113 1114
    } else if (next_offset != gapOffset_)
        return;

    // Loop on offset-ordered received packet until a discontinuity in sequence number
    while (item != std::end(reorderBuffer_) and item->first <= next_offset) {
        auto pkt_offset = item->first;
1115 1116 1117 1118 1119
        auto pkt = std::move(item->second);

        // Remove item before unlocking to not trash the item' relationship
        next_offset = pkt_offset + 1;
        item = reorderBuffer_.erase(item);
1120 1121 1122 1123 1124 1125 1126 1127 1128

        if (callbacks_.onRxData) {
            lk.unlock();
            callbacks_.onRxData(std::move(pkt));
            lk.lock();
        }
    }

    gapOffset_ = std::max(gapOffset_, next_offset);
1129
    lastReadTime_ = now;
1130
}
1131

1132
TlsSessionState
1133
TlsSession::TlsSessionImpl::handleStateEstablished(TlsSessionState state)
1134
{
1135 1136
    // Nothing to do in reliable mode, so just wait for state change
    if (transport_.isReliable()) {
1137
        auto disconnected = [this]() ->  bool {
1138 1139
            return state_.load() != TlsSessionState::ESTABLISHED
             or newState_.load() != TlsSessionState::NONE;
1140 1141 1142
        };
        std::unique_lock<std::mutex> lk(stateMutex_);
        stateCondition_.wait(lk, disconnected);
1143 1144 1145 1146 1147 1148 1149 1150 1151
        auto oldState = state_.load();
        if (oldState == TlsSessionState::ESTABLISHED) {
            auto newState = newState_.load();
            if (newState != TlsSessionState::NONE) {
                newState_ = TlsSessionState::NONE;
                return newState;
            }
        }
        return oldState;
1152 1153
    }

1154 1155 1156 1157 1158 1159
    // block until rx packet or state change
    {
        std::unique_lock<std::mutex> lk {rxMutex_};
        rxCv_.wait(lk, [this]{ return !rxQueue_.empty() or state_ != TlsSessionState::ESTABLISHED; });
        state = state_.load();
        if (state != TlsSessionState::ESTABLISHED)
1160
            return state;
1161
    }
1162

1163
    std::array<uint8_t, 8> seq;
1164
    rawPktBuf_.resize(RX_MAX_SIZE);
1165
    auto ret = gnutls_record_recv_seq(session_, rawPktBuf_.data(), rawPktBuf_.size(), &seq[0]);
1166

1167
    if (ret > 0) {
1168
        // Are we in PMTUD phase?
1169
        if (!pmtudOver_) {
1170 1171 1172
            mtuProbe_ = MTUS_[std::max(0, hbPingRecved_ - 1)];
            gnutls_dtls_set_mtu(session_, mtuProbe_);
            maxPayload_ = gnutls_dtls_get_data_mtu(session_);
1173
            pmtudOver_ = true;
Adrien Béraud's avatar
Adrien Béraud committed
1174
            JAMI_DBG() << "[TLS] maxPayload: " << maxPayload_.load();
1175

1176 1177
            if (!initFromRecordState(-1))
                return TlsSessionState::SHUTDOWN;
1178 1179
        }

1180 1181 1182 1183
        rawPktBuf_.resize(ret);
        handleDataPacket(std::move(rawPktBuf_), array2uint(seq));
        // no state change
    } else if (ret == GNUTLS_E_HEARTBEAT_PING_RECEIVED) {
Adrien Béraud's avatar
Adrien Béraud committed
1184
        JAMI_DBG("[TLS] PMTUD: ping received sending pong");
1185
        auto errno_send = gnutls_heartbeat_pong(session_, 0);
1186

1187
        if (errno_send != GNUTLS_E_SUCCESS){
Adrien Béraud's avatar
Adrien Béraud committed
1188
            JAMI_ERR("[TLS] PMTUD: failed on pong with error %d: %s", errno_send,
1189 1190 1191
                      gnutls_strerror(errno_send));
        } else {
            ++hbPingRecved_;
1192
        }
1193 1194
        // no state change
    } else if (ret == 0) {
Adrien Béraud's avatar
Adrien Béraud committed
1195
        JAMI_DBG("[TLS] eof");
1196 1197
        state = TlsSessionState::SHUTDOWN;
    } else if (ret == GNUTLS_E_REHANDSHAKE) {
Adrien Béraud's avatar
Adrien Béraud committed
1198
        JAMI_DBG("[TLS] re-handshake");
1199 1200
        state = TlsSessionState::HANDSHAKE;
    } else if (gnutls_error_is_fatal(ret)) {
Adrien Béraud's avatar
Adrien Béraud committed
1201
        JAMI_ERR("[TLS] fatal error in recv: %s", gnutls_strerror(ret));
1202 1203
        state = TlsSessionState::SHUTDOWN;
    } // else non-fatal error... let's continue
1204 1205 1206 1207 1208

    return state;
}

TlsSessionState
1209
TlsSession::TlsSessionImpl::handleStateShutdown(TlsSessionState state)
1210
{
Adrien Béraud's avatar
Adrien Béraud committed
1211
    JAMI_DBG("[TLS] shutdown");
1212 1213 1214 1215 1216 1217 1218

    // Stop ourself
    thread_.stop();
    return state;
}

void
1219
TlsSession::TlsSessionImpl::process()
1220 1221 1222 1223 1224 1225
{
    auto old_state = state_.load();
    auto new_state = fsmHandlers_[old_state](old_state);

    // update state_ with taking care for external state change
    if (not std::atomic_compare_exchange_strong(&state_, &old_state, new_state))
1226
        new_state = old_state;
1227

1228 1229 1230
    if (old_state != new_state)
        stateCondition_.notify_all();

1231 1232 1233 1234
    if (old_state != new_state and callbacks_.onStateChange)
        callbacks_.onStateChange(new_state);
}

1235 1236
//==============================================================================

1237 1238
TlsSession::TlsSession(SocketType& transport, const TlsParams& params,
                       const TlsSessionCallbacks& cbs, bool anonymous)
1239

1240
    : pimpl_ { std::make_unique<TlsSessionImpl>(transport, params, cbs, anonymous) }
1241 1242 1243
{}

TlsSession::~TlsSession()
1244
{}
1245 1246

bool
1247
TlsSession::isInitiator() const
1248
{
1249
    return !pimpl_->isServer_;
1250 1251
}

1252 1253
bool
TlsSession::isReliable() const
1254
{
1255
    return pimpl_->transport_.isReliable();
1256 1257
}

1258 1259
int
TlsSession::maxPayload() const
1260
{
1261
    if (pimpl_->state_ == TlsSessionState::SHUTDOWN)
1262 1263
        throw std::runtime_error("Getting maxPayload from non-valid TLS session");
    return pimpl_->transport_.maxPayload();
1264 1265 1266
}

const char*
1267
TlsSession::currentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
{
    // get current session cipher suite info
    gnutls_cipher_algorithm_t cipher, s_cipher = gnutls_cipher_get(pimpl_->session_);
    gnutls_kx_algorithm_t kx, s_kx = gnutls_kx_get(pimpl_->session_);
    gnutls_mac_algorithm_t mac, s_mac = gnutls_mac_get(pimpl_->session_);

    // Loop on all known cipher suites until matching with session data, extract it's cs_id
    for (std::size_t i=0; ; ++i) {
        const char* const suite = gnutls_cipher_suite_info(i, cs_id.data(), &kx, &cipher, &mac,
                                                           nullptr);
        if (!suite)
          break;
        if (cipher == s_cipher && kx == s_kx && mac == s_mac)
            return suite;
    }

    auto name = gnutls_cipher_get_name(s_cipher);
Adrien Béraud's avatar
Adrien Béraud committed
1285
    JAMI_WARN("[TLS] No Cipher Suite Id found for cipher %s", name ? name : "<null>");
1286 1287 1288
    return {};
}

1289 1290 1291 1292 1293
// Called by anyone to stop the connection and the FSM thread
void
TlsSession::shutdown()
{
    pimpl_->state_ = TlsSessionState::SHUTDOWN;
1294
    pimpl_->stateCondition_.notify_all();