Commit e4cdde22 authored by Guillaume Roguez's avatar Guillaume Roguez

ringaccount: register a public address at ICE creation

This patch tries to solve a situation where one peer has
a TURN server registered but the other peer doesn't have
any NAT traversal systems.
To solve that we use a recent OpenDHT API that returns
a list of public IP discovered during DHT transferts.
The most seen address is used to register a new relflective
candidate, coupled to an existing and registered host address.
It's port is stolen, but as this candidate is added after
ICE initialization, the candidate is not used for negotiation.
This is mosly a PJSIP hack as we use an implementation details.

Issue: #78582
Change-Id: Ic16527f04b4e07905c405d8681223a02fee16d55
parent d578cc4d
......@@ -317,7 +317,7 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> {
void removeCall();
bool initIceTransport(bool master, unsigned channel_num=4);
virtual bool initIceTransport(bool master, unsigned channel_num=4);
int waitForIceInitialization(unsigned timeout);
......
......@@ -136,7 +136,8 @@ IceTransport::IceTransport(const char* name, int component_count, bool master,
}
RING_WARN("ICE: STUN='%s', PORT=%d", options.stunServer.c_str(),
config_.stun.port);
}
} else
config_.stun.port = 0;
/* TURN */
if (not options.turnServer.empty()) {
......@@ -164,7 +165,8 @@ IceTransport::IceTransport(const char* name, int component_count, bool master,
RING_WARN("ICE: TURN='%s', PORT=%d", options.turnServer.c_str(),
config_.turn.port);
}
} else
config_.turn.port = 0;
pj_ice_strans* icest = nullptr;
pj_status_t status = pj_ice_strans_create(name, &config_, component_count,
......@@ -492,9 +494,35 @@ IceTransport::getLocalCandidatesAddr(unsigned comp_id) const
return cand_addrs;
}
bool
IceTransport::registerPublicIP(unsigned compId, const IpAddr& publicIP)
{
// Register only if no NAT traversal methods exists
if (upnp_ or config_.stun.port > 0 or config_.turn.port > 0)
return false;
// Find the local candidate corresponding to local host,
// then register a rflx candidate using given public address
// and this local address as base. It's port is used for both address
// even if on the public side it have strong probabilities to not exist.
// But as this candidate is made after initialization, it's not used during
// negotiation, only to exchanged candidates between peers.
auto localIP = ip_utils::getLocalAddr();
auto pubIP = publicIP;
for (const auto& addr : getLocalCandidatesAddr(compId)) {
auto port = addr.getPort();
localIP.setPort(port);
if (addr != localIP)
continue;
pubIP.setPort(port);
addReflectiveCandidate(compId, addr, pubIP);
return true;
}
return false;
}
void
IceTransport::addCandidate(int comp_id, const IpAddr& localAddr,
const IpAddr& publicAddr)
IceTransport::addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr)
{
pj_ice_sess_cand cand;
......@@ -506,8 +534,8 @@ IceTransport::addCandidate(int comp_id, const IpAddr& localAddr,
/* 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, localAddr.pjPtr());
pj_sockaddr_cp(&cand.addr, publicAddr.pjPtr());
pj_sockaddr_cp(&cand.base_addr, base.pjPtr());
pj_sockaddr_cp(&cand.addr, addr.pjPtr());
pj_sockaddr_cp(&cand.rel_addr, &cand.base_addr);
pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr);
......@@ -525,13 +553,11 @@ IceTransport::addCandidate(int comp_id, const IpAddr& localAddr,
if (ret != PJ_SUCCESS) {
RING_ERR("failed to add candidate for comp_id=%d : %s : %s", comp_id
, localAddr.toString().c_str()
, publicAddr.toString().c_str());
, base.toString().c_str(), addr.toString().c_str());
sip_utils::sip_printerror(ret);
} else {
RING_DBG("succeed to add candidate for comp_id=%d : %s : %s", comp_id
, localAddr.toString().c_str()
, publicAddr.toString().c_str());
, base.toString().c_str(), addr.toString().c_str());
}
}
......@@ -559,7 +585,7 @@ IceTransport::selectUPnPIceCandidates()
uint16_t port_used;
if (upnp_->addAnyMapping(port, upnp::PortType::UDP, true, &port_used)) {
publicIP.setPort(port_used);
addCandidate(comp_id, addr, publicIP);
addReflectiveCandidate(comp_id, addr, publicIP);
} else
RING_WARN("UPnP: Could not create a port mapping for the ICE candide");
}
......
......@@ -138,6 +138,8 @@ class IceTransport {
return getLocalAddress(0);
}
bool registerPublicIP(unsigned compId, const IpAddr& publicIP);
/**
* Return ICE session attributes
*/
......@@ -171,7 +173,7 @@ class IceTransport {
ssize_t waitForData(int comp_id, unsigned int timeout);
unsigned getComponentCount() const {return component_count_;};
unsigned getComponentCount() const {return component_count_;}
private:
static constexpr int MAX_CANDIDATES {32};
......@@ -246,10 +248,10 @@ class IceTransport {
std::vector<IpAddr> getLocalCandidatesAddr(unsigned comp_id) const;
/**
* Adds candidate to ICE session
* Adds a reflective candidate to ICE session
* Must be called before negotiation
*/
void addCandidate(int comp_id, const IpAddr& localAddr,
const IpAddr& publicAddr);
void addReflectiveCandidate(int comp_id, const IpAddr& base, const IpAddr& addr);
/**
* Creates UPnP port mappings and adds ICE candidates based on those mappings
......
......@@ -87,6 +87,35 @@ static constexpr int ICE_NEGOTIATION_TIMEOUT {60};
constexpr const char * const RingAccount::ACCOUNT_TYPE;
/* constexpr */ const std::pair<uint16_t, uint16_t> RingAccount::DHT_PORT_RANGE {4000, 8888};
/**
* Local ICE Transport factory helper
*
* RingAccount must use this helper than direct IceTranportFactory API
*/
template <class... Args>
std::shared_ptr<IceTransport>
RingAccount::createIceTransport(Args... args)
{
// We need a public address in case of NAT'ed network
// Trying to use one discovered by DHT service
if (getPublishedAddress().empty()) {
const auto& addresses = dht_.getPublicAddress();
if (addresses.size())
setPublishedAddress(IpAddr{addresses[0].first});
}
auto ice = Manager::instance().getIceTransportFactory().createTransport(args...);
if (!ice or ice->waitForInitialization(ICE_INIT_TIMEOUT) <= 0)
throw std::runtime_error("ICE transport creation failed");
if (const auto& publicIP = getPublishedIpAddress()) {
for (unsigned compId = 1; compId <= ice->getComponentCount(); ++compId)
ice->registerPublicIP(compId, publicIP);
}
return ice;
}
RingAccount::RingAccount(const std::string& accountID, bool /* presenceEnabled */)
: SIPAccountBase(accountID), via_addr_()
{
......@@ -150,9 +179,8 @@ RingAccount::newOutgoingCall(const std::string& toUrl)
call->setSecure(isTlsEnabled());
// Create an ICE transport for SIP channel
auto& tfactory = manager.getIceTransportFactory();
auto ice = tfactory.createTransport(("sip:" + call->getCallId()).c_str(),
ICE_COMPONENTS, true, getIceOptions());
auto ice = createIceTransport(("sip:" + call->getCallId()).c_str(),
ICE_COMPONENTS, true, getIceOptions());
if (not ice) {
call->removeCall();
return nullptr;
......@@ -664,7 +692,8 @@ dhtStatusStr(dht::Dht::Status status) {
"disconnected");
}
void RingAccount::doRegister_()
void
RingAccount::doRegister_()
{
try {
loadTreatedCalls();
......@@ -674,20 +703,25 @@ void RingAccount::doRegister_()
}
auto identity = loadIdentity();
dht_.setOnStatusChanged([=](dht::Dht::Status s4, dht::Dht::Status s6) {
RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6));
auto status = std::max(s4, s6);
switch(status) {
case dht::Dht::Status::Connecting:
case dht::Dht::Status::Connected:
setRegistrationState(status == dht::Dht::Status::Connected ? RegistrationState::REGISTERED : RegistrationState::TRYING);
break;
case dht::Dht::Status::Disconnected:
default:
setRegistrationState(status == dht::Dht::Status::Disconnected ? RegistrationState::UNREGISTERED : RegistrationState::ERROR_GENERIC);
break;
}
});
dht_.setOnStatusChanged([this](dht::Dht::Status s4, dht::Dht::Status s6) {
RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6));
RegistrationState state;
switch (std::max(s4, s6)) {
case dht::Dht::Status::Connecting:
state = RegistrationState::TRYING;
break;
case dht::Dht::Status::Connected:
state = RegistrationState::REGISTERED;
break;
case dht::Dht::Status::Disconnected:
state = RegistrationState::UNREGISTERED;
break;
default:
state = RegistrationState::ERROR_GENERIC;
break;
}
setRegistrationState(state);
});
dht_.run((in_port_t)dhtPortUsed_, identity, false);
......@@ -786,7 +820,7 @@ void RingAccount::doRegister_()
return;
}
this_.incomingCall(std::move(msg));
runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg)); });
}
);
return true;
......@@ -795,7 +829,7 @@ void RingAccount::doRegister_()
this_.findCertificate(msg.from.toString().c_str());
}
// public incoming calls allowed or we explicitly authorised this public key
this_.incomingCall(std::move(msg));
runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg)); });
return true;
}
);
......@@ -838,17 +872,15 @@ void RingAccount::doRegister_()
}
}
void RingAccount::incomingCall(dht::IceCandidates&& msg)
void
RingAccount::incomingCall(dht::IceCandidates&& msg)
{
auto from = msg.from.toString();
auto reply_vid = msg.id+1;
RING_WARN("ICE incoming from DHT peer %s\n%s", from.c_str(),
std::string(msg.ice_data.cbegin(), msg.ice_data.cend()).c_str());
auto call = Manager::instance().callFactory.newCall<SIPCall, RingAccount>(*this, Manager::instance().getNewCallID(), Call::CallType::INCOMING);
auto ice = Manager::instance().getIceTransportFactory().createTransport(
("sip:"+call->getCallId()).c_str(), ICE_COMPONENTS, false, getIceOptions());
if (ice->waitForInitialization(ICE_INIT_TIMEOUT) <= 0)
throw std::runtime_error("Can't initialize ICE..");
auto ice = createIceTransport(("sip:"+call->getCallId()).c_str(), ICE_COMPONENTS, false, getIceOptions());
std::weak_ptr<SIPCall> weak_call = call;
auto shared = std::static_pointer_cast<RingAccount>(shared_from_this());
......
......@@ -402,6 +402,9 @@ class RingAccount : public SIPAccountBase {
char contactBuffer_[PJSIP_MAX_URL_SIZE] {};
pj_str_t contact_ {contactBuffer_, 0};
pjsip_transport *via_tp_ {nullptr};
template <class... Args>
std::shared_ptr<IceTransport> createIceTransport(Args... args);
};
} // namespace ring
......
......@@ -353,4 +353,13 @@ SIPAccountBase::onTextMessage(const std::string& from, const std::string& msg)
emitSignal<DRing::ConfigurationSignal::IncomingAccountMessage>(accountID_, from, msg);
}
void
SIPAccountBase::setPublishedAddress(const IpAddr& ip_addr)
{
publishedIp_ = ip_addr;
publishedIpAddress_ = ip_addr.toString();
RING_DBG("[Account %s] Using public address %s", getAccountID().c_str(),
publishedIpAddress_.c_str());
}
} // namespace ring
......@@ -176,10 +176,7 @@ public:
return publishedIp_;
}
void setPublishedAddress(const IpAddr& ip_addr) {
publishedIp_ = ip_addr;
publishedIpAddress_ = ip_addr.toString();
}
void setPublishedAddress(const IpAddr& ip_addr);
/**
* Get the published port, which is the port to be advertised as the port
......
......@@ -1065,4 +1065,17 @@ SIPCall::InvSessionDeleter::operator ()(pjsip_inv_session* inv) const noexcept
pjsip_dlg_dec_lock(inv->dlg);
}
bool
SIPCall::initIceTransport(bool master, unsigned channel_num)
{
auto result = Call::initIceTransport(master, channel_num);
if (result) {
if (const auto& publicIP = getSIPAccount().getPublishedIpAddress()) {
for (unsigned compId = 1; compId <= iceTransport_->getComponentCount(); ++compId)
iceTransport_->registerPublicIP(compId, publicIP);
}
}
return result;
}
} // namespace ring
......@@ -215,6 +215,8 @@ class SIPCall : public Call
virtual std::map<std::string, std::string> getDetails() const;
bool initIceTransport(bool master, unsigned channel_num=4) override;
private:
NON_COPYABLE(SIPCall);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment