Commit dd42165c authored by Guillaume Roguez's avatar Guillaume Roguez

daemon: implement ICE (NAT-traversal solution)

This patch brings a full ICE support using PJSIP implementation (PJNATH).
Audio and video SIP medias use it.

Refs #53981

Change-Id: I34f9c184b2e420f1908d767ca615e43b0ac88825
parent 1388c21e
......@@ -101,6 +101,8 @@ libsflphone_la_SOURCES = conference.cpp \
ip_utils.h \
ip_utils.cpp \
utf8_utils.cpp \
ice_transport.cpp \
ice_transport.h \
plugin_manager.cpp \
plugin_loader_dl.cpp \
ring_plugin.h \
......
......@@ -473,6 +473,23 @@ AVFormatRtpSession::start(int localPort)
startReceiver();
}
void
AVFormatRtpSession::start(std::unique_ptr<IceSocket> rtp_sock,
std::unique_ptr<IceSocket> rtcp_sock)
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (not sending_ and not receiving_) {
stop();
return;
}
socketPair_.reset(new SocketPair(std::move(rtp_sock), std::move(rtcp_sock)));
startSender();
startReceiver();
}
void
AVFormatRtpSession::stop()
{
......
......@@ -54,6 +54,7 @@ class RingBuffer;
class Resampler;
class AudioSender;
class AudioReceiveThread;
class IceSocket;
class AVFormatRtpSession {
public:
......@@ -62,6 +63,8 @@ class AVFormatRtpSession {
~AVFormatRtpSession();
void start(int localPort);
void start(std::unique_ptr<IceSocket> rtp_sock,
std::unique_ptr<IceSocket> rtcp_sock);
void stop();
void updateDestination(const std::string& destination, unsigned int port);
void updateSDP(const Sdp &sdp);
......
......@@ -60,6 +60,7 @@ void
Call::removeCall()
{
Manager::instance().callFactory.removeCall(*this);
iceTransport_.reset();
}
const std::string&
......@@ -300,3 +301,82 @@ Call::getNullDetails()
details["ACCOUNTID"] = "";
return details;
}
void
Call::initIceTransport(bool master, unsigned channel_num)
{
auto& iceTransportFactory = Manager::instance().getIceTransportFactory();
const auto& on_initdone = [this, master](sfl::IceTransport& iceTransport, bool done) {
if (done) {
if (master)
iceTransport.setInitiatorSession();
else
iceTransport.setSlaveSession();
}
{
std::unique_lock<std::mutex> lk(callMutex_);
iceTransportInitDone_ = done;
}
iceCV_.notify_one();
};
const auto& on_negodone = [this, master](sfl::IceTransport& /*iceTransport*/, bool done) {
{
std::unique_lock<std::mutex> lk(callMutex_);
iceTransportNegoDone_ = done;
}
iceCV_.notify_one();
};
iceTransport_ = iceTransportFactory.createTransport(getCallId().c_str(), channel_num,
on_initdone,
on_negodone);
}
int
Call::waitForIceInitialization(unsigned timeout)
{
std::unique_lock<std::mutex> lk(callMutex_);
if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout),
[this]{ return iceTransportInitDone_; })) {
SFL_WARN("waitForIceInitialization: timeout");
return -1;
}
SFL_DBG("waitForIceInitialization: %u", iceTransportInitDone_);
return iceTransportInitDone_;
}
int
Call::waitForIceNegotiation(unsigned timeout)
{
std::unique_lock<std::mutex> lk(callMutex_);
if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout),
[this]{ return iceTransportNegoDone_; })) {
SFL_WARN("waitForIceNegotiation: timeout");
return -1;
}
SFL_DBG("waitForIceNegotiation: %u", iceTransportNegoDone_);
return iceTransportNegoDone_;
}
bool
Call::isIceUsed() const
{
std::unique_lock<std::mutex> lk(callMutex_);
return iceTransportInitDone_;
}
bool
Call::isIceRunning() const
{
std::unique_lock<std::mutex> lk(callMutex_);
return iceTransportNegoDone_;
}
sfl::IceSocket*
Call::newIceSocket(unsigned compId) const
{
return new sfl::IceSocket(iceTransport_, compId);
}
......@@ -38,12 +38,14 @@
#include "audio/recordable.h"
#include "ip_utils.h"
#include "ice_transport.h"
#include <mutex>
#include <map>
#include <sstream>
#include <memory>
#include <vector>
#include <condition_variable>
class VoIPLink;
class Account;
......@@ -55,7 +57,7 @@ template <class T> using CallMap = std::map<std::string, std::shared_ptr<T> >;
* @brief A call is the base class for protocol-based calls
*/
class Call : public sfl::Recordable {
class Call : public sfl::Recordable, public std::enable_shared_from_this<Call> {
public:
static const char * const DEFAULT_ID;
......@@ -300,6 +302,19 @@ class Call : public sfl::Recordable {
void removeCall();
void initIceTransport(bool master, unsigned channel_num=4);
int waitForIceInitialization(unsigned timeout);
int waitForIceNegotiation(unsigned timeout);
bool isIceUsed() const;
bool isIceRunning() const;
sfl::IceSocket* newIceSocket(unsigned compId) const;
std::shared_ptr<sfl::IceTransport> getIceTransport() const {
return iceTransport_;
}
protected:
/**
* Constructor of a call
......@@ -308,6 +323,8 @@ class Call : public sfl::Recordable {
*/
Call(Account& account, const std::string& id, Call::CallType type);
std::shared_ptr<sfl::IceTransport> iceTransport_ {};
private:
bool validTransition(CallState newState);
......@@ -355,6 +372,11 @@ class Call : public sfl::Recordable {
time_t timestamp_start_ {0};
time_t timestamp_stop_ {0};
/** ICE support */
std::condition_variable iceCV_ {};
bool iceTransportInitDone_ {false};
bool iceTransportNegoDone_ {false};
};
#endif // __CALL_H__
/*
* Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
* 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.
* 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.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#ifndef ICE_SOCKET_H
#define ICE_SOCKET_H
#include <memory>
namespace sfl {
class IceTransport;
class IceSocket
{
private:
std::shared_ptr<IceTransport> ice_transport_ {};
int compId_ = -1;
public:
IceSocket(std::shared_ptr<IceTransport> iceTransport, int compId)
: ice_transport_(iceTransport), compId_(compId) {}
void close();
ssize_t recv(unsigned char* buf, size_t len);
ssize_t send(const unsigned char* buf, size_t len);
ssize_t getNextPacketSize() const;
ssize_t waitForData(unsigned int timeout);
};
};
#endif /* ICE_SOCKET_H */
This diff is collapsed.
/*
* Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
* 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.
* 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.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#ifndef ICE_TRANSPORT_H
#define ICE_TRANSPORT_H
#include "ice_socket.h"
#include "ip_utils.h"
#include <pjnath.h>
#include <pjlib.h>
#include <pjlib-util.h>
#include <map>
#include <functional>
#include <memory>
#include <atomic>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
namespace sfl {
class IceTransport;
using IceTransportCompleteCb = std::function<void(IceTransport&, bool)>;
using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>;
using IceCandidate = pj_ice_sess_cand;
class IceTransport {
public:
using Attribute = struct {
std::string ufrag;
std::string pwd;
};
/**
* Constructor
*/
IceTransport(const char* name, int component_count,
IceTransportCompleteCb on_initdone_cb,
IceTransportCompleteCb on_negodone_cb);
/**
* 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();
/**
* Start tranport negociation between local candidates and given remote
* to find the right candidate pair.
* This function doesn't block, the callback on_negodone_cb will be called
* with the negotiation result when operation is really done.
* Return false if negotiation cannot be started else true.
*/
bool start(const Attribute& rem_attrs,
const std::vector<IceCandidate>& rem_candidates);
bool start(const std::vector<uint8_t>& attrs_candidates);
/**
* Stop a started or completed transport.
*/
bool stop();
bool isInitialized() const;
bool isStarted() const;
bool isCompleted() const;
IpAddr getLocalAddress(unsigned comp_id) const;
IpAddr getRemoteAddress(unsigned comp_id) const;
IpAddr getDefaultLocalAddress() const {
return getLocalAddress(0);
}
/**
* Return ICE session attributes
*/
const Attribute getLocalAttributes() const;
/**
* Return ICE session attributes
*/
std::vector<std::string> getLocalCandidates(unsigned comp_id) const;
/**
* Returns serialized ICE attributes and candidates.
*/
std::vector<uint8_t> getLocalAttributesAndCandidates() const;
bool getCandidateFromSDP(const std::string& line, IceCandidate& cand);
// I/O methods
void setOnRecv(unsigned comp_id, IceRecvCb cb);
ssize_t recv(int comp_id, unsigned char* buf, size_t len);
ssize_t send(int comp_id, const unsigned char* buf, size_t len);
ssize_t getNextPacketSize(int comp_id);
ssize_t waitForData(int comp_id, unsigned int timeout);
private:
static constexpr int MAX_CANDIDATES {32};
// New line character used for (de)serialisation
static constexpr char NEW_LINE = '\n';
static void cb_on_rx_data(pj_ice_strans *ice_st,
unsigned comp_id,
void *pkt, pj_size_t size,
const pj_sockaddr_t *src_addr,
unsigned src_addr_len);
static void cb_on_ice_complete(pj_ice_strans *ice_st,
pj_ice_strans_op op,
pj_status_t status);
static std::string unpackLine(std::vector<uint8_t>::const_iterator& begin,
std::vector<uint8_t>::const_iterator& end);
struct IceSTransDeleter {
void operator ()(pj_ice_strans* ptr) {
pj_ice_strans_stop_ice(ptr);
pj_ice_strans_destroy(ptr);
}
};
void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op,
pj_status_t status);
void onReceiveData(unsigned comp_id, void *pkt, pj_size_t size);
bool createIceSession(pj_ice_sess_role role);
void getUFragPwd();
void getDefaultCanditates();
IceTransportCompleteCb on_initdone_cb_;
IceTransportCompleteCb on_negodone_cb_;
std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_;
unsigned component_count_;
pj_ice_sess_cand cand_[MAX_CANDIDATES] {};
std::string local_ufrag_;
std::string local_pwd_;
pj_sockaddr remoteAddr_;
struct Packet {
Packet(void *pkt, pj_size_t size);
std::unique_ptr<char> data;
size_t datalen;
};
struct ComponentIO {
std::mutex mutex;
std::condition_variable cv;
std::deque<Packet> queue;
IceRecvCb cb;
};
std::vector<ComponentIO> compIO_;
};
class IceTransportFactory {
public:
IceTransportFactory();
~IceTransportFactory();
std::shared_ptr<IceTransport> createTransport(const char* name,
int component_count,
IceTransportCompleteCb&& on_initdone_cb,
IceTransportCompleteCb&& on_negodone_cb);
int processThread();
const pj_ice_strans_cfg* getIceCfg() const { return &ice_cfg_; }
private:
int handleEvents(unsigned max_msec, unsigned *p_count);
pj_ice_strans_cfg ice_cfg_;
std::unique_ptr<pj_thread_t, decltype(*pj_thread_destroy)> thread_;
pj_bool_t thread_quit_flag_ {PJ_FALSE};
};
};
#endif /* ICE_TRANSPORT_H */
......@@ -79,6 +79,7 @@
#endif
#include "conference.h"
#include "ice_transport.h"
#include <cerrno>
#include <algorithm>
......@@ -95,6 +96,8 @@ using namespace sfl;
std::atomic_bool ManagerImpl::initialized = {false};
using namespace sfl;
static void
copy_over(const std::string &srcPath, const std::string &destPath)
{
......@@ -137,7 +140,7 @@ ManagerImpl::ManagerImpl() :
waitingCalls_(), waitingCallsMutex_(), path_()
, ringbufferpool_(new sfl::RingBufferPool)
, callFactory(), conferenceMap_(), history_(),
finished_(false), accountFactory_()
finished_(false), accountFactory_(), ice_tf_()
{
// initialize random generator
// mt19937_64 should be seeded with 2 x 32 bits
......@@ -176,6 +179,20 @@ ManagerImpl::init(const std::string &config_file)
// FIXME: this is no good
initialized = true;
#define TRY(ret) do { \
if (ret != PJ_SUCCESS) \
throw std::runtime_error(#ret " failed"); \
} while (0)
// Our PJSIP dependency (SIP and ICE)
TRY(pj_init());
TRY(pjlib_util_init());
TRY(pjnath_init());
#undef TRY
ice_tf_.reset(new IceTransportFactory());
path_ = config_file.empty() ? retrieveConfigPath() : config_file;
SFL_DBG("Configuration file path: %s", path_.c_str());
......@@ -265,6 +282,9 @@ ManagerImpl::finish()
audiodriver_.reset();
}
ice_tf_.reset();
pj_shutdown();
} catch (const VoipLinkException &err) {
SFL_ERR("%s", err.what());
}
......
......@@ -74,10 +74,12 @@ namespace sfl {
class RingBufferPool;
class DTMF;
class TelephoneTone;
class IceTransportFactory;
}
class PluginManager;
/** To send multiple string */
typedef std::list<std::string> TokenList;
......@@ -972,6 +974,8 @@ class ManagerImpl {
*/
void unregisterEventHandler(uintptr_t handlerId);
sfl::IceTransportFactory& getIceTransportFactory() { return *ice_tf_; }
private:
NON_COPYABLE(ManagerImpl);
......@@ -1009,6 +1013,9 @@ class ManagerImpl {
void loadAccount(const YAML::Node &item, int &errorCount,
const std::string &accountOrder);
/* ICE support */
std::unique_ptr<sfl::IceTransportFactory> ice_tf_;
};
#endif // MANAGER_IMPL_H_
......@@ -45,6 +45,7 @@
#endif
#include <algorithm>
#include <cassert>
using std::string;
using std::map;
......@@ -841,7 +842,6 @@ void Sdp::addSdesAttribute(const vector<std::string>& crypto)
}
}
void Sdp::addZrtpAttribute(pjmedia_sdp_media* media, std::string hash)
{
/* Format: ":version value" */
......@@ -853,6 +853,75 @@ void Sdp::addZrtpAttribute(pjmedia_sdp_media* media, std::string hash)
throw SdpException("Could not add zrtp attribute to media");
}
void
Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands)
{
assert(media_index < localSession_->media_count);
auto media = localSession_->media[media_index];
for (const auto &item : cands) {
pj_str_t val = { (char*) item.c_str(), static_cast<pj_ssize_t>(item.size()) };
pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_, "candidate", &val);
if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS)
throw SdpException("Could not add ICE candidates attribute to media");
}
}
std::vector<std::string>
Sdp::getIceCandidates(unsigned media_index) const
{
auto session = remoteSession_ ? remoteSession_ : activeRemoteSession_;
assert(session);
assert(media_index < session->media_count);
auto media = session->media[media_index];
std::vector<std::string> candidates;
for (unsigned i=0; i < media->attr_count; i++) {
pjmedia_sdp_attr *attribute = media->attr[i];
if (pj_stricmp2(&attribute->name, "candidate") == 0)
candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen));
}
return candidates;
}
void
Sdp::addIceAttributes(const sfl::IceTransport::Attribute&& ice_attrs)
{
pj_str_t value;
pjmedia_sdp_attr *attr;
value = { (char*)ice_attrs.ufrag.c_str(), static_cast<pj_ssize_t>(ice_attrs.ufrag.size()) };
attr = pjmedia_sdp_attr_create(memPool_, "ice-ufrag", &value);
if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
throw SdpException("Could not add ICE.ufrag attribute to local SDP");
value = { (char*)ice_attrs.pwd.c_str(), static_cast<pj_ssize_t>(ice_attrs.pwd.size()) };
attr = pjmedia_sdp_attr_create(memPool_, "ice-pwd", &value);
if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
throw SdpException("Could not add ICE.pwd attribute to local SDP");
}
sfl::IceTransport::Attribute
Sdp::getIceAttributes() const
{
sfl::IceTransport::Attribute ice_attrs;
auto session = remoteSession_ ? remoteSession_ : activeRemoteSession_;
assert(session);
for (unsigned i=0; i < session->attr_count; i++) {
pjmedia_sdp_attr *attribute = session->attr[i];
if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
}
return ice_attrs;
}
// Returns index of desired media attribute, or -1 if not found */
static int
getIndexOfAttribute(const pjmedia_sdp_session * const session, const char * const type)
......
......@@ -34,6 +34,7 @@
#include "noncopyable.h"
#include "ip_utils.h"
#include "ice_transport.h"
#include <pjmedia/sdp.h>
#include <pjmedia/sdp_neg.h>
......@@ -89,6 +90,10 @@ class Sdp {
return localSession_;
}
const pjmedia_sdp_session *getActiveLocalSdpSession() const {
return activeLocalSession_;
}
/**
* Read accessor. Get the remote passive sdp session information before negotiation
*
......@@ -98,6 +103,10 @@ class Sdp {
return remoteSession_;
}
const pjmedia_sdp_session *getActiveRemoteSdpSession() const {
return activeRemoteSession_;
}
/**
* Set the negotiated sdp offer from the sip payload.
*
......@@ -270,7 +279,16 @@ class Sdp {
bool getOutgoingVideoSettings(std::map<std::string, std::string> &settings) const;
bool getOutgoingAudioSettings(std::map<std::string, std::string> &settings) const;
void addIceAttributes(const sfl::IceTransport::Attribute&& ice_attrs);
sfl::IceTransport::Attribute getIceAttributes() const;
void addIceCandidates(unsigned media_index,
const std::vector<std::string>& cands);
std::vector<std::string> getIceCandidates(unsigned media_index) const;
private:
NON_COPYABLE(Sdp);
friend class SDPTest;
......
......@@ -214,6 +214,8 @@ SIPAccount::newOutgoingCall(const std::string& id, const std::string& toUrl)
SFL_DBG("UserAgent: New registered account call to %s", toUrl.c_str());
}
call->initIceTransport(true);
call->setIPToIP(isIP2IP());
call->setPeerNumber(toUri);
call->initRecFilename(to);
......@@ -275,6 +277,9 @@ SIPAccount::newOutgoingCall(const std::string& id, const std::string& toUrl)
bool
SIPAccount::SIPStartCall(std::shared_ptr<SIPCall>& call)
{
// Add Ice headers to local SDP
call->setupLocalSDPFromIce();
std::string toUri(call->getPeerNumber()); // expecting a fully well formed sip uri
pj_str_t pjTo = pj_str((char*) toUri.c_str());
......
......@@ -42,20 +42,26 @@
#include "sdp.h"
#include "manager.h"
#include "array_size.h"