2885 lines
90 KiB
C++
2885 lines
90 KiB
C++
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ICEStream.h"
|
|
#include "ICENetworkHelper.h"
|
|
#include "ICEBinding.h"
|
|
#include "ICERelaying.h"
|
|
#include "ICELog.h"
|
|
#include "ICESync.h"
|
|
#include "ICESession.h"
|
|
|
|
#include <stdexcept>
|
|
#include <strstream>
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
|
|
using namespace ice;
|
|
|
|
#define LOG_SUBSYSTEM "ice"
|
|
|
|
const char* ice::RunningStateToString(RunningState state)
|
|
{
|
|
switch(state)
|
|
{
|
|
case None: return "None";
|
|
case CandidateGathering: return "CandidateGathering";
|
|
case EliminateRedudand: return "EliminateRedudand";
|
|
case ComputingFoundations: return "ComputingFoundations";
|
|
case StartingKeepAlives: return "StartingKeepAlives";
|
|
case PrioritizingCandidates: return "PrioritizingCandidates";
|
|
case ChoosingDefault: return "ChoosingDefault";
|
|
case CreatingSDP: return "CreatingSDP";
|
|
case ConnCheck: return "ConnCheck";
|
|
case Failed: return "Failed";
|
|
case Success: return "Success";
|
|
}
|
|
return "Undefined";
|
|
}
|
|
|
|
// This class is intended to bring client binding results with candidates
|
|
class BindingAction: public Action
|
|
{
|
|
public:
|
|
BindingAction(Stream& stream, StackConfig& config)
|
|
:Action(stream, config)
|
|
{
|
|
}
|
|
|
|
virtual void finished(Transaction& st)
|
|
{
|
|
// Declare possible keepalive binding transaction
|
|
ClientBinding* kaBinding = NULL;
|
|
|
|
// Cast finished transaction to ClientBinding
|
|
ClientBinding& cb = dynamic_cast<ClientBinding&>(st);
|
|
|
|
// Iterate local candidate to update values
|
|
for (unsigned i=0; i<mStream.mLocalCandidate.size(); i++)
|
|
{
|
|
Candidate& c = mStream.mLocalCandidate[i];
|
|
if (c.mType == Candidate::ServerReflexive && c.mComponentId == st.component())
|
|
{
|
|
// Update candidate state
|
|
c.mFailed = cb.state() == Transaction::Failed;
|
|
c.mReady = true;
|
|
if (!c.mFailed)
|
|
c.mExternalAddr = cb.mappedAddress();
|
|
}
|
|
}
|
|
|
|
if (cb.state() == Transaction::Success)
|
|
{
|
|
ICELogInfo( << "Stack ID " << st.stackId() << ". Gathered reflexive address " << cb.mappedAddress().toStdString());
|
|
|
|
// Delete other parallel gathering requests to avoid races
|
|
mStream.removeGatherRequests(st.component(), cb.failoverId(), &st);
|
|
|
|
// Set working address
|
|
mStream.mConfig.mServerAddr4 = st.destination();
|
|
|
|
#ifdef ICE_ENABLE_KEEPALIVE
|
|
// Create keepalive transaction (it is usual Binding)
|
|
kaBinding = new ClientBinding();
|
|
kaBinding->setStackId(mStream.mStackId);
|
|
|
|
// Set destination address
|
|
kaBinding->setDestination(cb.transportType() == IPv4 ? mConfig.mServerAddr4 : mConfig.mServerAddr6);
|
|
|
|
// Set source component port number
|
|
kaBinding->setComponent(st.component());
|
|
kaBinding->setKeepalive(true);
|
|
kaBinding->setInterval(mConfig.mKeepAliveInterval);
|
|
mStream.mActiveChecks.addRegular(kaBinding);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ICELogCritical( << "Stack ID " << st.stackId() << ". Failed to gather reflexive candidate.");
|
|
mStream.mErrorCode = cb.errorCode();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
class RelayingAction: public Action
|
|
{
|
|
protected:
|
|
bool mAutoreleaseAllocation;
|
|
|
|
public:
|
|
RelayingAction(Stream& stream, StackConfig& config)
|
|
:Action(stream, config), mAutoreleaseAllocation(false)
|
|
{
|
|
}
|
|
|
|
void autoreleaseAllocation()
|
|
{
|
|
mAutoreleaseAllocation = true;
|
|
}
|
|
|
|
virtual void finished(Transaction& transaction)
|
|
{
|
|
ClientAllocate& ca = dynamic_cast<ClientAllocate&>(transaction);
|
|
|
|
// Increase counter of finished Allocate requests
|
|
mStream.mFailoverRequestsFinished++;
|
|
ICELogDebug(<< "Increase failover gathering finished requests counter to " << mStream.mFailoverRequestsFinished);
|
|
|
|
// Check if this Allocation must be freed ASAP
|
|
if (mAutoreleaseAllocation)
|
|
{
|
|
if (ca.state() == Transaction::Success)
|
|
{
|
|
ICELogInfo(<< "Has to free this allocation as it is not needed due to failover scheme");
|
|
// Increase counter of turn allocation for stream. It will be decreased again in ClientRefresh::processSuccessMessage
|
|
mStream.mTurnAllocated++;
|
|
|
|
// Send deallocation message
|
|
ClientRefresh* allocation = new ClientRefresh(0, &mStream, &ca);
|
|
allocation->setKeepalive(false);
|
|
allocation->setDestination(ca.destination());
|
|
mStream.mActiveChecks.addRegular(allocation);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Save latest credentials from server
|
|
if (ca.realm().size() && ca.nonce().size())
|
|
{
|
|
mStream.mCachedRealm = ca.realm();
|
|
mStream.mCachedNonce = ca.nonce();
|
|
}
|
|
|
|
// Update local candidates
|
|
for (unsigned i=0; i<mStream.mLocalCandidate.size(); i++)
|
|
{
|
|
Candidate& c = mStream.mLocalCandidate[i];
|
|
|
|
// Avoid updating of successful candidates to failed state
|
|
if (c.mComponentId == ca.component() && (!c.mReady || c.mFailed))
|
|
{
|
|
c.mReady = true;
|
|
c.mFailed = ca.state() == Transaction::Failed;
|
|
if (!c.mFailed)
|
|
{
|
|
if (c.mType == Candidate::ServerReflexive)
|
|
c.mExternalAddr = ca.reflexiveAddress();
|
|
else
|
|
if (c.mType == Candidate::ServerRelayed)
|
|
c.mExternalAddr = ca.relayedAddress();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ca.state() == Transaction::Success)
|
|
{
|
|
ICELogInfo( << "Stack ID " << transaction.stackId() << ". Gathered reflexive address " << ca.reflexiveAddress().toStdString() << " and relayed address " << ca.relayedAddress().toStdString());
|
|
|
|
// Remove other parallel requests sent to be failover
|
|
mStream.removeGatherRequests(transaction.component(), transaction.failoverId(), &transaction);
|
|
|
|
// Set working address
|
|
mStream.mConfig.mServerAddr4 = transaction.destination();
|
|
|
|
// Create keepalive transaction (cast lifetime parameter to milliseconds)
|
|
#ifdef ICE_ENABLE_KEEPALIVE
|
|
ClientRefresh* cf = new ClientRefresh( ca.lifetime(), &mStream, &ca );
|
|
// Set 5 seconds interval to avoid NAT problems
|
|
cf->setInterval(5);
|
|
//cf->setInterval( ca.lifetime() / 2);
|
|
cf->setKeepalive( true );
|
|
cf->setType( Transaction::KeepAlive );
|
|
cf->setDestination(ca.destination());
|
|
|
|
// Defer transaction run to interval() value
|
|
cf->setTimestamp( ICETimeHelper::timestamp() );
|
|
|
|
// Increase counter of TURN allocations
|
|
mStream.mTurnAllocated++;
|
|
|
|
// Associate Refresh transaction with retransmission timer
|
|
mStream.mActiveChecks.addRegular(cf);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ICELogCritical( << "Stack ID " << transaction.stackId() << ". Failed to gather reflexive/relayed addresses.");
|
|
bool ressurected = false;
|
|
// Check if single server is used and error code is 437 - in this case attempt to recover old Allocation will be made
|
|
if (mStream.mConfig.mServerList4.size() < 2 && ca.errorCode() == 437)
|
|
{
|
|
if (true == (ressurected = mStream.ressurectAllocation(transaction.component())))
|
|
{
|
|
// Increase counter of TURN allocations
|
|
mStream.mTurnAllocated++;
|
|
|
|
ICELogCritical(<< "Stack ID " << transaction.stackId() << ". Ressurected old Refresh transaction. Error will not be raized.");
|
|
}
|
|
}
|
|
|
|
if (!ressurected)
|
|
mStream.mErrorCode = ca.errorCode();
|
|
|
|
// There is no any further processing here - result of transaction will be handled later
|
|
}
|
|
}
|
|
};
|
|
|
|
class CheckPairAction: public Action
|
|
{
|
|
friend struct Stream;
|
|
public:
|
|
CheckPairAction(Stream& stream, StackConfig& config, PCandidatePair& p)
|
|
:Action(stream, config), mPair(p), mNomination(false), mAgentRole(stream.mAgentRole)
|
|
{
|
|
}
|
|
|
|
virtual ~CheckPairAction()
|
|
{
|
|
}
|
|
|
|
virtual void finished(Transaction& transaction)
|
|
{
|
|
CheckResult& cr = dynamic_cast<CheckResult&>(transaction);
|
|
|
|
//ConnectivityCheck& cb = dynamic_cast<ConnectivityCheck&>(transaction);
|
|
|
|
bool success = false;
|
|
if (cr.resultState() == Transaction::Failed)
|
|
{
|
|
// Handle role conflict
|
|
if (cr.resultError() == 487)
|
|
{
|
|
// Change session role
|
|
if (mAgentRole == RoleControlling)
|
|
mStream.mAgentRole = RoleControlled;
|
|
else
|
|
mStream.mAgentRole = RoleControlling;
|
|
|
|
transaction.restart();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
success = true;
|
|
|
|
// A check is considered to be a success if all of the following are
|
|
// true:
|
|
//- the STUN transaction generated a success response
|
|
success &= cr.resultState() == Transaction::Success;
|
|
|
|
if (success && mNomination &&
|
|
mPair->nomination() != CandidatePair::Nomination_Finished)
|
|
{
|
|
ICELogInfo(<< "Received response for nominated request");
|
|
|
|
// Add keep-alive check
|
|
mStream.addKeepAliveCheck(*mPair);
|
|
|
|
// Nomination is finished for this pair
|
|
mPair->setNomination(CandidatePair::Nomination_Finished);
|
|
|
|
// Ensure RTCP component is unfrozen
|
|
mStream.unfreeze(mPair->foundation());
|
|
|
|
// Maybe processing for component is finished?
|
|
mStream.checkNominated();
|
|
|
|
return;
|
|
}
|
|
|
|
// Reset transaction tag for checked pair
|
|
mPair->setTransaction(NULL);
|
|
|
|
ICELogInfo ( << "Stack ID " << transaction.stackId() << ". Check " << mPair->toStdString() << " is " << (success ? "ok" : "failed") << ".");
|
|
|
|
//- the source IP address and port of the response equals the
|
|
// destination IP address and port that the Binding Request was sent
|
|
// to
|
|
if (success)
|
|
success &= (cr.resultSource() == mPair->second().mExternalAddr);
|
|
|
|
//- the destination IP address and port of the response match the
|
|
// source IP address and port that the Binding Request was sent from
|
|
mPair->setState(success ? CandidatePair::Succeeded : CandidatePair::Failed);
|
|
|
|
if (success)
|
|
{
|
|
std::string foundation = mPair->foundation();
|
|
mStream.dumpLocalCandidateList();
|
|
unsigned li = mStream.findLocalCandidate( cr.resultLocal() );
|
|
ICELogDebug( << "Looking for " << cr.resultLocal().toStdString() << " in local candidate list. Result index is " << int(li) );
|
|
if ( li == NoIndex )
|
|
{
|
|
// Add peer reflexive candidate
|
|
ICELogInfo(<< "Add peer reflexive candidate " << cr.resultLocal().toStdString());
|
|
|
|
// Its type is equal to peer reflexive.
|
|
Candidate cand(Candidate::PeerReflexive);
|
|
cand.mExternalAddr = cr.resultLocal();
|
|
|
|
// Its base is set equal to the local candidate of the candidate pair from which the STUN check was sent.
|
|
cand.mLocalAddr = mPair->first().mLocalAddr;
|
|
|
|
// Its priority is set equal to the value of the PRIORITY attribute in the Binding Request.
|
|
cand.mPriority = cr.resultPriority();
|
|
|
|
// Its foundation is selected as described in Section 4.1.1.
|
|
cand.computeFoundation();
|
|
|
|
// This peer reflexive candidate is then added to the list of local
|
|
// candidates for the media stream
|
|
mStream.mLocalCandidate.push_back(cand);
|
|
|
|
// The agent constructs a candidate pair whose local candidate equals
|
|
// the mapped address of the response, and whose remote candidate equals
|
|
// the destination address to which the request was sent.
|
|
CandidatePair _pair;
|
|
|
|
// Use created local peer reflexive candidate
|
|
_pair.first() = cand;
|
|
|
|
// Create new remote candidate
|
|
Candidate& newRemoteCand = _pair.second();
|
|
newRemoteCand.mLocalAddr = cr.resultSource();
|
|
newRemoteCand.mExternalAddr = cr.resultSource();
|
|
newRemoteCand.mPriority = cr.resultPriority();
|
|
newRemoteCand.computeFoundation();
|
|
|
|
_pair.updateFoundation();
|
|
_pair.updatePriority();
|
|
_pair.setRole(CandidatePair::Valid);
|
|
mStream.checkList().add(_pair);
|
|
|
|
// Ensure reference to original ICECandidatePair is valid yet
|
|
ICELogInfo( << "Stack ID " << transaction.stackId() << ". Created peer reflexive candidate and corresponding pair " << _pair.toStdString() << " in valid list.");
|
|
}
|
|
else
|
|
{
|
|
ICELogInfo( << "Stack ID " << transaction.stackId() << ". Pair " << mPair->toStdString() <<" is add to valid list.");
|
|
mPair->setRole(CandidatePair::Valid); // And this pair is valid now
|
|
}
|
|
|
|
// Now - unfreeze all pairs from the same queue and same foundation
|
|
mStream.unfreeze(foundation.c_str());
|
|
mPair->setTransaction(&transaction);
|
|
|
|
// Start regular nomination
|
|
if (mStream.mAgentRole == RoleControlling && !mNomination)
|
|
{
|
|
Component& component = mStream.mComponentMap[transaction.component()];
|
|
|
|
// If remote address is not LAN - maybe it should be delayed to give chance
|
|
// to finish LAN -> LAN checks before.
|
|
// ICE_NOMINATION_WAIT_INTERVAL is responsible for this
|
|
if (!mPair->second().mExternalAddr.isLAN() && ICE_NOMINATION_WAIT_INTERVAL != 0 && mStream.mState != Success)
|
|
{
|
|
// Check if
|
|
if (!component.mNominationWaitIntervalStartTime)
|
|
{
|
|
component.mNominationWaitIntervalStartTime = ICETimeHelper::timestamp();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
unsigned currentTime = ICETimeHelper::timestamp();
|
|
if (ICETimeHelper::findDelta(component.mNominationWaitIntervalStartTime, currentTime) >= ICE_NOMINATION_WAIT_INTERVAL)
|
|
mNomination = true; // Start nomination
|
|
}
|
|
}
|
|
else
|
|
mNomination = true;
|
|
|
|
if (mNomination && mStream.mState != Success)
|
|
{
|
|
component.mNominationWaitIntervalStartTime = 0; // Stop nomination interval timer
|
|
mStream.nominatePair(mPair);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
mPair->setState(CandidatePair::Failed);
|
|
}
|
|
|
|
PCandidatePair& getPair()
|
|
{
|
|
return mPair;
|
|
}
|
|
|
|
void setNomination(bool value)
|
|
{
|
|
mNomination = value;
|
|
}
|
|
|
|
bool nomination() const
|
|
{
|
|
return mNomination;
|
|
}
|
|
|
|
protected:
|
|
PCandidatePair mPair; /// Pair
|
|
bool mNomination; /// Nomination request flag
|
|
int mAgentRole;
|
|
};
|
|
|
|
|
|
|
|
//This class is intended to bring client binding results with candidates
|
|
class ICEInstallPermissionsAction: public Action
|
|
{
|
|
protected:
|
|
InstallPermissionsCallback* mCallback;
|
|
|
|
public:
|
|
ICEInstallPermissionsAction(Stream& stream, StackConfig& config, InstallPermissionsCallback* cb)
|
|
:Action(stream, config), mCallback(cb)
|
|
{
|
|
}
|
|
|
|
~ICEInstallPermissionsAction()
|
|
{
|
|
delete mCallback;
|
|
}
|
|
|
|
virtual void finished(Transaction& st)
|
|
{
|
|
ClientCreatePermission& ccp = dynamic_cast<ClientCreatePermission&>(st);
|
|
int code = 0;
|
|
|
|
if (st.state() == Transaction::Success)
|
|
{
|
|
ClientCreatePermission::IpList al = ccp.ipList();
|
|
std::string iptext;
|
|
for (unsigned i=0; i<al.size(); i++)
|
|
iptext += al[i].ip() + " ";
|
|
ICELogInfo(<<"TURN client permissions are installed for: " << iptext);
|
|
}
|
|
else
|
|
{
|
|
ICELogCritical(<<"Failed to install TURN client permissions.");
|
|
AuthTransaction& auth = dynamic_cast<AuthTransaction&>(st);
|
|
code = auth.errorCode() ? auth.errorCode() : -1;
|
|
}
|
|
if (mCallback)
|
|
mCallback->onPermissionsInstalled(mStream.mId, st.component(), code);
|
|
}
|
|
};
|
|
|
|
static long GStackID = 0;
|
|
|
|
Stream::Stream()
|
|
{
|
|
mErrorCode = 0;
|
|
mState = None;
|
|
mFoundationGenerator = 0xFFFFFFFF;
|
|
mAgentRole = RoleControlling;
|
|
mDefaultIPChanged = false;
|
|
mCanTransmit = false;
|
|
|
|
mStackId = Atomic::increment(&GStackID);
|
|
mTurnAllocated = 0;
|
|
mFailoverRequestsFinished = 0;
|
|
mFailoverIdGenerator = 0;
|
|
}
|
|
|
|
Stream::~Stream()
|
|
{
|
|
}
|
|
|
|
void Stream::setConfig(StackConfig& config)
|
|
{
|
|
mCachedNonce.clear();
|
|
mCachedRealm.clear();
|
|
mConfig = config;
|
|
}
|
|
|
|
int Stream::addComponent(void* tag, unsigned short port4, unsigned short port6)
|
|
{
|
|
int componentID = mComponentMap.size() + 1;
|
|
|
|
Component& c = mComponentMap[componentID];
|
|
c.mTag = tag;
|
|
c.mPort4 = port4;
|
|
c.mPort6 = port6;
|
|
|
|
return componentID;
|
|
}
|
|
void Stream::setAgentRole(AgentRole role)
|
|
{
|
|
mAgentRole = role;
|
|
}
|
|
|
|
void Stream::setLocalPwd(const std::string& pwd)
|
|
{
|
|
mLocalPwd = pwd;
|
|
}
|
|
|
|
void Stream::setLocalUfrag(const std::string& ufrag)
|
|
{
|
|
mLocalUfrag = ufrag;
|
|
}
|
|
|
|
void Stream::setRemotePwd(const std::string& pwd)
|
|
{
|
|
mRemotePwd = pwd;
|
|
}
|
|
|
|
void Stream::setRemoteUfrag(const std::string& ufrag)
|
|
{
|
|
mRemoteUfrag = ufrag;
|
|
}
|
|
|
|
void Stream::setTieBreaker(const std::string& tieBreaker)
|
|
{
|
|
mTieBreaker = tieBreaker;
|
|
}
|
|
|
|
void Stream::gatherCandidates()
|
|
{
|
|
ICELogInfo (<<"Stack ID " << mStackId << ". Attempt to gather candidates. Use IPv4 = " << mConfig.mUseIPv4 << ", IPv6 = " << mConfig.mUseIPv6);
|
|
mState = CandidateGathering;
|
|
mErrorCode = 0;
|
|
mFailoverRequestsFinished = 0;
|
|
mLocalCandidate.clear();
|
|
mDefaultCandidate.clear();
|
|
|
|
// Get list of available IP interfaces
|
|
Lock l(NetworkHelper::instance().guard());
|
|
std::vector<NetworkAddress>& interfaceList = NetworkHelper::instance().interfaceList();
|
|
for (auto& addr: interfaceList)
|
|
ICELogDebug(<< " " << addr.toStdString());
|
|
|
|
// See if there is public IP address
|
|
// Iterate components
|
|
|
|
int numberOfActions = 0;
|
|
|
|
ComponentMap::iterator componentIter;
|
|
for (componentIter = mComponentMap.begin(); componentIter != mComponentMap.end(); ++componentIter)
|
|
{
|
|
ICELogDebug(<< "Checking ICE component " << componentIter->first);
|
|
BindingAction* bindingAction = nullptr;
|
|
RelayingAction *relayingAction = nullptr, /**getIp6Action = nullptr,*/ *getIp4Action = nullptr;
|
|
|
|
// Clear binding results as new allocation will exists anyway
|
|
removeBindingResult(componentIter->first);
|
|
|
|
// Iterate available IP interfaces i.e. local IP addresses
|
|
for (unsigned interfaceIndex = 0; interfaceIndex < interfaceList.size(); interfaceIndex++)
|
|
{
|
|
NetworkAddress& ipInterface = interfaceList[interfaceIndex];
|
|
if (ipInterface.isLoopback() || ipInterface.isLinkLocal() || ipInterface.isZero())
|
|
continue;
|
|
|
|
ICELogInfo(<< "Checking interface " << ipInterface.toStdString());
|
|
|
|
// Find local port number for this
|
|
int port = ipInterface.family() == AF_INET ? componentIter->second.mPort4 :
|
|
componentIter->second.mPort6;
|
|
|
|
// Define host candidate
|
|
Candidate host(Candidate::Host);
|
|
if ((ipInterface.family() == AF_INET && mConfig.mUseIPv4) ||
|
|
(ipInterface.family() == AF_INET6 && mConfig.mUseIPv6))
|
|
{
|
|
host.setLocalAndExternalAddresses(ipInterface,
|
|
ipInterface.family() == AF_INET6 ? componentIter->second.mPort6 : componentIter->second.mPort4);
|
|
host.mComponentId = componentIter->first;
|
|
host.mInterfacePriority = 0;
|
|
// Gathering of host candidate is finished
|
|
host.mReady = true; // Gathering finished
|
|
host.mFailed = false; // With success
|
|
|
|
mLocalCandidate.push_back(host);
|
|
}
|
|
|
|
// Check if IPv4 reflexive address is needed.
|
|
// It can be gathered with IPv4 relaying request also
|
|
bool needReflexiveAddr = mConfig.mUseIPv4 && mConfig.mServerList4.size() > 0;
|
|
|
|
// Check if IPv4 relayed address is needed
|
|
bool needRelayingAddr =
|
|
mConfig.mUseIPv4 && mConfig.mServerList4.size() > 0 && mConfig.mUseTURN;
|
|
|
|
// Check if 6-to-4 relaying is required
|
|
bool needIPv6ToIPv4 = !mConfig.mUseIPv4 && mConfig.mUseIPv6 &&
|
|
!mConfig.mServerAddr6.isEmpty() && !mConfig.mServerAddr6.isZero() && mConfig.mUseProtocolRelay;
|
|
|
|
// Check if 4-to-6 relaying is required
|
|
/*bool needIPv4ToIPv6 = mConfig.mUseIPv4 && mConfig.mUseIPv6 &&
|
|
mConfig.mServerList4.size() > 0 && mConfig.mUseProtocolRelay;
|
|
*/
|
|
|
|
// Prepare candidate instances.
|
|
Candidate reflexive(Candidate::ServerReflexive);
|
|
Candidate relayed(Candidate::ServerRelayed);
|
|
|
|
// Save initial local&external IP and port number
|
|
relayed.setLocalAndExternalAddresses( ipInterface, port );
|
|
reflexive.setLocalAndExternalAddresses( ipInterface, port );
|
|
|
|
// Assign component ID
|
|
relayed.mComponentId = reflexive.mComponentId = componentIter->first;
|
|
|
|
// Put interface priority as zero - there is no configuration option for it yet
|
|
relayed.mInterfacePriority = reflexive.mInterfacePriority = 0;
|
|
|
|
bool restOnPublic = false;
|
|
#ifdef ICE_REST_ON_PUBLICIP
|
|
if ( ipInterface.isPublic() && ipInterface.type() == AF_INET )
|
|
restOnPublic = true;
|
|
#endif
|
|
|
|
// See if there we need reflexive candidate yet
|
|
if (mConfig.mUseSTUN && !restOnPublic && !bindingAction &&
|
|
needReflexiveAddr && ipInterface.family() == AF_INET)
|
|
{
|
|
ICELogInfo(<< "Request reflexive address");
|
|
// Add candidate to list
|
|
mLocalCandidate.push_back(reflexive);
|
|
|
|
// Bind transaction to candidate
|
|
if (!bindingAction)
|
|
bindingAction = new BindingAction(*this, mConfig);
|
|
}
|
|
|
|
if (mConfig.mUseTURN && !restOnPublic && !relayingAction && needRelayingAddr)
|
|
{
|
|
ICELogInfo(<< "Request relayed address");
|
|
// Add relayed candidate to list
|
|
mLocalCandidate.push_back(relayed);
|
|
|
|
// Add reflexive candidate to list
|
|
mLocalCandidate.push_back(reflexive);
|
|
|
|
// Bind allocation to reflexive candidate
|
|
if (!relayingAction)
|
|
relayingAction = new RelayingAction(*this, mConfig);
|
|
}
|
|
|
|
if (!getIp4Action && needIPv6ToIPv4 && ipInterface.family() == AF_INET6)
|
|
{
|
|
ICELogInfo(<< "Requesting IPv6 to IPv4 relay.");
|
|
Candidate c(Candidate::ServerRelayed);
|
|
c.setLocalAndExternalAddresses(ipInterface, port);
|
|
c.mInterfacePriority = 0;
|
|
c.mComponentId = componentIter->first;
|
|
mLocalCandidate.push_back(relayed);
|
|
getIp4Action = new RelayingAction(*this, mConfig);
|
|
}
|
|
|
|
/*
|
|
if (!getIp6Action && needIPv4ToIPv6 && ipInterface.type() == AF_INET)
|
|
{
|
|
ICELogInfo(<< "Requesting IPv4 to IPv6 relay.");
|
|
Candidate c(Candidate::ServerRelayed);
|
|
c.setLocalAndExternalAddresses(ipInterface, port);
|
|
c.mInterfacePriority = 0;
|
|
c.mComponentId = componentIter->first;
|
|
mLocalCandidate.push_back(relayed);
|
|
getIp6Action = new RelayingAction(*this, mConfig);
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (bindingAction)
|
|
{
|
|
bind(mConfig.mServerAddr4, PAction(bindingAction), false/*it is gathering!*/, componentIter->first, mConfig.mServerList4.empty() ? FailoverOff : FailoverOn);
|
|
numberOfActions++;
|
|
}
|
|
if (relayingAction)
|
|
{
|
|
ICELogDebug(<< "Request relayed candidate IPv4 <-> IPv4.");
|
|
AllocateOptions options;
|
|
options.mActionOnFinish = PAction(relayingAction);
|
|
options.mComponent = componentIter->first;
|
|
options.mFailoverOption = mConfig.mServerList4.empty() ? FailoverOff : FailoverOn;
|
|
options.mServerAddress = mConfig.mServerAddr4;
|
|
allocate(options);
|
|
numberOfActions++;
|
|
}
|
|
|
|
if (getIp4Action)
|
|
{
|
|
ICELogDebug(<< "Request relayed candidate IPv6 <-> IPv4.");
|
|
AllocateOptions options;
|
|
options.mActionOnFinish = PAction(getIp4Action);
|
|
options.mComponent = componentIter->first;
|
|
options.mFailoverOption = mConfig.mServerList6.empty() ? FailoverOff : FailoverOn;
|
|
options.mServerAddress = mConfig.mServerAddr6;
|
|
options.mWireFamily = AF_INET6;
|
|
options.mAllocFamily = AF_INET;
|
|
// Ask TURN server to allocation IPv4 address via IPv6 address
|
|
allocate(options);
|
|
numberOfActions++;
|
|
}
|
|
|
|
// SDK does not use IPv4 to IPv6 relaying for now. Reverse direction has to be used instead.
|
|
/*
|
|
if (getIp6Action)
|
|
{
|
|
AllocateOptions options;
|
|
options.mActionOnFinish = PAction(getIp6Action);
|
|
options.mComponent = componentIter->first;
|
|
options.mAllocFamily = AF_INET6;
|
|
options.mWireFamily = AF_INET;
|
|
options.mServerAddress = mConfig.mServerAddr4;
|
|
options.mFailoverOption = mConfig.mServerList4.empty() ? FailoverOff : FailoverOn;
|
|
// Ask TURN server to allocate IPv6 address via IPv4 socket
|
|
allocate(options);
|
|
numberOfActions++;
|
|
}*/
|
|
|
|
}
|
|
|
|
if (!numberOfActions)
|
|
{
|
|
mState = EliminateRedudand;
|
|
processStateChain();
|
|
}
|
|
}
|
|
|
|
CheckList& Stream::checkList()
|
|
{
|
|
return mCheckList;
|
|
}
|
|
|
|
|
|
void Stream::allocate(const AllocateOptions& options)
|
|
{
|
|
assert(options.mComponent != -1);
|
|
|
|
if (options.mFailoverOption == FailoverOn)
|
|
{
|
|
// If failover scheme is required - spread requests to few servers
|
|
std::vector<NetworkAddress>& servers = options.mWireFamily == AF_INET ? mConfig.mServerList4 : mConfig.mServerList6;
|
|
int failoverId = ++mFailoverIdGenerator;
|
|
for (std::vector<NetworkAddress>::iterator addrIter = servers.begin(); addrIter != servers.end(); addrIter++)
|
|
{
|
|
AllocateOptions options2(options);
|
|
options2.mServerAddress = *addrIter;
|
|
options2.mFailoverId = failoverId;
|
|
options2.mFailoverOption = FailoverOff;
|
|
allocate(options2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClientAllocate* allocation = new ClientAllocate(mConfig.mTurnLifetime);
|
|
allocation->setStackId(mStackId);
|
|
allocation->setWireFamily(options.mWireFamily);
|
|
allocation->setAllocFamily(options.mAllocFamily);
|
|
allocation->setDestination(options.mServerAddress);
|
|
allocation->setComponent(options.mComponent);
|
|
allocation->setFailoverId(options.mFailoverId);
|
|
|
|
// Use auth for TURN server
|
|
allocation->setPassword(mConfig.mTurnPassword);
|
|
allocation->setUsername(mConfig.mTurnUsername);
|
|
|
|
// Associate it with action
|
|
allocation->setAction(options.mActionOnFinish);
|
|
|
|
mActiveChecks.addRegular(allocation);
|
|
}
|
|
}
|
|
|
|
void Stream::bind(const NetworkAddress& dest, PAction action, bool /*auth*/, int component, Failover f)
|
|
{
|
|
assert(component != -1);
|
|
|
|
if (f == FailoverOn)
|
|
{
|
|
for (std::vector<NetworkAddress>::iterator addrIter = mConfig.mServerList4.begin(); addrIter != mConfig.mServerList4.end(); addrIter++)
|
|
{
|
|
bind(*addrIter, action, false, component, FailoverOff);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ClientBinding* binding = new ClientBinding();
|
|
binding->setStackId(mStackId);
|
|
binding->setDestination(dest);
|
|
binding->setComponent(component);
|
|
|
|
// Associate it with action
|
|
binding->setAction(action);
|
|
|
|
mActiveChecks.addRegular(binding);
|
|
}
|
|
}
|
|
|
|
|
|
bool Stream::hasPortNumber(int family, unsigned short portNumber, int* component)
|
|
{
|
|
ComponentMap::iterator componentIterator;
|
|
for (componentIterator = mComponentMap.begin(); componentIterator != mComponentMap.end(); ++componentIterator)
|
|
{
|
|
if ((family == AF_INET && componentIterator->second.mPort4 == portNumber) ||
|
|
(family == AF_INET6 && componentIterator->second.mPort6 == portNumber))
|
|
{
|
|
if (component)
|
|
*component = componentIterator->first;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unsigned Stream::findRemoteCandidate(const NetworkAddress& addr, int componentID)
|
|
{
|
|
for (size_t i=0; i<mRemoteCandidate.size(); i++)
|
|
{
|
|
Candidate& cand = mRemoteCandidate[i];
|
|
|
|
if (cand.mExternalAddr == addr && cand.mComponentId == componentID)
|
|
return (unsigned)i;
|
|
}
|
|
|
|
return NoIndex;
|
|
}
|
|
|
|
unsigned Stream::findLocalCandidate(const NetworkAddress& addr)
|
|
{
|
|
for (size_t i=0; i<mLocalCandidate.size(); i++)
|
|
{
|
|
Candidate& cand = mLocalCandidate[i];
|
|
|
|
if (cand.mExternalAddr == addr)
|
|
return (unsigned)i;
|
|
}
|
|
|
|
return NoIndex;
|
|
}
|
|
|
|
void Stream::cancelCheck(CandidatePair& p)
|
|
{
|
|
if (mActiveChecks.exists(reinterpret_cast<Transaction*>(p.transaction())))
|
|
{
|
|
ICELogInfo(<< "Transaction for pair " << p.toStdString() << " is cancelled");
|
|
mActiveChecks.erase((Transaction*)p.transaction());
|
|
}
|
|
}
|
|
|
|
class EraseBindingAndRelaying: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
ClientRefresh* c = dynamic_cast<ClientRefresh*>(t);
|
|
if (c)
|
|
{
|
|
if (!c->lifetime())
|
|
return false;
|
|
}
|
|
|
|
if ((Transaction::Binding | Transaction::Relaying) & t->type())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class KeepDeallocationRequest: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
ClientRefresh* c = dynamic_cast<ClientRefresh*>(t);
|
|
if (c)
|
|
{
|
|
if (!c->lifetime())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
void Stream::cancelAllocations()
|
|
{
|
|
mActiveChecks.erase(KeepDeallocationRequest());
|
|
}
|
|
|
|
void Stream::handleBindingRequest(ServerBinding& binding, ByteBuffer& buffer, int component)
|
|
{
|
|
if (mComponentMap.find(component) == mComponentMap.end())
|
|
return;
|
|
|
|
// If the source transport address of the request does not match any
|
|
// existing remote candidates, it represents a new peer reflexive remote
|
|
// candidate. This candidate is constructed as follows:
|
|
unsigned candidateIndex = findRemoteCandidate(buffer.remoteAddress(), component);
|
|
if (candidateIndex == NoIndex)
|
|
{
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Remote candidate with address " << buffer.remoteAddress().toStdString() << " is not found so creating peer reflexive candidate.");
|
|
|
|
// Construct candidate
|
|
Candidate newRemoteCand(Candidate::PeerReflexive);
|
|
|
|
// Set priority from request's priority attribute
|
|
newRemoteCand.mPriority = binding.priorityValue();
|
|
|
|
// Set address
|
|
newRemoteCand.setLocalAndExternalAddresses(buffer.remoteAddress());
|
|
|
|
// The foundation of the candidate is set to an arbitrary value,
|
|
// different from the foundation for all other remote candidates. If
|
|
// any subsequent offer/answer exchanges contain this peer reflexive
|
|
// candidate in the SDP, it will signal the actual foundation for the
|
|
// candidate.
|
|
sprintf(newRemoteCand.mFoundation, "%u", --mFoundationGenerator);
|
|
|
|
// The component ID of this candidate is set to the component ID for
|
|
// the local candidate to which the request was sent.
|
|
newRemoteCand.mComponentId = component;
|
|
|
|
// This candidate is added to the list of remote candidates. However,
|
|
// the agent does not pair this candidate with any local candidates.
|
|
mRemoteCandidate.push_back(newRemoteCand);
|
|
|
|
candidateIndex = (unsigned)mRemoteCandidate.size()-1;
|
|
}
|
|
|
|
// Next, the agent constructs a pair whose local candidate is equal to
|
|
// the transport address on which the STUN request was received, and a
|
|
// remote candidate equal to the source transport address where the
|
|
// request came from (which may be peer-reflexive remote candidate that
|
|
// was just learned).
|
|
CandidatePair _pair;
|
|
|
|
// Find local transport address (IP)
|
|
NetworkAddress interfaceIP;
|
|
if (buffer.relayed())
|
|
{
|
|
// Find relayed ip:port for given component
|
|
for (unsigned i=0; i<mLocalCandidate.size(); i++)
|
|
if (mLocalCandidate[i].mType == Candidate::ServerRelayed && mLocalCandidate[i].component() == buffer.component())
|
|
interfaceIP = mLocalCandidate[i].mExternalAddr;
|
|
}
|
|
|
|
// If looking for relayed candidate failed or it is not relayed candidate at all - just find source interface for peer address
|
|
if (interfaceIP.isEmpty())
|
|
{
|
|
interfaceIP = NetworkHelper::instance().sourceInterface(buffer.remoteAddress());
|
|
|
|
// Find local port number
|
|
switch (buffer.remoteAddress().family())
|
|
{
|
|
case AF_INET:
|
|
interfaceIP.setPort(mComponentMap[buffer.component()].mPort4);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
interfaceIP.setPort(mComponentMap[buffer.component()].mPort6);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// And try to find local candidate with such address - addrToFind
|
|
unsigned existingLocalCandIndex = findLocalCandidate(interfaceIP);
|
|
|
|
// There MUST be such candidate!
|
|
if (existingLocalCandIndex == NoIndex)
|
|
{
|
|
unsigned short port = interfaceIP.port();
|
|
std::string ip = interfaceIP.ip();
|
|
ICELogCritical(<<"Failed to find local candidate for " << ip << ":" << port << ". Ignoring binding request");
|
|
return;
|
|
}
|
|
|
|
// Construct pair
|
|
_pair.first() = mLocalCandidate[existingLocalCandIndex];
|
|
_pair.second() = mRemoteCandidate[candidateIndex];
|
|
|
|
// Since both candidates are known to the agent, it
|
|
// can obtain their priorities and compute the candidate pair priority.
|
|
_pair.updateFoundation();
|
|
_pair.updatePriority();
|
|
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Constructed candidate pair " << _pair.toStdString());
|
|
|
|
if (mAgentRole == RoleControlled && binding.hasUseCandidate())
|
|
{
|
|
_pair.setNomination(CandidatePair::Nomination_Finished);
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Pair " << _pair.toStdString() << " nominated as received incoming Binding request with UseCandidate attribute.");
|
|
}
|
|
|
|
// This pair is then looked up in the check list.
|
|
PCandidatePair existingPair = mCheckList.findEqualPair(_pair, CheckList::CT_TreatHostAsUniform);
|
|
|
|
if (existingPair)
|
|
{
|
|
// Nominate new pair if the similar pair was nominated before
|
|
if (_pair.nomination() == CandidatePair::Nomination_Finished)
|
|
existingPair->setNomination(CandidatePair::Nomination_Finished);
|
|
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Pair " << existingPair->toStdString()
|
|
<< " already exists in check list. Its state is "
|
|
<< CandidatePair::stateToString(existingPair->state()));
|
|
|
|
if (mConfig.mTreatRequestAsConfirmation)
|
|
{
|
|
if (existingPair->transaction())
|
|
{
|
|
ConnectivityCheck* cc = dynamic_cast<ConnectivityCheck*>((Transaction*)existingPair->transaction());
|
|
if (cc)
|
|
{
|
|
if (!cc->removed())
|
|
cc->confirmTransaction(buffer.remoteAddress());
|
|
}
|
|
}
|
|
}
|
|
|
|
// There can be one of several outcomes:
|
|
// If the state of that pair is Waiting or Frozen, a check for
|
|
// that pair is enqueued into the triggered check queue if not
|
|
// already present.
|
|
switch (existingPair->state())
|
|
{
|
|
case CandidatePair::Frozen:
|
|
case CandidatePair::Waiting:
|
|
existingPair->setRole(CandidatePair::Triggered);
|
|
existingPair->setState(CandidatePair::Waiting);
|
|
break;
|
|
|
|
case CandidatePair::InProgress:
|
|
// If the state of that pair is In-Progress, the agent cancels the
|
|
// in-progress transaction. Cancellation means that the agent
|
|
// will not retransmit the request, will not treat the lack of
|
|
// response to be a failure, but will wait the duration of the
|
|
// transaction timeout for a response. In addition, the agent
|
|
// MUST create a new connectivity check for that pair
|
|
// (representing a new STUN Binding Request transaction) by
|
|
// enqueuing the pair in the triggered check queue. The state of
|
|
// the pair is then changed to Waiting.
|
|
if (existingPair->role() == CandidatePair::Regular)
|
|
{
|
|
cancelCheck(*existingPair);
|
|
existingPair->setState(CandidatePair::Waiting);
|
|
existingPair->setRole(CandidatePair::Triggered);
|
|
|
|
// Re-run new check in prioritized list
|
|
Transaction* t = createCheckRequest(existingPair);
|
|
if (buffer.relayed())
|
|
{
|
|
t->setComment("Check from " + existingPair->first().mExternalAddr.toStdString() +
|
|
" to " + existingPair->second().mExternalAddr.toStdString());
|
|
t->setRelayed(true);
|
|
}
|
|
existingPair->setTransaction(t);
|
|
mActiveChecks.addPrioritized(t);
|
|
}
|
|
break;
|
|
|
|
case CandidatePair::Failed:
|
|
// If the state of the pair is Failed, it is changed to Waiting
|
|
// and the agent MUST create a new connectivity check for that
|
|
// pair (representing a new STUN Binding Request transaction), by
|
|
// enqueuing the pair in the triggered check queue.
|
|
existingPair->setState(CandidatePair::Waiting);
|
|
existingPair->setRole(CandidatePair::Triggered);
|
|
break;
|
|
|
|
case CandidatePair::Succeeded:
|
|
//existingPair.setNominated();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the pair is not already on the check list:
|
|
// The pair is inserted into the check list based on its priority
|
|
|
|
// Its state is set to Waiting
|
|
_pair.setState(CandidatePair::Waiting);
|
|
_pair.setRole(CandidatePair::Triggered);
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Adding pair " << _pair.toStdString() << " to triggered check list");
|
|
mCheckList.add(_pair);
|
|
ICELogInfo(<< "Resulting check list is \r\n" << mCheckList.toStdString());
|
|
}
|
|
}
|
|
|
|
bool Stream::processData(StunMessage& msg, ByteBuffer& buffer, int component)
|
|
{
|
|
bool result = true;
|
|
|
|
// Depending on the state call the handler
|
|
if (mState < CreatingSDP)
|
|
{
|
|
result = handleCgIn(msg, buffer.remoteAddress());
|
|
if (result)
|
|
processStateChain();
|
|
return result;
|
|
}
|
|
else
|
|
if (mState >= ConnCheck)
|
|
{
|
|
if (handleConnChecksIn(msg, buffer.remoteAddress()))
|
|
{
|
|
ICELogDebug(<< "Found response for transaction from " << buffer.remoteAddress().toStdString());
|
|
checkNominated();
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
if (handleConnChecksIn(msg, buffer.remoteAddress()))
|
|
return true;
|
|
|
|
|
|
// Check if source address&request belongs to TURN server - so it is relayed candidate in action
|
|
if (buffer.remoteAddress() == mConfig.mServerAddr4)
|
|
{
|
|
if (buffer.size() < 4)
|
|
return false;
|
|
|
|
TurnPrefix prefix = *reinterpret_cast<const TurnPrefix*>(buffer.data());
|
|
|
|
// For requests being received on a relayed candidate, the source
|
|
// transport address used for STUN processing (namely, generation of the
|
|
// XOR-MAPPED-ADDRESS attribute) is the transport address as seen by the
|
|
// TURN server. That source transport address will be present in the
|
|
// REMOTE-ADDRESS attribute of a Data Indication message, if the Binding
|
|
// Request was delivered through a Data Indication (a TURN server
|
|
// delivers packets encapsulated in a Data Indication when no active
|
|
// destination is set). If the Binding Request was not encapsulated in
|
|
// a Data Indication, that source address is equal to the current active
|
|
// destination for the TURN session.
|
|
|
|
//check channel binding prefix
|
|
if (mTurnPrefixMap.find(prefix) != mTurnPrefixMap.end())
|
|
{
|
|
NetworkAddress& sourceAddr = mTurnPrefixMap[prefix];
|
|
buffer.setRemoteAddress(sourceAddr);
|
|
buffer.erase(0, 4);
|
|
buffer.setRelayed(true);
|
|
}
|
|
}
|
|
|
|
handleIncomingRequest(msg, buffer, component);
|
|
|
|
if (mCheckList.state() == CheckList::Failed)
|
|
mState = Failed;
|
|
else
|
|
checkNominated();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool Stream::handleCgIn(StunMessage& msg, NetworkAddress& address)
|
|
{
|
|
if (!mActiveChecks.processIncoming(msg, address))
|
|
return false;
|
|
if (mLocalCandidate.empty())
|
|
return true;
|
|
|
|
// Check if failover and relaying is used
|
|
if (mConfig.mUseTURN && !mConfig.mServerList4.empty())
|
|
{
|
|
int successCounter = 0, failedCounter = 0;
|
|
for (size_t i=0; i<mLocalCandidate.size(); i++)
|
|
if (mLocalCandidate[i].mReady)
|
|
{
|
|
if (mLocalCandidate[i].mFailed)
|
|
failedCounter++;
|
|
else
|
|
successCounter++;
|
|
}
|
|
ICELogDebug(<< "Succeed " << successCounter << " candidates, failed " << failedCounter << " candidates from " << (int)mLocalCandidate.size());
|
|
if ((size_t)successCounter >= mLocalCandidate.size())
|
|
{
|
|
// Shift to next state
|
|
ICELogInfo(<< "All candidates succeeded, shift to EliminateRedudand");
|
|
mState = EliminateRedudand;
|
|
}
|
|
else
|
|
{
|
|
// See if all gathering requests are performed.
|
|
if ((size_t)mFailoverRequestsFinished == mConfig.mServerList4.size() * mComponentMap.size())
|
|
{
|
|
ICELogInfo(<< "All failover gathering requests finished, shift to Failed");
|
|
mState = Failed;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Look if all gathering checks are finished
|
|
bool finished = true;
|
|
|
|
for (size_t i=0; i<mLocalCandidate.size() && finished; i++)
|
|
if (false == mLocalCandidate[i].mReady)
|
|
finished &= false;
|
|
|
|
// NOTE! Checking for timeout is made in other place!
|
|
if (finished && mState != /*RunningState::*/Failed)
|
|
mState = EliminateRedudand;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool FailedCand ( const ice::Candidate& value ) {
|
|
return value.mFailed;
|
|
}
|
|
|
|
bool GreaterCand( const ice::Candidate& c1, const ice::Candidate& c2)
|
|
{
|
|
if (c1.mExternalAddr.port() > c2.mExternalAddr.port())
|
|
return true;
|
|
else
|
|
if (c1.mExternalAddr.port() < c2.mExternalAddr.port())
|
|
return false;
|
|
|
|
if (c1.mExternalAddr.ip() > c2.mExternalAddr.ip())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool RedudandCand( const ice::Candidate& c1, const ice::Candidate& c2)
|
|
{
|
|
return c1.mExternalAddr == c2.mExternalAddr;
|
|
}
|
|
|
|
void Stream::Handle_ER()
|
|
{
|
|
// Remove failed candidates (without STUN/TURN responses)
|
|
mLocalCandidate.erase(std::remove_if(mLocalCandidate.begin(), mLocalCandidate.end(),
|
|
FailedCand), mLocalCandidate.end());
|
|
|
|
// Sort candidates by priority
|
|
unsigned int cand_per_component = unsigned(( mLocalCandidate.size() ) / mComponentMap.size());
|
|
for (unsigned int i=0; i<mComponentMap.size(); i++)
|
|
{
|
|
CandidateVector::iterator bit = mLocalCandidate.begin() + i * cand_per_component;
|
|
CandidateVector::iterator fit = mLocalCandidate.begin() + (i+1) * cand_per_component;
|
|
if (i == mComponentMap.size() - 1)
|
|
fit = mLocalCandidate.end();
|
|
|
|
std::sort(bit, fit, GreaterCand);
|
|
}
|
|
|
|
// Leave only unique candidates
|
|
mLocalCandidate.erase(std::unique(mLocalCandidate.begin(), mLocalCandidate.end(), RedudandCand), mLocalCandidate.end());
|
|
|
|
mState = ComputingFoundations;
|
|
}
|
|
|
|
void Stream::Handle_CF()
|
|
{
|
|
//create single list of all candidates
|
|
CandidateVector::iterator cit;
|
|
|
|
for (cit = mLocalCandidate.begin(); cit != mLocalCandidate.end(); ++cit)
|
|
cit->computeFoundation();
|
|
|
|
mState = StartingKeepAlives;
|
|
}
|
|
|
|
void Stream::Handle_SKA()
|
|
{
|
|
#ifdef ICE_ENABLE_KEEPALIVE
|
|
//TODO: replace successful transactions to keep alive state
|
|
#endif
|
|
|
|
mState = PrioritizingCandidates;
|
|
}
|
|
|
|
bool PriorityGreater(const Candidate& c1, const Candidate& c2)
|
|
{
|
|
return c1.mPriority > c2.mPriority;
|
|
}
|
|
|
|
void Stream::Handle_PC()
|
|
{
|
|
for (size_t i = 0; i < mLocalCandidate.size(); ++i)
|
|
mLocalCandidate[i].computePriority(mConfig.mTypePreferenceList);
|
|
|
|
// Find the candidates range for each component ID
|
|
ComponentMap::iterator componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
{
|
|
// Find range start
|
|
size_t minIndex = 0xFFFFFFFF, maxIndex = 0;
|
|
|
|
for (CandidateVector::size_type i = 0; i < mLocalCandidate.size(); i++)
|
|
{
|
|
if (mLocalCandidate[i].mComponentId == (int)componentIter->first)
|
|
{
|
|
// Wow, we have found first candidate with the specified componentID
|
|
minIndex = i;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find range max
|
|
for (int i = (int)mLocalCandidate.size()-1; i >=0; i--)
|
|
{
|
|
if (mLocalCandidate[i].mComponentId == componentIter->first)
|
|
{
|
|
// Wow, we have found first candidate with the specified componentID
|
|
maxIndex = i;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (minIndex == 0xFFFFFFFF)
|
|
return;
|
|
|
|
CandidateVector::iterator bit = mLocalCandidate.begin() + minIndex;
|
|
CandidateVector::iterator fit;
|
|
if (maxIndex == mLocalCandidate.size()-1)
|
|
fit = mLocalCandidate.end();
|
|
else
|
|
fit = mLocalCandidate.begin() + maxIndex + 1;
|
|
|
|
std::sort(bit, fit, PriorityGreater);
|
|
}
|
|
|
|
mState = ChoosingDefault;
|
|
}
|
|
|
|
void Stream::Handle_CD()
|
|
{
|
|
mDefaultCandidate.clear();
|
|
ComponentMap::iterator componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
mDefaultCandidate[componentIter->first] = findDefaultCandidate(componentIter->first);
|
|
|
|
if (mDefaultCandidate.empty())
|
|
throw std::logic_error("Cannot find default candidate.");
|
|
|
|
mState = CreatingSDP;
|
|
}
|
|
|
|
Candidate Stream::findDefaultCandidate(int componentID)
|
|
{
|
|
// Extract all candidates for specified component to separate vector
|
|
CandidateVector candidateList;
|
|
for (size_t i=0; i<mLocalCandidate.size();i++)
|
|
if (mLocalCandidate[i].mComponentId == componentID)
|
|
candidateList.push_back(mLocalCandidate[i]);
|
|
|
|
// Check if we have reflexive candidate - return it
|
|
for (size_t i=0; i<candidateList.size(); i++)
|
|
if (candidateList[i].mType == Candidate::ServerReflexive)
|
|
return candidateList[i];
|
|
|
|
// Find best interface for public IP peer address
|
|
NetworkAddress foundIP = NetworkHelper::instance().sourceInterface(NetworkAddress(mConfig.mTargetIP, 1000));
|
|
if (!foundIP.isEmpty())
|
|
{
|
|
for (size_t i=0; i<candidateList.size(); i++)
|
|
{
|
|
if (candidateList[i].mType == Candidate::Host &&
|
|
candidateList[i].mLocalAddr.ip() == foundIP.ip())
|
|
return candidateList[i];
|
|
}
|
|
}
|
|
|
|
for (size_t i=0; i<candidateList.size(); i++)
|
|
if (candidateList[i].mType == Candidate::Host)
|
|
return candidateList[i];
|
|
|
|
return Candidate(Candidate::Host);
|
|
}
|
|
|
|
bool Stream::handleConnChecksIn(StunMessage& msg, NetworkAddress& address)
|
|
{
|
|
/* if (mRemotePwd.empty())
|
|
return false; */
|
|
|
|
bool result;
|
|
result = mActiveChecks.processIncoming(msg, address);
|
|
return result;
|
|
}
|
|
|
|
void Stream::handleIncomingRequest(StunMessage& msg, ByteBuffer& buffer, int component)
|
|
{
|
|
// Check if it is binding request
|
|
ServerBinding binding;
|
|
|
|
// Use Fingerprint&MessageIntegrity attributes to generate responses
|
|
binding.addFingerprint(true);
|
|
binding.addShortTermCredentials(true);
|
|
|
|
// Set username and password
|
|
binding.setUsername(mLocalUfrag + ":" + mRemoteUfrag);
|
|
binding.setPassword(mLocalPwd);
|
|
|
|
// Is this request Bind?
|
|
if (binding.processData(msg, buffer.remoteAddress()))
|
|
{
|
|
// Check if packet has message integrity with local password
|
|
if (!msg.validatePacket(mLocalPwd))
|
|
return;
|
|
|
|
// Is binding processed ok?
|
|
if (binding.gotRequest())
|
|
{
|
|
// Is role conflict detected? it happens on user agent level - not streams or components
|
|
if (binding.role() == mAgentRole && binding.role())
|
|
{
|
|
bool errorGenerated = handleRoleConflict(binding);
|
|
if (errorGenerated)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ICELogInfo( << "Stack ID " << mStackId << ". Got a binding request from " << buffer.remoteAddress().toStdString() << ".");
|
|
|
|
handleBindingRequest(binding, buffer, component);
|
|
} //of Generate 400
|
|
|
|
// Create response packet
|
|
ByteBuffer* responseBuffer = binding.generateData(true);
|
|
|
|
// If there is a response packet
|
|
if (responseBuffer)
|
|
{
|
|
// Set comment
|
|
responseBuffer->setComment("Response for connectivity check request.");
|
|
|
|
// Set destination address
|
|
// If request has come via relayed candidate - send it back using SendIndication
|
|
if (buffer.relayed())
|
|
{
|
|
ByteBuffer* si = SendIndication::buildPacket( buffer.remoteAddress(), *responseBuffer, mConfig.mServerAddr4, buffer.component() );
|
|
delete responseBuffer; responseBuffer = si;
|
|
responseBuffer->setComment( "SI for CC response to " + buffer.remoteAddress().toStdString() );
|
|
}
|
|
else
|
|
responseBuffer->setRemoteAddress( buffer.remoteAddress() );
|
|
|
|
// Set component port number
|
|
responseBuffer->setComponent(buffer.component());
|
|
|
|
// Send response immediately
|
|
mResponseQueue.push_back(responseBuffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Stream::checkNominated(int component)
|
|
{
|
|
// If there are no nominated pairs in the valid list for a media
|
|
// stream and the state of the check list is Running, ICE processing
|
|
// continues.
|
|
if (mCheckList.state() != CheckList::Running)
|
|
return;
|
|
|
|
// Attempt to find nominated pair
|
|
unsigned i;
|
|
for (i=0; i<mCheckList.count() && (mCheckList[i]->nomination() != CandidatePair::Nomination_Finished || mCheckList[i]->state() != CandidatePair::Succeeded); i++)
|
|
;
|
|
|
|
if (i == mCheckList.count())
|
|
return;
|
|
|
|
// If there is at least one nominated pair in the valid list for a
|
|
// media stream and the state of the check list is Running:
|
|
|
|
// The agent MUST remove all Waiting and Frozen pairs in the check
|
|
// list and triggered check queue for the same component as the
|
|
// nominated pairs for that media stream
|
|
mCheckList.removePairs(CandidatePair::Frozen, component);
|
|
mCheckList.removePairs(CandidatePair::Waiting, component);
|
|
|
|
#ifndef ICE_DONT_CANCEL_CHECKS_ON_SUCCESS
|
|
// If an In-Progress pair in the check list is for the same
|
|
// component as a nominated pair, the agent SHOULD cease
|
|
// retransmissions for its check if its pair priority is lower
|
|
// than the lowest priority nominated pair for that component.
|
|
PCandidatePair lowestPair = mCheckList.findLowestNominatedPair(component);
|
|
if (lowestPair)
|
|
{
|
|
int64_t lowestPriority = lowestPair->priority();
|
|
|
|
for (unsigned i=0; i<mCheckList.count(); i++)
|
|
{
|
|
CandidatePair& pair = *mCheckList[i];
|
|
if (((pair.state() == CandidatePair::InProgress &&
|
|
pair.priority() < lowestPriority) || pair.nomination() != CandidatePair::Nomination_Finished) && pair.first().component() == component)
|
|
cancelCheck(pair);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Stream::checkNominated()
|
|
{
|
|
if (mState == Success)
|
|
return;
|
|
|
|
// Check if there is running nomination waiting interval timer
|
|
if (ICE_NOMINATION_WAIT_INTERVAL)
|
|
{
|
|
for (auto componentIter = mComponentMap.begin(); componentIter != mComponentMap.end(); ++componentIter)
|
|
{
|
|
if (componentIter->second.mNominationWaitIntervalStartTime && mAgentRole == RoleControlling)
|
|
{
|
|
PCandidatePair validPair = mCheckList.findBestValidPair(componentIter->first);
|
|
if (validPair)
|
|
{
|
|
unsigned currentTime = ICETimeHelper::timestamp();
|
|
if (ICETimeHelper::findDelta(componentIter->second.mNominationWaitIntervalStartTime, currentTime) >= ICE_NOMINATION_WAIT_INTERVAL)
|
|
nominatePair(validPair);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mNominationWaitStartTime && mCheckList.countOfValidPairs() && mAgentRole == RoleControlling)
|
|
{
|
|
unsigned currentTime = ICETimeHelper::timestamp();
|
|
if (ICETimeHelper::findDelta(mNominationWaitStartTime, currentTime) >= ICE_NOMINATION_WAIT_INTERVAL)
|
|
{
|
|
// Iterate components
|
|
for (auto componentIter = mComponentMap.begin(); componentIter != mComponentMap.end(); ++componentIter)
|
|
{
|
|
PCandidatePair validPair = mCheckList.findBestValidPair(componentIter->first);
|
|
if (validPair)
|
|
nominatePair(validPair);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process all components
|
|
ComponentMap::iterator componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
checkNominated(componentIter->first);
|
|
|
|
// Once there is at least one nominated pair in the valid list for
|
|
// every component of at least one media stream and the state of the
|
|
// check list is Running:
|
|
|
|
// The agent MUST change the state of processing for its check
|
|
// list for that media stream to Completed.
|
|
|
|
bool found = true;
|
|
componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
found &= mCheckList.findNominatedPair(componentIter->first).get() != nullptr;
|
|
|
|
if (found && mCheckList.state() == CheckList::Running)
|
|
{
|
|
mCheckList.setState(CheckList::Completed);
|
|
|
|
// Create vector of default
|
|
std::map<int, Candidate> defaultCandList;
|
|
componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
{
|
|
int componentId = componentIter->first;
|
|
PCandidatePair highestPair = mCheckList.findHighestNominatedPair(componentId);
|
|
if (highestPair)
|
|
{
|
|
defaultCandList[componentId] = Candidate(highestPair->first());
|
|
|
|
// Compare with existing default IP list
|
|
if (!Candidate::equal(defaultCandList[componentId], mDefaultCandidate[componentId]))
|
|
mDefaultIPChanged = true;
|
|
}
|
|
}
|
|
|
|
mDefaultCandidate = defaultCandList;
|
|
mCanTransmit = true;
|
|
}
|
|
|
|
// Once the state of each check list is Completed:
|
|
//ICELogDebug(<< "Stack ID " << mStackID << ". Check list state is " << mCheckList.stateToString(mCheckList.state()));
|
|
if (mCheckList.state() == CheckList::Completed)
|
|
{
|
|
// The agent sets the state of ICE processing overall to
|
|
// completed.
|
|
if (mState != Success && mState > CandidateGathering)
|
|
{
|
|
ICELogCritical( << "Stack ID " << mStackId << ". Check list is completed, so session is succeeded.");
|
|
mState = Success;
|
|
}
|
|
}
|
|
else
|
|
if (mCheckList.state() == CheckList::Failed)
|
|
{
|
|
// If the state of the check list is Failed, ICE has not been able to
|
|
// complete for this media stream. The correct behavior depends on
|
|
// the state of the check lists for other media streams:
|
|
|
|
// If all check lists are Failed, ICE processing overall is
|
|
// considered to be in the Failed state, and the agent SHOULD
|
|
// consider the session a failure, SHOULD NOT restart ICE, and the
|
|
// controlling agent SHOULD terminate the entire session.
|
|
if (mState != Failed && mState > CandidateGathering)
|
|
{
|
|
ICELogCritical(<< "Stack ID " << mStackId << ". Check list is failed, so session is failed too.");
|
|
mState = Failed;
|
|
}
|
|
clearChecks();
|
|
//TODO:
|
|
//If at least one of the check lists for other media streams is
|
|
//Completed, the controlling agent SHOULD remove the failed media
|
|
//stream from the session in its updated offer.
|
|
|
|
//If none of the check lists for other media streams are
|
|
//Completed, but at least one is Running, the agent SHOULD let
|
|
//ICE continue.
|
|
}
|
|
}
|
|
|
|
bool Stream::handleRoleConflict(ServerBinding& binding)
|
|
{
|
|
ICELogCritical( << "Stack ID " << mStackId << ". Detected role conflict. Local role is " <<
|
|
(mAgentRole == RoleControlled ? "Controlled" : "Controlling") << ", remote role is " <<
|
|
(binding.role() == RoleControlled ? "Controlled" : "Controlling") << ".");
|
|
|
|
// Compare tie breakers
|
|
int compareResult = memcmp(mTieBreaker.c_str(), binding.remoteTieBreaker().c_str(), 8);
|
|
|
|
if (mAgentRole == RoleControlling)
|
|
{
|
|
// So remove
|
|
if (compareResult >= 0)
|
|
{
|
|
// Queue 487 error to remote peer
|
|
binding.generate487();
|
|
return true;
|
|
}
|
|
else
|
|
mAgentRole = RoleControlled;
|
|
}
|
|
else
|
|
{
|
|
if (compareResult < 0)
|
|
{
|
|
// Enqueue 487 error to remote peer
|
|
binding.generate487();
|
|
return true;
|
|
}
|
|
else
|
|
mAgentRole = RoleControlling;
|
|
}
|
|
|
|
// Recompute pair priorities for all stream's queues and triggered check queue
|
|
mCheckList.updatePairPriorities();
|
|
|
|
return false;
|
|
}
|
|
|
|
void Stream::createOfferSdp(std::vector<std::string>& defaultIP, std::vector<unsigned short>& defaultPort,
|
|
std::vector<std::string>& candidateList)
|
|
{
|
|
// Iterator all components
|
|
ComponentMap::iterator componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
{
|
|
// Find default IP for this component
|
|
defaultIP.push_back(mDefaultCandidate[componentIter->first].mExternalAddr.ip());
|
|
defaultPort.push_back(mDefaultCandidate[componentIter->first].mExternalAddr.port());
|
|
|
|
for (unsigned candidateCounter=0; candidateCounter<mLocalCandidate.size(); candidateCounter++)
|
|
{
|
|
Candidate& cand = mLocalCandidate[candidateCounter];
|
|
|
|
if (cand.mComponentId == (int)componentIter->first)
|
|
{
|
|
candidateList.push_back(cand.createSdp());
|
|
#ifdef ICE_VIRTUAL_CANDIDATES
|
|
if (cand.mType == Candidate::ServerReflexive || cand.mType == Candidate::PeerReflexive)
|
|
{
|
|
for (int i=0; i<ICE_VIRTUAL_CANDIDATES; i++)
|
|
{
|
|
Candidate v = cand;
|
|
v.mExternalAddr.setPort(v.mExternalAddr.port()+i+1);
|
|
candidateList.push_back(v.createSdp());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NetworkAddress Stream::defaultAddress(int componentID)
|
|
{
|
|
if ((int)mDefaultCandidate.size() >= componentID)
|
|
return mDefaultCandidate[componentID].mExternalAddr;
|
|
|
|
if (mComponentMap.find(componentID) != mComponentMap.end())
|
|
{
|
|
NetworkAddress result = NetworkHelper::instance().sourceInterface(NetworkAddress(mConfig.mTargetIP, 1));
|
|
result.setPort(mComponentMap[componentID].mPort4);
|
|
return result;
|
|
}
|
|
return NetworkAddress();
|
|
}
|
|
|
|
void Stream::candidateList(int componentID, std::vector<std::string>& candidateList)
|
|
{
|
|
for (unsigned i=0; i<mLocalCandidate.size(); i++)
|
|
{
|
|
Candidate& cand = mLocalCandidate[i];
|
|
|
|
if ((cand.mComponentId == componentID || componentID == -1) && cand.mReady && !cand.mFailed)
|
|
{
|
|
candidateList.push_back(cand.createSdp());
|
|
#ifdef ICE_VIRTUAL_CANDIDATES
|
|
if (cand.mType == Candidate::ServerReflexive || cand.mType == Candidate::PeerReflexive)
|
|
{
|
|
for (int i=0; i<ICE_VIRTUAL_CANDIDATES; i++)
|
|
{
|
|
Candidate v = cand;
|
|
v.mExternalAddr.setPort(v.mExternalAddr.port()+i+1);
|
|
candidateList.push_back(v.createSdp());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void Stream::isTimeout()
|
|
{
|
|
mActiveChecks.checkTimeout();
|
|
}
|
|
|
|
PByteBuffer Stream::getDataToSend(bool& response, int& component, void*&tag)
|
|
{
|
|
// All responses are served at half priority
|
|
PByteBuffer result;
|
|
if (!mResponseQueue.empty())
|
|
{
|
|
//ICELogInfo(<<"Return to send response packet");
|
|
result = PByteBuffer(mResponseQueue.front());
|
|
mResponseQueue.erase(mResponseQueue.begin());
|
|
response = true;
|
|
}
|
|
else
|
|
{
|
|
response = false;
|
|
result = PByteBuffer(handleConnChecksOut()); // Generates connectivity check/binding requests/responses
|
|
if (result)
|
|
{
|
|
//ICELogInfo(<<"Return to send request packet");
|
|
}
|
|
}
|
|
|
|
if (!result)
|
|
return PByteBuffer();
|
|
|
|
component = result->component();
|
|
tag = mComponentMap[component].mTag;
|
|
|
|
return result;
|
|
}
|
|
|
|
Transaction* Stream::runCheckList(CandidatePair::Role r, CandidatePair::State state)
|
|
{
|
|
Transaction* result = NULL;
|
|
for (unsigned i=0; i<mCheckList.count() && !result; i++)
|
|
{
|
|
// Extract reference to candidate pair
|
|
PCandidatePair& p = mCheckList[i];
|
|
|
|
if (p->role() != r || p->transaction())
|
|
continue;
|
|
|
|
if (p->state() == state)
|
|
{
|
|
ICELogInfo(<<"Creating check request for " << p->toStdString());
|
|
|
|
// Create binding request
|
|
result = createCheckRequest(p);
|
|
|
|
// Mark candidate pair as InProgress
|
|
p->setTransaction(result);
|
|
p->setState(CandidatePair::InProgress);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ByteBuffer* Stream::handleConnChecksOut()
|
|
{
|
|
// Update stream state
|
|
checkNominated();
|
|
|
|
// Loop while we have time to send packets
|
|
if (mScheduleTimer.isTimeToSend())
|
|
{
|
|
// Declare pointer to created check request
|
|
Transaction* request = NULL;
|
|
|
|
// Check for triggered request
|
|
//ICELogInfo(<<"Create checks for triggered waiting pairs for list " << std::endl << mCheckList.toStdString());
|
|
request = runCheckList(CandidatePair::Triggered, CandidatePair::Waiting);
|
|
if (request)
|
|
mActiveChecks.addPrioritized(request);
|
|
else
|
|
{
|
|
request = runCheckList(CandidatePair::Regular, CandidatePair::Waiting);
|
|
if (request)
|
|
mActiveChecks.addRegular(request);
|
|
else
|
|
{
|
|
// Find Frozen check with highest priority and lowest componentID in check list
|
|
request = runCheckList(CandidatePair::Regular, CandidatePair::Frozen);
|
|
if (request)
|
|
mActiveChecks.addRegular(request);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// ICELogDebug(<< "Looking for transaction to send");
|
|
// Loop while active transaction is not found
|
|
Transaction* t; int attemptCounter = 0;
|
|
do
|
|
{
|
|
attemptCounter++;
|
|
t = mActiveChecks.getNextTransaction();
|
|
if (!t)
|
|
continue;
|
|
// This code forces keepalive transaction to its intervals
|
|
if (!t->hasToRunNow())
|
|
t = NULL;
|
|
}
|
|
while (!t && attemptCounter < mActiveChecks.count());
|
|
|
|
if (!t)
|
|
return NULL;
|
|
|
|
ByteBuffer* buffer = t->generateData();
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
// Self test
|
|
/*StunMessage msg;
|
|
msg.parsePacket(*buffer);
|
|
std::ostringstream oss;
|
|
msg.dump(oss);
|
|
ICELogDebug(<< "Self test result: " << oss.str());
|
|
*/
|
|
ICELogInfo(<< "Generating packet for " << (t->relayed() ? "relayed " : "") << t->comment() << " to " << t->destination().toStdString());
|
|
if (t->relayed())
|
|
{
|
|
ByteBuffer* si = SendIndication::buildPacket(t->destination(), *buffer, mConfig.mServerAddr4, t->component());
|
|
si->setComment("Relayed: " + buffer->comment());
|
|
delete buffer; buffer = si;
|
|
}
|
|
|
|
assert(!buffer->remoteAddress().isEmpty());
|
|
return buffer;
|
|
}
|
|
|
|
|
|
Transaction* Stream::createCheckRequest(PCandidatePair& p)
|
|
{
|
|
// Get reference to checked pair
|
|
|
|
// Create transaction - depending on candidates it can be regular ConnectivityCheck or RelayedConnectivityCheck
|
|
ConnectivityCheck* cc = new ConnectivityCheck();
|
|
|
|
cc->setRelayed( p->first().mType == Candidate::ServerRelayed );
|
|
cc->setStackId( mStackId );
|
|
cc->setComponent( p->first().mComponentId );
|
|
|
|
// Add PRIORITY attribute
|
|
unsigned int peerReflexivePriority = 0xFFFFFF * mConfig.mTypePreferenceList[Candidate::PeerReflexive] +
|
|
(p->first().mInterfacePriority << 8) + (256 - p->first().mComponentId);
|
|
|
|
cc->addPriority( peerReflexivePriority );
|
|
|
|
cc->addFingerprint( true );
|
|
cc->setDestination( p->second().mExternalAddr );
|
|
cc->setComment( p->toStdString() );
|
|
|
|
// Add CONTROLLED or CONTROLLING attribute
|
|
switch (mAgentRole)
|
|
{
|
|
case RoleControlled:
|
|
cc->addControlledRole(mTieBreaker);
|
|
break;
|
|
|
|
case RoleControlling:
|
|
cc->addControllingRole(mTieBreaker);
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
// Enable short term credentials
|
|
cc->addShortTermCredentials(true);
|
|
|
|
// Set right username and password
|
|
cc->setUsername(mRemoteUfrag + ":" + mLocalUfrag);
|
|
cc->setPassword(mRemotePwd);
|
|
|
|
// Add action
|
|
cc->setAction(PAction(new CheckPairAction(*this, mConfig, p)));
|
|
|
|
// Do not forget about aggressive nomination
|
|
if (mConfig.mAggressiveNomination)
|
|
cc->addUseCandidate();
|
|
|
|
ICELogInfo( << "Stack ID " << mStackId << ". Created " << (cc->relayed() ? "RCC" : "CC") << " for " << p->toStdString() << " and destination " << cc->destination().toStdString() << ".");
|
|
|
|
return cc;
|
|
}
|
|
|
|
TurnPrefix Stream::findTurnPrefixByAddress(NetworkAddress& peerAddress)
|
|
{
|
|
TurnPrefixMap::iterator tpit = mTurnPrefixMap.begin();
|
|
for (; tpit != mTurnPrefixMap.end(); ++tpit)
|
|
{
|
|
NetworkAddress& addr = tpit->second;
|
|
if (addr == peerAddress)
|
|
return tpit->first;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
class KeepAliveCheckCondition: public TransactionCondition
|
|
{
|
|
protected:
|
|
NetworkAddress mTarget;
|
|
int mComponentId;
|
|
|
|
public:
|
|
KeepAliveCheckCondition(const NetworkAddress& address, int componentId)
|
|
:mTarget(address), mComponentId(componentId)
|
|
{
|
|
|
|
}
|
|
|
|
bool check(Transaction* t) const
|
|
{
|
|
BindingIndication* bi = dynamic_cast<BindingIndication*>(t);
|
|
if (!bi)
|
|
return false;
|
|
if (!bi->keepalive())
|
|
return false;
|
|
|
|
return (t->component() == mComponentId && t->destination() == mTarget);
|
|
}
|
|
};
|
|
|
|
void Stream::addKeepAliveCheck(CandidatePair& _pair)
|
|
{
|
|
#ifdef ICE_ENABLE_KEEPALIVE
|
|
// Ensure it is new keepalive check
|
|
if (mActiveChecks.exists(KeepAliveCheckCondition(_pair.second().mExternalAddr, _pair.first().mComponentId)))
|
|
return;
|
|
|
|
// Create binding indication transaction
|
|
BindingIndication* bi = new BindingIndication(mConfig.mKeepAliveInterval);
|
|
bi->setStackId(mStackId);
|
|
bi->setKeepalive(true);
|
|
bi->setInterval(mConfig.mKeepAliveInterval);
|
|
|
|
// Restart transaction on finish
|
|
//bi->setUserObject(new CandidatePair(_pair));
|
|
|
|
// Do not forget to comment transaction
|
|
bi->setComment("Keepalive check for " + _pair.toStdString());
|
|
|
|
// Set destination
|
|
bi->setDestination(_pair.second().mExternalAddr);
|
|
|
|
// Set component port
|
|
bi->setComponent(_pair.first().mComponentId);
|
|
|
|
// Put transaction to container
|
|
mActiveChecks.addRegular(bi);
|
|
#endif
|
|
}
|
|
|
|
bool Stream::processSdpOffer( std::vector<std::string>& candidateList, std::string defaultIP, unsigned short defaultPort, bool deleteRelayed)
|
|
{
|
|
if (candidateList.empty())
|
|
{
|
|
// Anyway we may need TURN permissions installed
|
|
if (mState >= CreatingSDP && mConfig.mUseTURN)
|
|
{
|
|
ICELogDebug(<< "Installing permissions for default address " << defaultIP << ":" << defaultPort);
|
|
installPermissions(-1, NetworkAddress(defaultIP, defaultPort));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Save remote candidate list
|
|
std::set<int> availComponents;
|
|
|
|
for (size_t i=0; i<candidateList.size(); i++)
|
|
{
|
|
Candidate cand = Candidate::parseSdp(candidateList[i].c_str());
|
|
|
|
if (availComponents.find(cand.mComponentId) == availComponents.end())
|
|
availComponents.insert(cand.mComponentId);
|
|
|
|
unsigned existingIndex = findRemoteCandidate(cand.mExternalAddr, cand.mComponentId);
|
|
if (existingIndex != NoIndex)
|
|
{
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Remote candidate " << cand.mExternalAddr.toStdString() << " is found already, so just update priority value.");
|
|
mRemoteCandidate[existingIndex].mPriority = cand.mPriority;
|
|
}
|
|
else
|
|
{
|
|
ICELogInfo(<< "Stack ID " << mStackId << ". Remote candidate " << cand.mExternalAddr.toStdString() << " is add.");
|
|
mRemoteCandidate.push_back(cand);
|
|
}
|
|
}
|
|
|
|
// Update number of components according to sdp
|
|
ComponentMap validComponentMap;
|
|
|
|
ComponentMap::iterator componentIter = mComponentMap.begin();
|
|
for (; componentIter != mComponentMap.end(); componentIter++)
|
|
{
|
|
if (availComponents.find(componentIter->first) != availComponents.end())
|
|
validComponentMap[componentIter->first] = componentIter->second;
|
|
else
|
|
{
|
|
// Do not deallocate turn candidate here - side effect can be bad
|
|
}
|
|
}
|
|
mComponentMap = validComponentMap;
|
|
|
|
// Ensure there is at least one component
|
|
if (mComponentMap.empty())
|
|
{
|
|
ICELogCritical(<< "SDP offer is not valid; it has no components");
|
|
return false;
|
|
}
|
|
|
|
// Check if candidate list includes default IP:pair
|
|
bool result = candidateListContains(defaultIP, defaultPort);
|
|
|
|
// Delete relayed candidates if needed
|
|
if (deleteRelayed)
|
|
{
|
|
mRemoteRelayedCandidate.clear();
|
|
CandidateVector::iterator candIter = mRemoteCandidate.begin();
|
|
while (candIter != mRemoteCandidate.end())
|
|
{
|
|
if (candIter->mType == Candidate::ServerRelayed)
|
|
{
|
|
mRemoteRelayedCandidate.push_back(*candIter);
|
|
candIter = mRemoteCandidate.erase(candIter);
|
|
}
|
|
else
|
|
candIter++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool Stream::candidateListContains(const std::string& remoteIP, unsigned short remotePort)
|
|
{
|
|
NetworkAddress remote;
|
|
remote.setIp(remoteIP.c_str());
|
|
remote.setPort(remotePort);
|
|
|
|
for (size_t i=0; i<mRemoteCandidate.size(); i++)
|
|
if (mRemoteCandidate[i].mExternalAddr == remote)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void Stream::createCheckList()
|
|
{
|
|
// Iterate all components
|
|
ComponentMap::iterator componentIter;
|
|
for (componentIter = mComponentMap.begin(); componentIter != mComponentMap.end(); componentIter++)
|
|
{
|
|
// Pair local candidates with remote candidates
|
|
for (unsigned localIndex = 0; localIndex < mLocalCandidate.size() && localIndex < ICE_CANDIDATE_LIMIT; localIndex++)
|
|
{
|
|
Candidate& local = mLocalCandidate[localIndex];
|
|
if (local.mComponentId != (int)componentIter->first || !local.mReady )
|
|
continue;
|
|
|
|
for (unsigned remoteIndex=0; remoteIndex<mRemoteCandidate.size() && remoteIndex < ICE_CANDIDATE_LIMIT; remoteIndex++)
|
|
{
|
|
Candidate& remote = mRemoteCandidate[remoteIndex];
|
|
|
|
// Ensure both candidates use the same transport and belongs to the same component ID
|
|
if (local.mComponentId != remote.mComponentId || local.mExternalAddr.family() != remote.mExternalAddr.family())
|
|
continue;
|
|
|
|
if (local.mType == Candidate::ServerRelayed && !remote.mExternalAddr.isPublic())
|
|
continue;
|
|
|
|
#ifdef ICE_SKIP_RELAYED_CHECKS
|
|
if (local.mType == Candidate::ServerRelayed)
|
|
continue;
|
|
#endif
|
|
|
|
CandidatePair pair;
|
|
|
|
pair.first() = local;
|
|
pair.second() = remote;
|
|
if (mAgentRole == RoleControlled)
|
|
{
|
|
pair.setControlledIndex(0);
|
|
pair.setControllingIndex(1);
|
|
}
|
|
else
|
|
if (mAgentRole == RoleControlling)
|
|
{
|
|
pair.setControlledIndex(1);
|
|
pair.setControllingIndex(0);
|
|
}
|
|
else
|
|
assert(0);
|
|
|
|
pair.updatePriority();
|
|
pair.updateFoundation();
|
|
mCheckList.add(pair);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort
|
|
|
|
// Replace server reflexive candidates with their bases + prune list
|
|
mCheckList.prune(ICE_CONNCHECK_LIMIT);
|
|
|
|
|
|
ICELogInfo(<< "Stack ID: " << mStackId << ". Created check list: \n" << mCheckList.toStdString());
|
|
}
|
|
|
|
void
|
|
Stream::startChecks()
|
|
{
|
|
// Set state to connectivity checks
|
|
mState = ConnCheck;
|
|
mErrorCode = 0;
|
|
|
|
// Checklist should not be cleared here - at this moment peer reflexive candidates can exists
|
|
// List of TURN bound channels should not be cleared here too - this binding can still exists on relay
|
|
//mCheckList.clear();
|
|
|
|
// Create TURN permissions
|
|
if (mConfig.mUseTURN)
|
|
installPermissions();
|
|
|
|
// Create check list
|
|
createCheckList();
|
|
|
|
// Create transactions for checklist
|
|
runCheckList(CandidatePair::Regular, CandidatePair::Waiting);
|
|
|
|
// Start send timer
|
|
mScheduleTimer.start(ICE_SCHEDULE_TIMER_INTERVAL);
|
|
}
|
|
|
|
class DeleteChecksAndGathers: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
ClientRefresh* c = dynamic_cast<ClientRefresh*>(t);
|
|
if (c)
|
|
return false;
|
|
ClientChannelBind* ccb = dynamic_cast<ClientChannelBind*>(t);
|
|
if (ccb)
|
|
return false;
|
|
ClientCreatePermission* ccp = dynamic_cast<ClientCreatePermission*>(t);
|
|
if (ccp)
|
|
return false;
|
|
|
|
// Send indication is not listed here; it is sent directly without transaction list proxy.
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void Stream::stopChecks()
|
|
{
|
|
// Clear generated check list
|
|
mCheckList.clear();
|
|
|
|
// Delete corresponding transactions
|
|
mActiveChecks.erase(DeleteChecksAndGathers());
|
|
|
|
// Avoid responding to remote peer's transactions
|
|
if (mState < Failed)
|
|
mState = Failed;
|
|
}
|
|
|
|
|
|
|
|
void Stream::clear()
|
|
{
|
|
ICELogDebug(<< "Clear ICE stream");
|
|
mState = None;
|
|
|
|
// Clear connectivity checks list
|
|
mCheckList.clear();
|
|
|
|
// Delete all transactions except possible deallocation requests
|
|
mActiveChecks.erase(KeepDeallocationRequest());
|
|
|
|
// Remote candidates are not needed anymore
|
|
mRemoteCandidate.clear();
|
|
mRemoteRelayedCandidate.clear();
|
|
mDefaultCandidate.clear();
|
|
mComponentLimit.clear();
|
|
mTurnPrefixMap.clear();
|
|
mScheduleTimer.stop();
|
|
mTurnAllocated = 0;
|
|
mDefaultIPChanged = false;
|
|
for (ComponentMap::iterator componentIter = mComponentMap.begin(); componentIter != mComponentMap.end(); componentIter++)
|
|
componentIter->second.mNominationWaitIntervalStartTime = 0;
|
|
mNominationWaitStartTime = 0;
|
|
mFailoverRequestsFinished = 0;
|
|
}
|
|
|
|
class DeleteConnChecks: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
return (dynamic_cast<ConnectivityCheck*>(t) != NULL);
|
|
}
|
|
};
|
|
|
|
class DeleteChannelBindingRequests: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
return (dynamic_cast<ClientChannelBind*>(t) != NULL);
|
|
}
|
|
};
|
|
|
|
class DeleteClientRefreshes: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
return (dynamic_cast<ClientRefresh*>(t) != NULL);
|
|
}
|
|
};
|
|
|
|
class DeleteCreatePermissions: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
return (dynamic_cast<ClientCreatePermission*>(t) != NULL);
|
|
}
|
|
};
|
|
|
|
class DeleteClientBindings: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
return dynamic_cast<ClientBinding*>(t) != NULL;
|
|
}
|
|
};
|
|
|
|
class DeleteClientAllocations: public TransactionCondition
|
|
{
|
|
public:
|
|
bool check(Transaction* t) const
|
|
{
|
|
return dynamic_cast<ClientAllocate*>(t) != NULL;
|
|
}
|
|
};
|
|
|
|
void Stream::clearForRestart(bool localNetworkChanged)
|
|
{
|
|
ICELogDebug(<< "Clear ice stream before restart");
|
|
mState = None;
|
|
mCheckList.clear();
|
|
mRemoteCandidate.clear();
|
|
mRemoteRelayedCandidate.clear();
|
|
mTurnAllocated = 0;
|
|
|
|
// Here deleting transactions common for remote or local peer changing networks
|
|
|
|
// Existing connectivity checks must be removed - there is no sense in result from them
|
|
mActiveChecks.erase(DeleteConnChecks());
|
|
|
|
// Existing channel binding requests are useless now - new ones should be sent - with new IP:port (when remote peer changes) or after creation new socket(s) (when local peer changes)
|
|
mActiveChecks.erase(DeleteChannelBindingRequests());
|
|
|
|
// And there is no sense in old TURN permissions
|
|
mActiveChecks.erase(DeleteCreatePermissions());
|
|
|
|
if (localNetworkChanged)
|
|
{
|
|
// No sense to refresh current TURN allocation - old socket is lost, there is no way to send from the same ip:port pair exactly
|
|
mActiveChecks.erase(DeleteClientRefreshes());
|
|
|
|
// Maybe new candidates were gathering when network changed - so remove old ClientBinding requests
|
|
mActiveChecks.erase(DeleteClientBindings());
|
|
|
|
// The same about TURN allocations - delete allocation requests.
|
|
mActiveChecks.erase(DeleteClientAllocations());
|
|
}
|
|
}
|
|
|
|
void Stream::restart()
|
|
{
|
|
mErrorCode = 0;
|
|
if (mState > CreatingSDP)
|
|
mState = ConnCheck;
|
|
else
|
|
mState = None;
|
|
|
|
mCheckList.clear();
|
|
}
|
|
|
|
NetworkAddress Stream::remoteAddress(int componentID)
|
|
{
|
|
NetworkAddress result;
|
|
PCandidatePair highestPair = mCheckList.findHighestNominatedPair(componentID);
|
|
if (highestPair)
|
|
{
|
|
result = highestPair->second().mExternalAddr;
|
|
result.setRelayed(highestPair->first().mType == Candidate::ServerRelayed);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NetworkAddress Stream::localAddress(int component)
|
|
{
|
|
PCandidatePair highestPair = mCheckList.findHighestNominatedPair(component);
|
|
if (highestPair)
|
|
return highestPair->first().mExternalAddr;
|
|
else
|
|
return NetworkAddress();
|
|
}
|
|
|
|
class FreeAllocationAction: public Action
|
|
{
|
|
protected:
|
|
DeleteAllocationCallback* mCallback;
|
|
public:
|
|
FreeAllocationAction(Stream& stream, StackConfig& config, DeleteAllocationCallback* cb)
|
|
:Action(stream, config), mCallback(cb)
|
|
{
|
|
|
|
}
|
|
~FreeAllocationAction()
|
|
{
|
|
delete mCallback;
|
|
}
|
|
|
|
void finished(Transaction& transaction)
|
|
{
|
|
AuthTransaction& auth = dynamic_cast<AuthTransaction&>(transaction);
|
|
int code = auth.state() == Transaction::Failed ? (auth.errorCode() ? auth.errorCode() : -1) : 0;
|
|
|
|
if (mCallback)
|
|
mCallback->onAllocationDeleted(mStream.mId, transaction.component(), code);
|
|
}
|
|
};
|
|
|
|
void Stream::freeAllocation(int component, DeleteAllocationCallback* cb)
|
|
{
|
|
// Clear bindings
|
|
removeBindingResult(component);
|
|
|
|
if (component == -1)
|
|
{
|
|
ComponentMap::iterator compIter;
|
|
for (compIter = mComponentMap.begin(); compIter != mComponentMap.end(); ++compIter)
|
|
freeAllocation(compIter->first, cb);
|
|
}
|
|
else
|
|
{
|
|
ClientRefresh* allocation = new ClientRefresh(0, this);
|
|
allocation->setStackId( mStackId );
|
|
allocation->setDestination( (mConfig.mServerAddr4.isZero() || mConfig.mServerAddr4.isEmpty()) ? mConfig.mServerAddr6 : mConfig.mServerAddr4);
|
|
allocation->setPassword( mConfig.mTurnPassword );
|
|
allocation->setUsername( mConfig.mTurnUsername );
|
|
allocation->setComponent( component );
|
|
allocation->setKeepalive( false );
|
|
#ifdef ICE_CACHE_REALM_NONCE
|
|
allocation->setRealm( mCachedRealm );
|
|
allocation->setNonce( mCachedNonce );
|
|
#endif
|
|
// Associate it with action
|
|
allocation->setAction(PAction(new FreeAllocationAction(*this, mConfig, cb)));
|
|
|
|
mActiveChecks.addRegular(allocation);
|
|
}
|
|
}
|
|
|
|
class ChannelBindResultAction: public Action
|
|
{
|
|
protected:
|
|
ChannelBoundCallback* mCallback;
|
|
|
|
public:
|
|
ChannelBindResultAction(Stream& stream, StackConfig& config, ChannelBoundCallback* cb)
|
|
:Action(stream, config), mCallback(cb)
|
|
{}
|
|
|
|
~ChannelBindResultAction()
|
|
{
|
|
delete mCallback;
|
|
}
|
|
|
|
void finished(Transaction& transaction)
|
|
{
|
|
ClientChannelBind& cb = dynamic_cast<ClientChannelBind&>(transaction);
|
|
Stream::BoundChannel channel;
|
|
channel.mComponentId = cb.component();
|
|
channel.mPrefix = cb.channelPrefix();
|
|
channel.mPeerAddress = cb.peerAddress();
|
|
channel.mResultCode = 0;
|
|
|
|
if (cb.state() == Transaction::Failed)
|
|
channel.mResultCode = cb.errorCode() ? cb.errorCode() : -1;
|
|
|
|
ICELogDebug(<< "Saving TURN channel binding result " << channel.mPrefix << " to " << channel.mPeerAddress.toStdString());
|
|
mStream.mBoundChannelList.push_back(channel);
|
|
if (mCallback)
|
|
mCallback->onChannelBound(mStream.mId, cb.component(), channel.mResultCode);
|
|
}
|
|
};
|
|
|
|
TurnPrefix Stream::bindChannel(const NetworkAddress& peerAddress, int component, ChannelBoundCallback* cb)
|
|
{
|
|
ICELogInfo(<< "Bind TURN channel for " << peerAddress.toStdString() << " for component " << component);
|
|
|
|
// Check if the request binding is done already
|
|
BoundChannelList::iterator channelIter = mBoundChannelList.begin();
|
|
for (;channelIter != mBoundChannelList.end(); ++channelIter)
|
|
{
|
|
if (channelIter->mComponentId == component && channelIter->mPeerAddress == peerAddress)
|
|
{
|
|
if (!channelIter->mResultCode)
|
|
{
|
|
ICELogInfo(<< "Already bound to prefix " << channelIter->mPrefix);
|
|
return channelIter->mPrefix;
|
|
}
|
|
}
|
|
}
|
|
|
|
ChannelBindResultAction* action = new ChannelBindResultAction(*this, mConfig, cb);
|
|
|
|
// Create transaction
|
|
ClientChannelBind* ccb = new ClientChannelBind( peerAddress );
|
|
ccb->setStackId( mStackId );
|
|
|
|
ccb->setUsername( mConfig.mTurnUsername );
|
|
ccb->setPassword( mConfig.mTurnPassword );
|
|
ccb->setDestination( mConfig.mUseIPv6 && !mConfig.mUseIPv4 ? mConfig.mServerAddr6 : mConfig.mServerAddr4 );
|
|
ccb->setComponent( component );
|
|
#ifdef ICE_CACHE_REALM_NONCE
|
|
ccb->setRealm( mCachedRealm );
|
|
ccb->setNonce( mCachedNonce );
|
|
#endif
|
|
ccb->setAction(PAction(action));
|
|
mActiveChecks.addRegular(ccb);
|
|
|
|
return ccb->channelPrefix();
|
|
}
|
|
|
|
bool Stream::isChannelBindingFailed(int component, TurnPrefix prefix)
|
|
{
|
|
BoundChannelList::iterator channelIter = mBoundChannelList.begin();
|
|
for (; channelIter != mBoundChannelList.end(); ++channelIter)
|
|
if (channelIter->mComponentId == component && channelIter->mPrefix == prefix)
|
|
return channelIter->mResultCode != 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
void Stream::removeBindingResult(int component)
|
|
{
|
|
// Clear related channel bindings
|
|
Stream::BoundChannelList::iterator channelIter = mBoundChannelList.begin();
|
|
while (channelIter != mBoundChannelList.end())
|
|
{
|
|
if (channelIter->mComponentId == component)
|
|
{
|
|
ICELogDebug(<< "Clearing TURN channel binding " << channelIter->mPrefix << " for " << channelIter->mPeerAddress.toStdString());
|
|
channelIter = mBoundChannelList.erase(channelIter);
|
|
}
|
|
else
|
|
channelIter++;
|
|
}
|
|
}
|
|
|
|
|
|
NetworkAddress Stream::reflexiveAddress(int componentID)
|
|
{
|
|
for (size_t i=0; i<mLocalCandidate.size(); i++)
|
|
{
|
|
if (mLocalCandidate[i].mType == Candidate::ServerReflexive && mLocalCandidate[i].mComponentId == componentID)
|
|
return mLocalCandidate[i].mExternalAddr;
|
|
}
|
|
|
|
return NetworkAddress();
|
|
}
|
|
|
|
NetworkAddress Stream::relayedAddress(int componentID)
|
|
{
|
|
for (size_t i=0; i<mLocalCandidate.size(); i++)
|
|
{
|
|
if (mLocalCandidate[i].mType == Candidate::ServerRelayed && mLocalCandidate[i].mComponentId == componentID)
|
|
return mLocalCandidate[i].mExternalAddr;
|
|
}
|
|
|
|
return NetworkAddress();
|
|
}
|
|
|
|
NetworkAddress Stream::remoteRelayedAddress(int component)
|
|
{
|
|
for (size_t i=0; i<mRemoteCandidate.size(); i++)
|
|
{
|
|
if (mRemoteCandidate[i].mType == Candidate::ServerRelayed && mRemoteCandidate[i].mComponentId == component)
|
|
return mRemoteCandidate[i].mExternalAddr;
|
|
}
|
|
|
|
for (size_t i=0; i<mRemoteRelayedCandidate.size(); i++)
|
|
{
|
|
if (mRemoteRelayedCandidate[i].mType == Candidate::ServerRelayed && mRemoteRelayedCandidate[i].mComponentId == component)
|
|
return mRemoteRelayedCandidate[i].mExternalAddr;
|
|
}
|
|
|
|
return NetworkAddress();
|
|
}
|
|
|
|
NetworkAddress Stream::remoteReflexiveAddress(int component)
|
|
{
|
|
for (size_t i=0; i<mRemoteCandidate.size(); i++)
|
|
{
|
|
if (mRemoteCandidate[i].mType == Candidate::ServerReflexive && mRemoteCandidate[i].mComponentId == component)
|
|
return mRemoteCandidate[i].mExternalAddr;
|
|
}
|
|
|
|
return NetworkAddress();
|
|
}
|
|
|
|
class ICEBindAction: public Action
|
|
{
|
|
public:
|
|
virtual void finished(Transaction& st)
|
|
{
|
|
ICELogInfo(<< "Stack ID " << st.stackId() << ". ChannelBind transaction is finished.");
|
|
}
|
|
};
|
|
|
|
|
|
void Stream::installPermissions(int component, const NetworkAddress& addr, InstallPermissionsCallback* cb)
|
|
{
|
|
// Here should be check if there are all components allocated and if remote candidates are known.
|
|
// It is useless for now - installPermissions() is called only when starting connectivity checks - components & remote candidates are here already.
|
|
ICELogInfo( << "Stack ID " << mStackId << ". Attempt to install TURN client permissions.");
|
|
|
|
// Iterate all components
|
|
int requestCounter = 0;
|
|
for (ComponentMap::iterator cmip = mComponentMap.begin(); cmip != mComponentMap.end(); ++cmip)
|
|
{
|
|
// Check if component is found
|
|
if (component != cmip->first && component != -1)
|
|
continue;
|
|
|
|
ClientCreatePermission* ccp = new ClientCreatePermission();
|
|
|
|
ccp->setComment( "CreatePermissions request" );
|
|
ccp->setStackId( mStackId );
|
|
ccp->setDestination( (mConfig.mServerAddr4.isEmpty() || mConfig.mServerAddr4.isZero()) ? mConfig.mServerAddr6 : mConfig.mServerAddr4 );
|
|
ccp->setUsername( mConfig.mTurnUsername );
|
|
ccp->setPassword( mConfig.mTurnPassword );
|
|
|
|
#ifdef ICE_CACHE_REALM_NONCE
|
|
ccp->setRealm( mCachedRealm );
|
|
ccp->setNonce( mCachedNonce );
|
|
#endif
|
|
ccp->setAction( PAction(new ICEInstallPermissionsAction(*this, mConfig, cb)) );
|
|
|
|
// Get remote candidates for given component
|
|
if (addr.isEmpty())
|
|
{
|
|
for (auto candIter = mRemoteCandidate.begin(); candIter != mRemoteCandidate.end(); candIter++)
|
|
if (candIter->mComponentId == cmip->first && candIter->mExternalAddr.isPublic())
|
|
ccp->addIpAddress(candIter->mExternalAddr);
|
|
for (auto candIter = mRemoteRelayedCandidate.begin(); candIter != mRemoteRelayedCandidate.end(); candIter++)
|
|
if (candIter->mComponentId == cmip->first)
|
|
ccp->addIpAddress(candIter->mExternalAddr);
|
|
}
|
|
else
|
|
ccp->addIpAddress(addr);
|
|
ccp->setComponent(cmip->first);
|
|
|
|
// Enqueue transaction
|
|
mActiveChecks.addPrioritized(ccp);
|
|
requestCounter++;
|
|
}
|
|
ICELogDebug(<< "Created " << requestCounter << " CreatePermission requests");
|
|
}
|
|
|
|
void Stream::dump(std::ostream& output)
|
|
{
|
|
output << Logger::TabPrefix << "State: " << RunningStateToString(mState) << std::endl;
|
|
output << Logger::TabPrefix << "Local candidates: " << std::endl;
|
|
for (unsigned i=0; i<mLocalCandidate.size(); i++)
|
|
mLocalCandidate[i].dump(output);
|
|
output << Logger::TabPrefix << "Remote candidates: " << std::endl;
|
|
for (unsigned i=0; i<mRemoteCandidate.size(); i++)
|
|
mRemoteCandidate[i].dump(output);
|
|
for (unsigned i=0; i<mRemoteRelayedCandidate.size(); i++)
|
|
mRemoteRelayedCandidate[i].dump(output);
|
|
output << Logger::TabPrefix << "Check list:" << std::endl;
|
|
mCheckList.dump(output);
|
|
}
|
|
|
|
bool Stream::findConcludePair(Candidate& local, Candidate& remote)
|
|
{
|
|
PCandidatePair validPair = mCheckList.findBestValidPair(local.mComponentId);
|
|
if (!validPair)
|
|
return false;
|
|
local = validPair->first();
|
|
remote = validPair->second();
|
|
return true;
|
|
}
|
|
|
|
int Stream::findComponentIdByPort(unsigned short port, int family)
|
|
{
|
|
ComponentMap::iterator it = mComponentMap.begin();
|
|
for (;it != mComponentMap.end();it++)
|
|
if (family == AF_INET)
|
|
{
|
|
if (it->second.mPort4 == port)
|
|
return it->first;
|
|
}
|
|
else
|
|
{
|
|
if (it->second.mPort6 == port)
|
|
return it->first;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void Stream::clearChecks()
|
|
{
|
|
switch (mState)
|
|
{
|
|
case Success:
|
|
mCheckList.clear();
|
|
mActiveChecks.erase(EraseBindingAndRelaying());
|
|
break;
|
|
|
|
case Failed:
|
|
mCheckList.clear();
|
|
mActiveChecks.erase(KeepDeallocationRequest());
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Stream::dumpLocalCandidateList()
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Local candidate list:" << std::endl;
|
|
for (unsigned i=0; i<mLocalCandidate.size(); i++)
|
|
{
|
|
Candidate& c = mLocalCandidate[i];
|
|
oss << " ";c.dump(oss);
|
|
}
|
|
ICELogDebug( << oss.str() );
|
|
}
|
|
|
|
void Stream::processStateChain()
|
|
{
|
|
if (mState == EliminateRedudand)
|
|
Handle_ER();
|
|
if (mState == ComputingFoundations)
|
|
Handle_CF();
|
|
if (mState == StartingKeepAlives)
|
|
Handle_SKA();
|
|
if (mState == PrioritizingCandidates)
|
|
Handle_PC();
|
|
if (mState == ChoosingDefault)
|
|
Handle_CD();
|
|
}
|
|
|
|
void Stream::unfreeze(const char* foundation)
|
|
{
|
|
for (unsigned i=0; i<mCheckList.count(); i++)
|
|
{
|
|
CandidatePair& c = *mCheckList[i];
|
|
if (c.foundation() == foundation && c.state() == CandidatePair::Frozen /*&& c.role() == r*/)
|
|
c.setState(CandidatePair::Waiting);
|
|
}
|
|
}
|
|
|
|
class GatheringCondition: public TransactionCondition
|
|
{
|
|
protected:
|
|
int mComponent;
|
|
int mFailoverId;
|
|
Transaction* mValidTransaction;
|
|
public:
|
|
GatheringCondition(int component, int failoverId, Transaction* valid)
|
|
:mComponent(component), mFailoverId(failoverId), mValidTransaction(valid)
|
|
{}
|
|
|
|
bool check(Transaction* t) const
|
|
{
|
|
if (!t)
|
|
return false;
|
|
if (t->removed())
|
|
return false;
|
|
if (!t->action())
|
|
return false;
|
|
if (t == mValidTransaction)
|
|
return false;
|
|
if (t->component() != mComponent)
|
|
return false;
|
|
if (t->failoverId() != mFailoverId)
|
|
return false;
|
|
|
|
// Special case for Allocate
|
|
RelayingAction* relayingAction = dynamic_cast<RelayingAction*>(t->action().get());
|
|
bool binding = dynamic_cast<BindingAction*>(t->action().get()) != NULL;
|
|
|
|
if (relayingAction)
|
|
relayingAction->autoreleaseAllocation();
|
|
|
|
return binding;
|
|
}
|
|
};
|
|
|
|
void Stream::removeGatherRequests(int component, int failoverId, Transaction* validOne)
|
|
{
|
|
mActiveChecks.erase(GatheringCondition(component, failoverId, validOne));
|
|
}
|
|
|
|
void Stream::nominatePair(PCandidatePair &p)
|
|
{
|
|
// Check if pair is already nominated
|
|
if (p->nomination() != p->Nomination_None)
|
|
return;
|
|
|
|
if (!p->transaction())
|
|
{
|
|
// It can be newly created candidate pair
|
|
PCandidatePair equalPair = mCheckList.findEqualPair(*p, CheckList::CT_Strict);
|
|
if (equalPair)
|
|
createCheckRequest(equalPair);
|
|
}
|
|
|
|
Transaction* t = reinterpret_cast<Transaction*>(p->transaction());
|
|
if (!t)
|
|
{
|
|
ICELogCritical(<< "Logical error in nomination procedure.");
|
|
return;
|
|
}
|
|
|
|
CheckResult* cr = dynamic_cast<CheckResult*>(t);
|
|
|
|
p->setNomination(CandidatePair::Nomination_Started);
|
|
|
|
// Restart binding transaction
|
|
t->restart();
|
|
|
|
|
|
// "upgrade" binding transaction to include USE-CANDIDATE attribute
|
|
cr->useCandidate();
|
|
|
|
// Push log records
|
|
t->setComment("Nominated request for " + p->toStdString());
|
|
ICELogInfo (<< "Stack ID " << t->stackId() << ". Generated nominated candidate request for pair " << p->toStdString());
|
|
|
|
// Prioritize restarted transaction
|
|
mActiveChecks.prioritize(t);
|
|
|
|
// Mark transaction's action as nomination
|
|
CheckPairAction* cp = dynamic_cast<CheckPairAction*>(t->action().get());
|
|
if (cp)
|
|
cp->setNomination(true);
|
|
}
|
|
|
|
class OldAllocationCondition: public TransactionCondition
|
|
{
|
|
protected:
|
|
int mComponent;
|
|
public:
|
|
OldAllocationCondition(int component)
|
|
:mComponent(component)
|
|
{}
|
|
|
|
bool check(Transaction* t) const
|
|
{
|
|
if (!t->removed())
|
|
return false;
|
|
ClientRefresh* cf = dynamic_cast<ClientRefresh*>(t);
|
|
if (!cf)
|
|
return false;
|
|
return true;
|
|
}
|
|
};
|
|
bool Stream::ressurectAllocation(int component)
|
|
{
|
|
std::vector<Transaction*> allocations;
|
|
mActiveChecks.copyMatching(OldAllocationCondition(component), allocations);
|
|
if (allocations.empty())
|
|
return false;
|
|
// Find most recent allocation
|
|
unsigned current = ICETimeHelper::timestamp();
|
|
unsigned delta = 0xFFFFFFFF;
|
|
Transaction* chosen = NULL;
|
|
for (size_t index=0; index<allocations.size(); index++)
|
|
{
|
|
Transaction* t = allocations[index];
|
|
if (current - t->timestamp() < delta)
|
|
{
|
|
chosen = t;
|
|
delta = current - t->timestamp();
|
|
}
|
|
}
|
|
|
|
if (!chosen)
|
|
return false;
|
|
|
|
ClientRefresh* cf = dynamic_cast<ClientRefresh*>(chosen);
|
|
if (!cf)
|
|
return false; // Just extra check
|
|
cf->setRemoved(false);
|
|
|
|
// Update local candidates
|
|
for (unsigned i=0; i<mLocalCandidate.size(); i++)
|
|
{
|
|
Candidate& c = mLocalCandidate[i];
|
|
|
|
// Avoid updating of successful candidates to failed state
|
|
if (c.mComponentId == component)
|
|
{
|
|
c.mReady = true;
|
|
c.mFailed = false;
|
|
if (c.mType == Candidate::ServerReflexive)
|
|
c.mExternalAddr = cf->reflexiveAddress();
|
|
else
|
|
if (c.mType == Candidate::ServerRelayed)
|
|
c.mExternalAddr = cf->relayedAddress();
|
|
}
|
|
}
|
|
return true;
|
|
}
|