Skip to content
Snippets Groups Projects
Select Git revision
  • de87ae908925900356bfdfcc321ddd309f40b57d
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

siptransport.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    siptransport.cpp 14.60 KiB
    /*
     *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
     *
     *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
     *  Author: Adrien Béraud <adrien.beraud@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.
     *
     *  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 "siptransport.h"
    #include "sip_utils.h"
    #include "ip_utils.h"
    #include "ice_transport.h"
    #include "security/tls_session.h"
    
    #include "jamidht/sips_transport_ice.h"
    
    #include "array_size.h"
    #include "compiler_intrinsics.h"
    #include "sipvoiplink.h"
    
    #include <pjsip.h>
    #include <pjsip/sip_types.h>
    #include <pjsip/sip_transport_tls.h>
    #include <pj/ssl_sock.h>
    #include <pjnath.h>
    #include <pjnath/stun_config.h>
    #include <pjlib.h>
    #include <pjlib-util.h>
    
    #include <opendht/crypto.h>
    
    #include <stdexcept>
    #include <sstream>
    #include <algorithm>
    
    #define RETURN_IF_FAIL(A, VAL, ...) if (!(A)) { JAMI_ERR(__VA_ARGS__); return (VAL); }
    
    namespace jami {
    
    constexpr const char* TRANSPORT_STATE_STR[] = {
        "CONNECTED", "DISCONNECTED", "SHUTDOWN", "DESTROY", "UNKNOWN STATE"
    };
    constexpr const size_t TRANSPORT_STATE_SZ = arraySize(TRANSPORT_STATE_STR);
    
    void
    SipTransport::deleteTransport(pjsip_transport* t)
    {
        pjsip_transport_dec_ref(t);
    }
    
    SipTransport::SipTransport(pjsip_transport* t)
        : transport_(nullptr, deleteTransport)
    {
        if (not t or pjsip_transport_add_ref(t) != PJ_SUCCESS)
            throw std::runtime_error("invalid transport");
    
        // Set pointer here, right after the successful pjsip_transport_add_ref
        transport_.reset(t);
    
        JAMI_DBG("SipTransport@%p {tr=%p {rc=%ld}}",
                 this, transport_.get(), pj_atomic_get(transport_->ref_cnt));
    }
    
    SipTransport::SipTransport(pjsip_transport* t,
                               const std::shared_ptr<TlsListener>& l)
        : SipTransport(t)
    {
        tlsListener_ = l;
    }
    
    SipTransport::~SipTransport()
    {
        JAMI_DBG("~SipTransport@%p {tr=%p {rc=%ld}}",
                 this, transport_.get(), pj_atomic_get(transport_->ref_cnt));
    }
    
    bool
    SipTransport::isAlive(UNUSED const std::shared_ptr<SipTransport>& t,
                          pjsip_transport_state state)
    {
        return state != PJSIP_TP_STATE_DISCONNECTED
    #if PJ_VERSION_NUM > (2 << 24 | 1 << 16)
        && state != PJSIP_TP_STATE_SHUTDOWN
        && state != PJSIP_TP_STATE_DESTROY
    #else
        && t && t->get()
        && !t->get()->is_shutdown
        && !t->get()->is_destroying
    #endif
        ;
    }
    
    const char*
    SipTransport::stateToStr(pjsip_transport_state state)
    {
        return TRANSPORT_STATE_STR[std::min<size_t>(state, TRANSPORT_STATE_SZ-1)];
    }
    
    void
    SipTransport::stateCallback(pjsip_transport_state state,
                                const pjsip_transport_state_info *info)
    {
        connected_ = state == PJSIP_TP_STATE_CONNECTED;
    
        auto extInfo = static_cast<const pjsip_tls_state_info*>(info->ext_info);
        if (isSecure() && extInfo && extInfo->ssl_sock_info && extInfo->ssl_sock_info->established) {
            auto tlsInfo = extInfo->ssl_sock_info;
            tlsInfos_.proto = (pj_ssl_sock_proto)tlsInfo->proto;
            tlsInfos_.cipher = tlsInfo->cipher;
            tlsInfos_.verifyStatus = (pj_ssl_cert_verify_flag_t)tlsInfo->verify_status;
            const auto& peers = tlsInfo->remote_cert_info->raw_chain;
            std::vector<std::pair<const uint8_t*, const uint8_t*>> bits;
            bits.resize(peers.cnt);
            std::transform(peers.cert_raw, peers.cert_raw+peers.cnt, std::begin(bits),
                           [](const pj_str_t& crt){
                               return std::make_pair((uint8_t*)crt.ptr,
                                                     (uint8_t*)(crt.ptr+crt.slen));
                           });
            tlsInfos_.peerCert = std::make_shared<dht::crypto::Certificate>(bits);
        } else {
            tlsInfos_ = {};
        }
    
        std::vector<SipTransportStateCallback> cbs;
        {
            std::lock_guard<std::mutex> lock(stateListenersMutex_);
            cbs.reserve(stateListeners_.size());
            for (auto& l : stateListeners_)
                cbs.push_back(l.second);
        }
        for (auto& cb : cbs)
            cb(state, info);
    }
    
    void
    SipTransport::addStateListener(uintptr_t lid, SipTransportStateCallback cb)
    {
        std::lock_guard<std::mutex> lock(stateListenersMutex_);
        auto pair = stateListeners_.insert(std::make_pair(lid, cb));
        if (not pair.second)
            pair.first->second = cb;
    }
    
    bool
    SipTransport::removeStateListener(uintptr_t lid)
    {
        std::lock_guard<std::mutex> lock(stateListenersMutex_);
        auto it = stateListeners_.find(lid);
        if (it != stateListeners_.end()) {
            stateListeners_.erase(it);
            return true;
        }
        return false;
    }
    
    uint16_t
    SipTransport::getTlsMtu()
    {
        if (isIceTransport_ && isSecure()) {
            auto tls_tr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(transport_.get())->self;
            return tls_tr->getTlsSessionMtu();
        }
        return 1232; /* Hardcoded yes (it's the IPv6 value).
                      * This method is broken by definition.
                      * A MTU should not be defined at this layer.
                      * And a correct value should come from the underlying transport itself,
                      * not from a constant...
                      */
    }
    
    SipTransportBroker::SipTransportBroker(pjsip_endpoint *endpt,
                                           pj_caching_pool& cp, pj_pool_t& pool) :
    cp_(cp), pool_(pool), endpt_(endpt)
    {
    /*
        pjsip_transport_register_type(PJSIP_TRANSPORT_DATAGRAM, "ICE",
                                      pjsip_transport_get_default_port_for_type(PJSIP_TRANSPORT_UDP),
                                      &ice_pj_transport_type_);
    */
        JAMI_DBG("SipTransportBroker@%p", this);
    }
    
    SipTransportBroker::~SipTransportBroker()
    {
        JAMI_DBG("~SipTransportBroker@%p", this);
    
        shutdown();
    
        udpTransports_.clear();
        transports_.clear();
    
        JAMI_DBG("destroying SipTransportBroker@%p", this);
    }
    
    void
    SipTransportBroker::transportStateChanged(pjsip_transport* tp,
                                              pjsip_transport_state state,
                                              const pjsip_transport_state_info* info)
    {
        JAMI_DBG("pjsip transport@%p %s -> %s",
                 tp, tp->info, SipTransport::stateToStr(state));
    
        // First make sure that this transport is handled by us
        // and remove it from any mapping if destroy pending or done.
    
        std::shared_ptr<SipTransport> sipTransport;
    
        {
            std::lock_guard<std::mutex> lock(transportMapMutex_);
            auto key = transports_.find(tp);
            if (key == transports_.end()) {
                JAMI_WARN("spurious pjsip transport state change");
                return;
            }
    
            sipTransport = key->second.lock();
    
    #if PJ_VERSION_NUM > (2 << 24 | 1 << 16)
            bool destroyed = state == PJSIP_TP_STATE_DESTROY;
    #else
            bool destroyed = tp->is_destroying;
    #endif
    
            // maps cleanup
            if (destroyed) {
                JAMI_DBG("unmap pjsip transport@%p {SipTransport@%p}",
                         tp, sipTransport.get());
                transports_.erase(key);
    
                // If UDP
                const auto type = tp->key.type;
                if (type == PJSIP_TRANSPORT_UDP or type == PJSIP_TRANSPORT_UDP6) {
                    const auto updKey = std::find_if(
                        udpTransports_.cbegin(), udpTransports_.cend(),
                        [tp](const std::pair<IpAddr, pjsip_transport*>& pair) {
                            return pair.second == tp;
                        });
                    if (updKey != udpTransports_.cend())
                        udpTransports_.erase(updKey);
                }
            }
        }
    
        // Propagate the event to the appropriate transport
        // Note the SipTransport may not be in our mappings if marked as dead
        if (sipTransport)
            sipTransport->stateCallback(state, info);
    }
    
    std::shared_ptr<SipTransport>
    SipTransportBroker::addTransport(pjsip_transport* t)
    {
        if (t) {
            std::lock_guard<std::mutex> lock(transportMapMutex_);
    
            auto key = transports_.find(t);
            if (key != transports_.end()) {
                if (auto sipTr = key->second.lock())
                    return sipTr;
            }
    
            auto sipTr = std::make_shared<SipTransport>(t);
            if (key != transports_.end())
                key->second = sipTr;
            else
                transports_.emplace(std::make_pair(t, sipTr));
            return sipTr;
        }
    
        return nullptr;
    }
    
    void
    SipTransportBroker::shutdown()
    {
        std::unique_lock<std::mutex> lock(transportMapMutex_);
        for (auto& t : transports_) {
            if (auto transport = t.second.lock()) {
                pjsip_transport_shutdown(transport->get());
            }
        }
    }
    
    std::shared_ptr<SipTransport>
    SipTransportBroker::getUdpTransport(const IpAddr& ipAddress)
    {
        std::lock_guard<std::mutex> lock(transportMapMutex_);
        auto itp = udpTransports_.find(ipAddress);
        if (itp != udpTransports_.end()) {
            auto it = transports_.find(itp->second);
            if (it != transports_.end()) {
                if (auto spt = it->second.lock()) {
                    JAMI_DBG("Reusing transport %s", ipAddress.toString(true).c_str());
                    return spt;
                }
                else {
                    // Transport still exists but have not been destroyed yet.
                    JAMI_WARN("Recycling transport %s", ipAddress.toString(true).c_str());
                    auto ret = std::make_shared<SipTransport>(itp->second);
                    it->second = ret;
                    return ret;
                }
            } else {
                JAMI_WARN("Cleaning up UDP transport %s", ipAddress.toString(true).c_str());
                udpTransports_.erase(itp);
            }
        }
        auto ret = createUdpTransport(ipAddress);
        if (ret) {
            udpTransports_[ipAddress] = ret->get();
            transports_[ret->get()] = ret;
        }
        return ret;
    }
    
    std::shared_ptr<SipTransport>
    SipTransportBroker::createUdpTransport(const IpAddr& ipAddress)
    {
        RETURN_IF_FAIL(ipAddress, nullptr, "Could not determine IP address for this transport");
    
        pjsip_udp_transport_cfg pj_cfg;
        pjsip_udp_transport_cfg_default(&pj_cfg, ipAddress.getFamily());
        pj_cfg.bind_addr = ipAddress;
        pjsip_transport *transport = nullptr;
        if (pj_status_t status = pjsip_udp_transport_start2(endpt_, &pj_cfg, &transport)) {
            JAMI_ERR("pjsip_udp_transport_start2 failed with error %d: %s", status,
                     sip_utils::sip_strerror(status).c_str());
            JAMI_ERR("UDP IPv%s Transport did not start on %s",
                ipAddress.isIpv4() ? "4" : "6",
                ipAddress.toString(true).c_str());
            return nullptr;
        }
    
        JAMI_DBG("Created UDP transport on address %s", ipAddress.toString(true).c_str());
        return std::make_shared<SipTransport>(transport);
    }
    
    std::shared_ptr<TlsListener>
    SipTransportBroker::getTlsListener(const IpAddr& ipAddress, const pjsip_tls_setting* settings)
    {
        RETURN_IF_FAIL(settings, nullptr, "TLS settings not specified");
        RETURN_IF_FAIL(ipAddress, nullptr, "Could not determine IP address for this transport");
        JAMI_DBG("Creating TLS listener on %s...", ipAddress.toString(true).c_str());
    #if 0
        JAMI_DBG(" ca_list_file : %s", settings->ca_list_file.ptr);
        JAMI_DBG(" cert_file    : %s", settings->cert_file.ptr);
        JAMI_DBG(" ciphers_num    : %d", settings->ciphers_num);
        JAMI_DBG(" verify server %d client %d client_cert %d", settings->verify_server, settings->verify_client, settings->require_client_cert);
        JAMI_DBG(" reuse_addr    : %d", settings->reuse_addr);
    #endif
    
        pjsip_tpfactory *listener = nullptr;
        const pj_status_t status = pjsip_tls_transport_start2(endpt_, settings, ipAddress.pjPtr(), nullptr, 1, &listener);
        if (status != PJ_SUCCESS) {
            JAMI_ERR("TLS listener did not start: %s", sip_utils::sip_strerror(status).c_str());
            return nullptr;
        }
        return std::make_shared<TlsListener>(listener);
    }
    
    std::shared_ptr<SipTransport>
    SipTransportBroker::getTlsTransport(const std::shared_ptr<TlsListener>& l, const IpAddr& remote, const std::string& remote_name)
    {
        if (!l || !remote)
            return nullptr;
        IpAddr remoteAddr {remote};
        if (remoteAddr.getPort() == 0)
            remoteAddr.setPort(pjsip_transport_get_default_port_for_type(l->get()->type));
    
        JAMI_DBG("Get new TLS transport to %s", remoteAddr.toString(true).c_str());
        pjsip_tpselector sel;
        sel.type = PJSIP_TPSELECTOR_LISTENER;
        sel.u.listener = l->get();
        sel.disable_connection_reuse = PJ_FALSE;
    
        pjsip_tx_data tx_data;
        tx_data.dest_info.name = pj_str_t{(char*)remote_name.data(), (pj_ssize_t)remote_name.size()};
    
        pjsip_transport *transport = nullptr;
        pj_status_t status = pjsip_endpt_acquire_transport2(
                endpt_,
                l->get()->type,
                remoteAddr.pjPtr(),
                remoteAddr.getLength(),
                &sel,
                remote_name.empty() ? nullptr : &tx_data,
                &transport);
    
        if (!transport || status != PJ_SUCCESS) {
            JAMI_ERR("Could not get new TLS transport: %s", sip_utils::sip_strerror(status).c_str());
            return nullptr;
        }
        auto ret = std::make_shared<SipTransport>(transport, l);
        pjsip_transport_dec_ref(transport);
        {
            std::lock_guard<std::mutex> lock(transportMapMutex_);
            transports_[ret->get()] = ret;
        }
        return ret;
    }
    
    std::shared_ptr<SipTransport>
    SipTransportBroker::getTlsIceTransport(const std::shared_ptr<jami::IceTransport>& ice,
                                           unsigned comp_id,
                                           const tls::TlsParams& params)
    {
        auto ipv6 = ice->getLocalAddress(comp_id).isIpv6();
        auto type = ipv6 ? PJSIP_TRANSPORT_DTLS6 : PJSIP_TRANSPORT_DTLS;
        if (ice->isTCPEnabled()) {
            type = ipv6 ? PJSIP_TRANSPORT_TLS6 : PJSIP_TRANSPORT_TLS;
        }
        auto sip_ice_tr = std::unique_ptr<tls::SipsIceTransport>(
            new tls::SipsIceTransport(endpt_, type, params, ice, comp_id));
        auto tr = sip_ice_tr->getTransportBase();
        auto sip_tr = std::make_shared<SipTransport>(tr);
        sip_tr->setIsIceTransport();
        sip_ice_tr.release(); // managed by PJSIP now
    
        {
            std::lock_guard<std::mutex> lock(transportMapMutex_);
            // we do not check for key existence as we've just created it
            // (member of new SipIceTransport instance)
            transports_.emplace(std::make_pair(tr, sip_tr));
        }
        return sip_tr;
    }
    
    } // namespace jami