Files
resiprocate/reTurn/client/TurnAsyncSocket.cxx
Andrey Semashev e5eb4da757 Update for Asio 1.34 and later compatibility.
This updates usage of Asio for compatibility with version 1.34 and
newer, where a number of previously deprecated APIs were removed.
2025-09-05 21:05:35 +03:00

1507 lines
55 KiB
C++

#include "TurnAsyncSocket.hxx"
#include "../AsyncSocketBase.hxx"
#include "ErrorCode.hxx"
#include <rutil/WinLeakCheck.hxx>
#include <rutil/Logger.hxx>
#include "../ReTurnSubsystem.hxx"
#include <functional>
#define RESIPROCATE_SUBSYSTEM ReTurnSubsystem::RETURN
using namespace std;
using namespace resip;
//#define TURN_CHANNEL_BINDING_REFRESH_SECONDS 20 // TESTING only
#define TURN_CHANNEL_BINDING_REFRESH_SECONDS 240 // 4 minuntes - this is one minute before the permission will expire, Note: ChannelBinding refreshes also refresh permissions
#define SOFTWARE_STRING "reTURN Async Client 0.3 - RFC5389/turn-12 " // Note padding size to a multiple of 4, to help compatibility with older clients
#ifdef BOOST_ASIO_HAS_STD_CHRONO
using namespace std::chrono;
#else
#include <chrono>
using namespace std::chrono;
#endif
namespace reTurn {
// Initialize static members
unsigned int TurnAsyncSocket::UnspecifiedLifetime = 0xFFFFFFFF;
unsigned int TurnAsyncSocket::UnspecifiedBandwidth = 0xFFFFFFFF;
unsigned short TurnAsyncSocket::UnspecifiedToken = 0;
asio::ip::address TurnAsyncSocket::UnspecifiedIpAddress;
TurnAsyncSocket::TurnAsyncSocket(asio::io_context& ioService,
AsyncSocketBase& asyncSocketBase,
TurnAsyncSocketHandler* turnAsyncSocketHandler,
const asio::ip::address& address,
unsigned short port) :
mIOService(ioService),
mTurnAsyncSocketHandler(turnAsyncSocketHandler),
mLocalBinding(StunTuple::None /* Set properly by sub class */, address, port),
mSoftware(SOFTWARE_STRING),
mHaveAllocation(false),
mActiveDestination(0),
mAsyncSocketBase(asyncSocketBase),
mCloseAfterDestroyAllocationFinishes(false),
mAllocationTimer(ioService)
{
}
TurnAsyncSocket::~TurnAsyncSocket()
{
clearActiveRequestMap();
cancelAllocationTimer();
cancelChannelBindingTimers();
DebugLog(<< "TurnAsyncSocket::~TurnAsyncSocket destroyed!");
}
void
TurnAsyncSocket::disableTurnAsyncHandler()
{
RecursiveLock lock(mHandlerMutex);
mTurnAsyncSocketHandler = 0;
}
void
TurnAsyncSocket::requestSharedSecret()
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [this] { doRequestSharedSecret(); }));
}
void
TurnAsyncSocket::doRequestSharedSecret()
{
// Should we check here if TLS and deny?
// Ensure Connected
if(!mAsyncSocketBase.isConnected())
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSharedSecretFailure(getSocketDescriptor(), asio::error_code(reTurn::NotConnected, asio::error::misc_category));
}
else
{
// Form Shared Secret request
StunMessage* request = createNewStunMessage(StunMessage::StunClassRequest, StunMessage::SharedSecretMethod);
// Send the Request and start transaction timers
sendStunMessage(request);
}
}
void
TurnAsyncSocket::setUsernameAndPassword(const char* username, const char* password, bool shortTermAuth)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doSetUsernameAndPassword(new Data(username), new Data(password), shortTermAuth); }));
}
void
TurnAsyncSocket::doSetUsernameAndPassword(Data* username, Data* password, bool shortTermAuth)
{
mUsername = *username;
mPassword = *password;
if(shortTermAuth)
{
// If we are using short term auth, then use short term password as HMAC key
mHmacKey = *password;
}
delete username;
delete password;
}
void
TurnAsyncSocket::setLocalPassword(const char* password)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doSetLocalPassword(new Data(password)); }));
}
void
TurnAsyncSocket::doSetLocalPassword(Data* password)
{
mLocalHmacKey = *password;
delete password;
}
void
TurnAsyncSocket::bindRequest()
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [this] { doBindRequest(); }));
}
void
TurnAsyncSocket::doBindRequest()
{
// Ensure Connected
if(!mAsyncSocketBase.isConnected())
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onBindFailure(getSocketDescriptor(), asio::error_code(reTurn::NotConnected, asio::error::misc_category), StunTuple());
}
else
{
// Form Stun Bind request
StunMessage* request = createNewStunMessage(StunMessage::StunClassRequest, StunMessage::BindMethod);
sendStunMessage(request);
}
}
void
TurnAsyncSocket::connectivityCheck(const StunTuple& targetAddr, uint32_t peerRflxPriority, bool setIceControlling, bool setIceControlled, unsigned int numRetransmits, unsigned int retrans_iterval_ms)
{
resip_assert(setIceControlling || setIceControlled);
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doConnectivityCheck(new StunTuple(targetAddr.getTransportType(), targetAddr.getAddress(), targetAddr.getPort()), peerRflxPriority, setIceControlling, setIceControlled, numRetransmits, retrans_iterval_ms); }));
}
void
TurnAsyncSocket::doConnectivityCheck(StunTuple* targetAddr, uint32_t peerRflxPriority, bool setIceControlling, bool setIceControlled, unsigned int numRetransmits, unsigned int retrans_iterval_ms)
{
// Form Stun Bind request
StunMessage* request = createNewStunMessage(StunMessage::StunClassRequest, StunMessage::BindMethod);
request->setIcePriority(peerRflxPriority);
if (setIceControlling)
{
request->setIceControlling();
request->setIceUseCandidate();
}
else if (setIceControlled)
{
request->setIceControlled();
}
request->mHasFingerprint = true;
sendStunMessage(request, false, numRetransmits, retrans_iterval_ms, targetAddr);
delete targetAddr;
}
void
TurnAsyncSocket::createAllocation(unsigned int lifetimeSecs,
unsigned int bandwidth, // Kilobits per second
unsigned char requestedProps,
uint64_t reservationToken,
StunTuple::TransportType requestedTransportType)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doCreateAllocation(lifetimeSecs, bandwidth, requestedProps, reservationToken, requestedTransportType); }));
}
void
TurnAsyncSocket::doCreateAllocation(unsigned int lifetimeSecs,
unsigned int bandwidth,
unsigned char requestedProps,
uint64_t reservationToken,
StunTuple::TransportType requestedTransportType)
{
// Store Allocation Properties
mRequestedTransportType = requestedTransportType;
// Relay Transport Type is requested type or socket type
if(mRequestedTransportType != StunTuple::None)
{
mRelayTransportType = mRequestedTransportType;
}
else
{
mRelayTransportType = mLocalBinding.getTransportType();
}
// Ensure Connected
if(!mAsyncSocketBase.isConnected())
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(reTurn::NotConnected, asio::error::misc_category));
return;
}
if(mHaveAllocation)
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(reTurn::AlreadyAllocated, asio::error::misc_category));
return;
}
// Form Turn Allocate request
StunMessage* request = createNewStunMessage(StunMessage::StunClassRequest, StunMessage::TurnAllocateMethod);
if(lifetimeSecs != UnspecifiedLifetime)
{
request->mHasTurnLifetime = true;
request->mTurnLifetime = lifetimeSecs;
}
if(bandwidth != UnspecifiedBandwidth)
{
request->mHasTurnBandwidth = true;
request->mTurnBandwidth = bandwidth;
}
if(requestedTransportType == StunTuple::None)
{
requestedTransportType = mLocalBinding.getTransportType();
}
request->mHasTurnRequestedTransport = true;
if(requestedTransportType == StunTuple::UDP)
{
request->mTurnRequestedTransport = StunMessage::RequestedTransportUdp;
}
else if(requestedTransportType == StunTuple::TCP &&
mLocalBinding.getTransportType() != StunTuple::UDP) // Ensure client is not requesting TCP over a UDP transport
{
request->mTurnRequestedTransport = StunMessage::RequestedTransportTcp;
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(reTurn::InvalidRequestedTransport, asio::error::misc_category));
delete request;
return;
}
if(requestedProps != StunMessage::PropsNone)
{
request->mHasTurnEvenPort = true;
request->mTurnEvenPort.propType = requestedProps;
}
else if(reservationToken != 0)
{
request->mHasTurnReservationToken = true;
request->mTurnReservationToken = reservationToken;
}
sendStunMessage(request);
}
void
TurnAsyncSocket::refreshAllocation(unsigned int lifetimeSecs)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doRefreshAllocation(lifetimeSecs); }));
}
void
TurnAsyncSocket::doRefreshAllocation(unsigned int lifetimeSecs)
{
if(!mHaveAllocation)
{
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onRefreshFailure(getSocketDescriptor(), asio::error_code(NoAllocation, asio::error::misc_category));
}
if(mCloseAfterDestroyAllocationFinishes)
{
actualClose();
}
return;
}
// Form Turn Refresh request
StunMessage* request = createNewStunMessage(StunMessage::StunClassRequest, StunMessage::TurnRefreshMethod);
if(lifetimeSecs != UnspecifiedLifetime)
{
request->mHasTurnLifetime = true;
request->mTurnLifetime = lifetimeSecs;
}
//if(mRequestedBandwidth != UnspecifiedBandwidth)
//{
// request.mHasTurnBandwidth = true;
// request.mTurnBandwidth = mRequestedBandwidth;
//}
sendStunMessage(request);
}
void
TurnAsyncSocket::destroyAllocation()
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [this] { doDestroyAllocation(); }));
}
void
TurnAsyncSocket::doDestroyAllocation()
{
doRefreshAllocation(0);
}
void
TurnAsyncSocket::setActiveDestination(const asio::ip::address& address, unsigned short port)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doSetActiveDestination(address, port); }));
}
void
TurnAsyncSocket::doSetActiveDestination(const asio::ip::address& address, unsigned short port)
{
// Setup Remote Peer
StunTuple remoteTuple(mRelayTransportType, address, port);
RemotePeer* remotePeer = mChannelManager.findRemotePeerByPeerAddress(remoteTuple);
if(remotePeer)
{
mActiveDestination = remotePeer;
}
else
{
// No channel binding yet (ie. no data sent or received from remote peer) - so create one
mActiveDestination = mChannelManager.createChannelBinding(remoteTuple);
resip_assert(mActiveDestination);
doChannelBinding(*mActiveDestination);
}
DebugLog(<< "TurnAsyncSocket::doSetActiveDestination: Active Destination set to: " << remoteTuple);
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSetActiveDestinationSuccess(getSocketDescriptor());
}
void TurnAsyncSocket::doChannelBinding(RemotePeer& remotePeer)
{
// Form Channel Bind request
StunMessage* request = createNewStunMessage(StunMessage::StunClassRequest, StunMessage::TurnChannelBindMethod);
// Set headers
request->mHasTurnChannelNumber = true;
request->mTurnChannelNumber = remotePeer.getChannel();
request->mCntTurnXorPeerAddress = 1;
StunMessage::setStunAtrAddressFromTuple(request->mTurnXorPeerAddress[0], remotePeer.getPeerTuple());
// Send the Request and start transaction timers
sendStunMessage(request);
// If not using UDP - then mark channel as confirmed - otherwise wait for ChannelBind response
if(mLocalBinding.getTransportType() != StunTuple::UDP)
{
remotePeer.setChannelConfirmed();
}
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onChannelBindRequestSent(getSocketDescriptor(), remotePeer.getChannel());
}
void
TurnAsyncSocket::clearActiveDestination()
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [this] { doClearActiveDestination(); }));
}
void
TurnAsyncSocket::doClearActiveDestination()
{
// ensure there is an allocation
if(!mHaveAllocation)
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onClearActiveDestinationFailure(getSocketDescriptor(), asio::error_code(reTurn::NoAllocation, asio::error::misc_category));
return;
}
mActiveDestination = 0;
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onClearActiveDestinationSuccess(getSocketDescriptor());
}
StunMessage*
TurnAsyncSocket::createNewStunMessage(uint16_t stunclass, uint16_t method, bool addAuthInfo)
{
StunMessage* msg = new StunMessage();
msg->createHeader(stunclass, method);
// Add Software Attribute
if(!mSoftware.empty())
{
msg->setSoftware(mSoftware.c_str());
}
if(addAuthInfo && !mUsername.empty() && !mHmacKey.empty())
{
msg->mHasMessageIntegrity = true;
msg->setUsername(mUsername.c_str());
msg->mHmacKey = mHmacKey;
if(!mRealm.empty())
{
msg->setRealm(mRealm.c_str());
}
if(!mNonce.empty())
{
msg->setNonce(mNonce.c_str());
}
}
return msg;
}
void
TurnAsyncSocket::sendStunMessage(StunMessage* message, bool reTransmission, unsigned int numRetransmits, unsigned int retrans_iterval_ms, const StunTuple* targetAddress)
{
constexpr size_t REQUEST_BUFFER_SIZE = 4096;
const auto buffer = AsyncSocketBase::allocateBuffer(REQUEST_BUFFER_SIZE);
unsigned int bufferSize;
bufferSize = message->stunEncodeMessage((char*)buffer->data(), REQUEST_BUFFER_SIZE);
buffer->truncate(bufferSize); // set size to real size
if(!reTransmission)
{
// If message is a request, then start appropriate transaction and retranmission timers
if(message->mClass == StunMessage::StunClassRequest)
{
const auto requestEntry = std::make_shared<RequestEntry>(mIOService, this, message, numRetransmits, retrans_iterval_ms, targetAddress);
mActiveRequestMap[message->mHeader.magicCookieAndTid] = requestEntry;
requestEntry->startTimer();
}
else
{
delete message;
}
}
if (targetAddress)
{
sendToUnframed(targetAddress->getAddress(), targetAddress->getPort(), buffer);
}
else
{
sendUnframed(buffer);
}
}
void
TurnAsyncSocket::handleReceivedData(const asio::ip::address& address, unsigned short port, const std::shared_ptr<DataBuffer>& data)
{
RecursiveLock lock(mHandlerMutex);
if(data->size() > 4)
{
// Stun Message has first two bits as 00
if((((*data)[0]) & 0xC0) == 0)
{
StunMessage* stunMsg = new StunMessage(mLocalBinding,
//StunTuple(mLocalBinding.getTransportType(), mAsyncSocketBase.getConnectedAddress(), mAsyncSocketBase.getConnectedPort()),
StunTuple(mLocalBinding.getTransportType(), address, port),
&(*data)[0], (unsigned int)data->size());
if(stunMsg->isValid())
{
handleStunMessage(*stunMsg);
delete stunMsg;
return;
}
delete stunMsg;
// Not a stun message so assume normal data
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onReceiveSuccess(getSocketDescriptor(),
address,
port,
data);
}
else if(mHaveAllocation) // If we have an allocation then this is a Turn Channel Data Message
{
// Get Channel number
unsigned short channelNumber;
memcpy(&channelNumber, &(*data)[0], 2);
channelNumber = ntohs(channelNumber);
if(mLocalBinding.getTransportType() == StunTuple::UDP)
{
// Check if the UDP datagram size is too short to contain the claimed length of the ChannelData message, then discard
unsigned short dataLen;
memcpy(&dataLen, &(*data)[2], 2);
dataLen = ntohs(dataLen);
if(data->size() < (unsigned int)dataLen+4)
{
WarningLog(<< "ChannelData message size=" << dataLen+4 << " too large for UDP packet size=" << data->size() <<". Dropping.");
return;
}
}
RemotePeer* remotePeer = mChannelManager.findRemotePeerByChannel(channelNumber);
if(remotePeer)
{
data->offset(4); // move buffer start past framing for callback
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onReceiveSuccess(getSocketDescriptor(),
remotePeer->getPeerTuple().getAddress(),
remotePeer->getPeerTuple().getPort(),
data);
}
else
{
WarningLog(<< "TurnAsyncSocket::handleReceivedData: receive channel data for non-existing channel - discarding!");
}
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onReceiveSuccess(getSocketDescriptor(),
address,
port,
data);
}
}
else // size <= 4
{
WarningLog(<< "TurnAsyncSocket::handleReceivedData: not enough data received (" << data->size() << " bytes) for stun or channel data message - discarding!");
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onReceiveFailure(getSocketDescriptor(), asio::error_code(reTurn::FrameError, asio::error::misc_category));
}
}
asio::error_code
TurnAsyncSocket::handleStunMessage(StunMessage& stunMessage)
{
asio::error_code errorCode;
if(stunMessage.isValid())
{
if(stunMessage.mClass == StunMessage::StunClassSuccessResponse ||
stunMessage.mClass == StunMessage::StunClassErrorResponse)
{
if(!stunMessage.checkMessageIntegrity(mHmacKey))
{
WarningLog(<< "TurnAsyncSocket::handleStunMessage: Stun message integrity is bad!");
return asio::error_code(reTurn::BadMessageIntegrity, asio::error::misc_category);
}
}
else
{
if(!stunMessage.checkMessageIntegrity(mLocalHmacKey))
{
WarningLog(<< "TurnAsyncSocket::handleStunMessage: Stun message integrity is bad!");
return asio::error_code(reTurn::BadMessageIntegrity, asio::error::misc_category);
}
}
// Request is authenticated, process it
switch(stunMessage.mClass)
{
case StunMessage::StunClassRequest:
switch (stunMessage.mMethod)
{
case StunMessage::BindMethod:
if(stunMessage.mUnknownRequiredAttributes.numAttributes > 0)
{
// There were unknown comprehension-required attributes in the request
StunMessage* response = new StunMessage();
response->mClass = StunMessage::StunClassErrorResponse;
response->mMethod = stunMessage.mMethod;
response->setErrorCode(420, "Unknown Attribute");
// Copy over TransactionId
response->mHeader.magicCookieAndTid = stunMessage.mHeader.magicCookieAndTid;
// Add Unknown Attributes
response->mHasUnknownAttributes = true;
response->mUnknownAttributes = stunMessage.mUnknownRequiredAttributes;
// Add Software Attribute
if(!mSoftware.empty())
{
response->setSoftware(mSoftware.c_str());
}
sendStunMessage(response);
}
else
{
errorCode = handleBindRequest(stunMessage);
}
break;
case StunMessage::SharedSecretMethod:
case StunMessage::TurnAllocateMethod:
case StunMessage::TurnRefreshMethod:
default:
// These requests are not handled by a client
StunMessage* response = new StunMessage();
response->mClass = StunMessage::StunClassErrorResponse;
response->mMethod = stunMessage.mMethod;
response->setErrorCode(400, "Invalid Request Method");
// Copy over TransactionId
response->mHeader.magicCookieAndTid = stunMessage.mHeader.magicCookieAndTid;
// Add Software Attribute
if(!mSoftware.empty())
{
response->setSoftware(mSoftware.c_str());
}
sendStunMessage(response);
break;
}
break;
case StunMessage::StunClassIndication:
switch (stunMessage.mMethod)
{
case StunMessage::TurnDataMethod:
if(stunMessage.mUnknownRequiredAttributes.numAttributes > 0)
{
// Unknown Comprehension-Required Attributes found
WarningLog(<< "Ignoring DataInd with unknown comprehension required attributes.");
errorCode = asio::error_code(reTurn::UnknownRequiredAttributes, asio::error::misc_category);
}
else
{
errorCode = handleDataInd(stunMessage);
}
break;
case StunMessage::BindMethod:
// A Bind indication is simply a keepalive with no response required
break;
case StunMessage::TurnSendMethod: // Don't need to handle these - only sent by client, never received
default:
// Unknown indication - just ignore
break;
}
break;
case StunMessage::StunClassSuccessResponse:
case StunMessage::StunClassErrorResponse:
{
if(stunMessage.mUnknownRequiredAttributes.numAttributes > 0)
{
// Unknown Comprehension-Required Attributes found
WarningLog(<< "Ignoring Response with unknown comprehension required attributes.");
return asio::error_code(reTurn::UnknownRequiredAttributes, asio::error::misc_category);
}
// First check if this response is for an active request
RequestMap::iterator it = mActiveRequestMap.find(stunMessage.mHeader.magicCookieAndTid);
if(it == mActiveRequestMap.end())
{
// Stray response - dropping
return asio::error_code(reTurn::StrayResponse, asio::error::misc_category);
}
{
const auto requestEntry = it->second;
mActiveRequestMap.erase(it);
requestEntry->stopTimer();
// If a realm and nonce attributes are present and the response is a 401 or 438 (Nonce Expired),
// then re-issue request with new auth attributes
if(stunMessage.mHasRealm &&
stunMessage.mHasNonce &&
stunMessage.mHasErrorCode &&
stunMessage.mErrorCode.errorClass == 4 &&
((stunMessage.mErrorCode.number == 1 && mHmacKey.empty()) || // Note if 401 error then ensure we haven't already tried once - if we've tried then mHmacKey will be populated
stunMessage.mErrorCode.number == 38))
{
mNonce = *stunMessage.mNonce;
mRealm = *stunMessage.mRealm;
stunMessage.calculateHmacKey(mHmacKey, mUsername, mRealm, mPassword);
// Create a new transaction - by starting with old request
StunMessage* newRequest = requestEntry->mRequestMessage;
requestEntry->mRequestMessage = 0; // clear out pointer in mActiveRequestMap so that it will not be deleted
newRequest->createHeader(newRequest->mClass, newRequest->mMethod); // updates TID
newRequest->mHasMessageIntegrity = true;
newRequest->setUsername(mUsername.c_str());
newRequest->mHmacKey = mHmacKey;
newRequest->setRealm(mRealm.c_str());
newRequest->setNonce(mNonce.c_str());
sendStunMessage(newRequest);
return errorCode;
}
switch (stunMessage.mMethod)
{
case StunMessage::BindMethod:
errorCode = handleBindResponse(*requestEntry->mRequestMessage, stunMessage);
break;
case StunMessage::SharedSecretMethod:
errorCode = handleSharedSecretResponse(*requestEntry->mRequestMessage, stunMessage);
break;
case StunMessage::TurnAllocateMethod:
errorCode = handleAllocateResponse(*requestEntry->mRequestMessage, stunMessage);
break;
case StunMessage::TurnRefreshMethod:
errorCode = handleRefreshResponse(*requestEntry->mRequestMessage, stunMessage);
break;
case StunMessage::TurnChannelBindMethod:
errorCode = handleChannelBindResponse(*requestEntry->mRequestMessage, stunMessage);
break;
default:
// Unknown method - just ignore
break;
}
}
}
break;
default:
// Illegal message class - ignore
break;
}
}
else
{
WarningLog(<< "TurnAsyncSocket::handleStunMessage: Read Invalid StunMsg.");
return asio::error_code(reTurn::ErrorParsingMessage, asio::error::misc_category);
}
return errorCode;
}
asio::error_code
TurnAsyncSocket::handleDataInd(StunMessage& stunMessage)
{
if(stunMessage.mCntTurnXorPeerAddress == 0 || !stunMessage.mHasTurnData)
{
// Missing RemoteAddress or TurnData attribute
WarningLog(<< "TurnAsyncSocket::handleDataInd: DataInd missing attributes.");
return asio::error_code(reTurn::MissingAttributes, asio::error::misc_category);
}
StunTuple remoteTuple;
remoteTuple.setTransportType(mRelayTransportType);
StunMessage::setTupleFromStunAtrAddress(remoteTuple, stunMessage.mTurnXorPeerAddress[0]);
// Should we record all the remoteTuples we have sent to before, and reject this message if
// not from one of the those endpoints?
const auto data = std::make_shared<DataBuffer>(stunMessage.mTurnData->data(), stunMessage.mTurnData->size());
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onReceiveSuccess(getSocketDescriptor(),
remoteTuple.getAddress(),
remoteTuple.getPort(),
data);
return asio::error_code();
}
asio::error_code
TurnAsyncSocket::handleChannelBindResponse(StunMessage &request, StunMessage &response)
{
if(response.mClass == StunMessage::StunClassSuccessResponse)
{
resip_assert(request.mHasTurnChannelNumber);
RemotePeer* remotePeer = mChannelManager.findRemotePeerByChannel(request.mTurnChannelNumber);
if(!remotePeer)
{
// Remote Peer not found - discard
WarningLog(<< "TurnAsyncSocket::handleChannelBindResponse: Received ChannelBindResponse for unknown channel (" << response.mTurnChannelNumber << ") - discarding");
asio::error_code ec(reTurn::InvalidChannelNumberReceived, asio::error::misc_category);
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onChannelBindFailure(getSocketDescriptor(), ec);
return ec;
}
DebugLog(<< "TurnAsyncSocket::handleChannelBindResponse: Channel " << remotePeer->getChannel() << " is now bound to " << remotePeer->getPeerTuple());
remotePeer->refresh();
remotePeer->setChannelConfirmed();
startChannelBindingTimer(remotePeer->getChannel());
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onChannelBindSuccess(getSocketDescriptor(), remotePeer->getChannel());
}
else
{
// Check error code
if(response.mHasErrorCode)
{
ErrLog(<< "TurnAsyncSocket::handleChannelBindResponse: Received ChannelBindResponse error: " << response.mErrorCode.errorClass * 100 + response.mErrorCode.number);
asio::error_code ec(response.mErrorCode.errorClass * 100 + response.mErrorCode.number, asio::error::misc_category);
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onChannelBindFailure(getSocketDescriptor(), ec);
return ec;
}
else
{
ErrLog(<< "TurnAsyncSocket::handleChannelBindResponse: Received ChannelBindResponse error but no error code attribute found.");
asio::error_code ec(MissingAttributes, asio::error::misc_category);
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onChannelBindFailure(getSocketDescriptor(), ec);
return ec;
}
}
return asio::error_code();
}
asio::error_code
TurnAsyncSocket::handleSharedSecretResponse(StunMessage &request, StunMessage &response)
{
if(response.mClass == StunMessage::StunClassSuccessResponse)
{
// Copy username and password to callers buffer - checking sizes first
if(!response.mHasUsername || !response.mHasPassword)
{
WarningLog(<< "TurnAsyncSocket::handleSharedSecretResponse: Stun response message for SharedSecretRequest is missing username and/or password!");
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSharedSecretFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category));
return asio::error_code(MissingAttributes, asio::error::misc_category);
}
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSharedSecretSuccess(getSocketDescriptor(), response.mUsername->c_str(), (unsigned int)response.mUsername->size(),
response.mPassword->c_str(), (unsigned int)response.mPassword->size());
}
else
{
// Check error code
if(response.mHasErrorCode)
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSharedSecretFailure(getSocketDescriptor(), asio::error_code(response.mErrorCode.errorClass * 100 + response.mErrorCode.number, asio::error::misc_category));
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSharedSecretFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category));
return asio::error_code(MissingAttributes, asio::error::misc_category);
}
}
return asio::error_code();
}
asio::error_code
TurnAsyncSocket::handleBindRequest(StunMessage& stunMessage)
{
// Note: handling of BindRequest is not fully backwards compatible with RFC3489 - it is inline with RFC5389
StunMessage* response = new StunMessage();
// form the outgoing message
response->mClass = StunMessage::StunClassSuccessResponse;
response->mMethod = StunMessage::BindMethod;
// Copy over TransactionId
response->mHeader.magicCookieAndTid = stunMessage.mHeader.magicCookieAndTid;
// Add XOrMappedAddress to response
response->mHasXorMappedAddress = true;
StunMessage::setStunAtrAddressFromTuple(response->mXorMappedAddress, stunMessage.mRemoteTuple);
// Add Software Attribute
if(!mSoftware.empty())
{
response->setSoftware(mSoftware.c_str());
}
// If the request contained MESSAGE-INTEGRITY, then the response needs to as well
if (stunMessage.mHasMessageIntegrity)
{
response->mHasMessageIntegrity = true;
response->mHmacKey = mLocalHmacKey;
}
if (stunMessage.mHasIceControlled || stunMessage.mHasIceControlling || stunMessage.mHasIcePriority)
{
response->mHasFingerprint = true;
}
// send bindResponse to local client
DebugLog(<< "Sending response to BIND to " << stunMessage.mRemoteTuple);
sendStunMessage(response, false, UDP_MAX_RETRANSMITS, DEFAULT_RETRANS_INTERVAL_MS, &(stunMessage.mRemoteTuple));
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onIncomingBindRequestProcessed(getSocketDescriptor(), stunMessage.mRemoteTuple);
return asio::error_code();
}
asio::error_code
TurnAsyncSocket::handleBindResponse(StunMessage &request, StunMessage &response)
{
if(response.mClass == StunMessage::StunClassSuccessResponse)
{
StunTuple reflexiveTuple;
reflexiveTuple.setTransportType(mLocalBinding.getTransportType());
if(response.mHasXorMappedAddress)
{
StunMessage::setTupleFromStunAtrAddress(reflexiveTuple, response.mXorMappedAddress);
}
else if(response.mHasMappedAddress) // Only look at MappedAddress if XorMappedAddress is not found - for backwards compatibility
{
StunMessage::setTupleFromStunAtrAddress(reflexiveTuple, response.mMappedAddress);
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onBindFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category), response.mRemoteTuple);
return asio::error_code(MissingAttributes, asio::error::misc_category);
}
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onBindSuccess(getSocketDescriptor(), reflexiveTuple, response.mRemoteTuple);
}
else
{
// Check if success or not
if(response.mHasErrorCode)
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onBindFailure(getSocketDescriptor(), asio::error_code(response.mErrorCode.errorClass * 100 + response.mErrorCode.number, asio::error::misc_category), response.mRemoteTuple);
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onBindFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category), response.mRemoteTuple);
return asio::error_code(MissingAttributes, asio::error::misc_category);
}
}
return asio::error_code();
}
asio::error_code
TurnAsyncSocket::handleAllocateResponse(StunMessage &request, StunMessage &response)
{
if(response.mClass == StunMessage::StunClassSuccessResponse)
{
StunTuple reflexiveTuple;
StunTuple relayTuple;
if(response.mHasXorMappedAddress)
{
reflexiveTuple.setTransportType(mLocalBinding.getTransportType());
StunMessage::setTupleFromStunAtrAddress(reflexiveTuple, response.mXorMappedAddress);
}
if(response.mHasTurnXorRelayedAddress)
{
relayTuple.setTransportType(mRelayTransportType);
StunMessage::setTupleFromStunAtrAddress(relayTuple, response.mTurnXorRelayedAddress);
}
if(response.mHasTurnLifetime)
{
mLifetime = response.mTurnLifetime;
}
else
{
mLifetime = 0;
}
// All was well - return 0 errorCode
if(mLifetime != 0)
{
mHaveAllocation = true;
startAllocationTimer();
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationSuccess(getSocketDescriptor(),
reflexiveTuple,
relayTuple,
mLifetime,
response.mHasTurnBandwidth ? response.mTurnBandwidth : 0,
response.mHasTurnReservationToken ? response.mTurnReservationToken : 0);
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category));
}
}
else
{
// Check if success or not
if(response.mHasErrorCode)
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(response.mErrorCode.errorClass * 100 + response.mErrorCode.number, asio::error::misc_category));
}
else
{
RecursiveLock lock(mHandlerMutex);
if(mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category));
return asio::error_code(MissingAttributes, asio::error::misc_category);
}
}
return asio::error_code();
}
asio::error_code
TurnAsyncSocket::handleRefreshResponse(StunMessage &request, StunMessage &response)
{
if(response.mClass == StunMessage::StunClassSuccessResponse)
{
if(response.mHasTurnLifetime)
{
mLifetime = response.mTurnLifetime;
}
else
{
mLifetime = 0;
}
if(mLifetime != 0)
{
mHaveAllocation = true;
startAllocationTimer();
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onRefreshSuccess(getSocketDescriptor(), mLifetime);
}
if(mCloseAfterDestroyAllocationFinishes)
{
mHaveAllocation = false;
actualClose();
}
}
else
{
cancelAllocationTimer();
mHaveAllocation = false;
mChannelManager.clear();
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onRefreshSuccess(getSocketDescriptor(), 0);
}
if(mCloseAfterDestroyAllocationFinishes)
{
actualClose();
}
}
}
else
{
// Check if success or not
if(response.mHasErrorCode)
{
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onRefreshFailure(getSocketDescriptor(), asio::error_code(response.mErrorCode.errorClass * 100 + response.mErrorCode.number, asio::error::misc_category));
}
if(mCloseAfterDestroyAllocationFinishes)
{
cancelAllocationTimer();
mHaveAllocation = false;
actualClose();
}
else if(response.mErrorCode.errorClass == 4 && response.mErrorCode.number == 37) // response is 437, then remove allocation
{
cancelAllocationTimer();
mHaveAllocation = false;
mChannelManager.clear();
}
}
else
{
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onRefreshFailure(getSocketDescriptor(), asio::error_code(MissingAttributes, asio::error::misc_category));
}
if(mCloseAfterDestroyAllocationFinishes)
{
cancelAllocationTimer();
mHaveAllocation = false;
actualClose();
}
return asio::error_code(MissingAttributes, asio::error::misc_category);
}
}
return asio::error_code();
}
void
TurnAsyncSocket::send(const char* const buffer, const size_t size)
{
sendFramed(std::make_shared<DataBuffer>(buffer, size));
}
void
TurnAsyncSocket::sendTo(const asio::ip::address& address, unsigned short port, const char* const buffer, const size_t size)
{
sendToFramed(address, port, std::make_shared<DataBuffer>(buffer, size));
}
void
TurnAsyncSocket::sendFramed(const std::shared_ptr<DataBuffer>& data)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doSendFramed(data); }));
}
void
TurnAsyncSocket::sendToFramed(const asio::ip::address& address, unsigned short port, const std::shared_ptr<DataBuffer>& data)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doSendToFramed(address, port, data); }));
}
void
TurnAsyncSocket::sendUnframed(const std::shared_ptr<DataBuffer>& data)
{
StunTuple destination(mLocalBinding.getTransportType(), mAsyncSocketBase.getConnectedAddress(), mAsyncSocketBase.getConnectedPort());
mAsyncSocketBase.send(destination, data);
}
void
TurnAsyncSocket::sendToUnframed(const asio::ip::address& address, unsigned short port, const std::shared_ptr<DataBuffer>& data)
{
StunTuple destination(mLocalBinding.getTransportType(), address, port);
mAsyncSocketBase.send(destination, data);
}
void
TurnAsyncSocket::doSendFramed(const std::shared_ptr<DataBuffer>& data)
{
if(mActiveDestination)
{
sendToRemotePeer(*mActiveDestination, data);
}
else if(mAsyncSocketBase.isConnected())
{
//DebugLog(<<"Sending to connected peer");
sendToUnframed(mAsyncSocketBase.getConnectedAddress(), mAsyncSocketBase.getConnectedPort(), data);
}
else
{
DebugLog(<<"no allocation, can't send!");
}
}
void
TurnAsyncSocket::doSendToFramed(const asio::ip::address& address, unsigned short port, const std::shared_ptr<DataBuffer>& data)
{
// Setup Remote Peer
StunTuple remoteTuple(mRelayTransportType, address, port);
RemotePeer* remotePeer = mChannelManager.findRemotePeerByPeerAddress(remoteTuple);
if(!remotePeer)
{
// No remote peer yet (ie. no data sent or received from remote peer) - so create one
remotePeer = mChannelManager.createChannelBinding(remoteTuple);
resip_assert(remotePeer);
doChannelBinding(*remotePeer);
}
return sendToRemotePeer(*remotePeer, data);
}
void
TurnAsyncSocket::sendToRemotePeer(RemotePeer& remotePeer, const std::shared_ptr<DataBuffer>& data)
{
if(remotePeer.isChannelConfirmed())
{
// send framed data to active destination
sendOverChannel(remotePeer.getChannel(), data);
//InfoLog( << "TurnAsyncSocket::sendTo: using channel " << remotePeer.getChannel() << " to send " << data->size() << " bytes.");
}
else
{
// Data must be wrapped in a Send Indication
// Wrap data in a SendInd
StunMessage* ind = createNewStunMessage(StunMessage::StunClassIndication, StunMessage::TurnSendMethod, false);
ind->mCntTurnXorPeerAddress = 1;
StunMessage::setStunAtrAddressFromTuple(ind->mTurnXorPeerAddress[0], remotePeer.getPeerTuple());
if(data->size() > 0)
{
ind->setTurnData(data->data(), (unsigned int)data->size());
}
// Send indication to Turn Server
sendStunMessage(ind);
}
}
void
TurnAsyncSocket::setSoftware(const char* software)
{
asio::dispatch(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [=] { doSetSoftware(new Data(software)); }));
}
void
TurnAsyncSocket::doSetSoftware(Data* software)
{
mSoftware = *software;
delete software;
const uint32_t unpaddedSize = (unsigned int)mSoftware.size();
if(unpaddedSize > 0)
{
// Pad size to a multiple of 4, to help compatibility with older clients
const uint32_t remainder = unpaddedSize % 4,
paddedSize = remainder ? unpaddedSize + 4 - remainder : unpaddedSize;
while(mSoftware.size() < paddedSize)
mSoftware.append(" ", 1);
}
}
void
TurnAsyncSocket::connect(const std::string& address, unsigned short port)
{
mAsyncSocketBase.connect(address, port);
}
void
TurnAsyncSocket::close()
{
asio::post(mIOService, weak_bind<AsyncSocketBase, void()>(mAsyncSocketBase.shared_from_this(), [this] { doClose(); }));
}
void
TurnAsyncSocket::doClose()
{
// If we have an allocation over UDP then we should send a refresh with lifetime 0 to destroy the allocation
// Note: For TCP and TLS, the socket disconnection will destroy the allocation automatically
if(mHaveAllocation && mLocalBinding.getTransportType() == StunTuple::UDP)
{
mCloseAfterDestroyAllocationFinishes = true;
destroyAllocation();
}
else
{
actualClose();
}
}
void
TurnAsyncSocket::actualClose()
{
clearActiveRequestMap();
cancelAllocationTimer();
cancelChannelBindingTimers();
mAsyncSocketBase.close();
}
void
TurnAsyncSocket::turnReceive()
{
if(mLocalBinding.getTransportType() == StunTuple::UDP)
{
//mAsyncSocketBase.receive();
mAsyncSocketBase.doReceive();
}
else
{
//mAsyncSocketBase.framedReceive();
mAsyncSocketBase.doFramedReceive();
}
}
void
TurnAsyncSocket::sendOverChannel(unsigned short channel, const std::shared_ptr<DataBuffer>& data)
{
StunTuple destination(mLocalBinding.getTransportType(), mAsyncSocketBase.getConnectedAddress(), mAsyncSocketBase.getConnectedPort());
mAsyncSocketBase.send(destination, channel, data);
}
TurnAsyncSocket::RequestEntry::RequestEntry(asio::io_context& ioService,
TurnAsyncSocket* turnAsyncSocket,
StunMessage* requestMessage,
unsigned int rc,
unsigned int retrans_interval_ms,
const StunTuple* dest)
:
mIOService(ioService),
mTurnAsyncSocket(turnAsyncSocket),
mRequestMessage(requestMessage),
mRequestTimer(ioService),
mRequestsSent(1),
mDest(dest ? new StunTuple(dest->getTransportType(), dest->getAddress(), dest->getPort()) : 0),
mRc(rc),
mRetransIntervalMs(retrans_interval_ms)
{
//std::cout << "RequestEntry::RequestEntry() " << mRequestMessage->mHeader.magicCookieAndTid << " dest: " << (mDest ? dest->getPort() : 0) << std::endl;
mTimeout = mTurnAsyncSocket->mLocalBinding.getTransportType() == StunTuple::UDP ? UDP_RT0 : TCP_RESPONSE_TIME;
}
void
TurnAsyncSocket::RequestEntry::startTimer()
{
//std::cout << "RequestEntry::startTimer() " << mTimeout << " " << mRequestMessage->mHeader.magicCookieAndTid << std::endl;
// start the request timer
mRequestTimer.expires_after(milliseconds(mTimeout));
mRequestTimer.async_wait(weak_bind<RequestEntry, void(const asio::error_code&)>(shared_from_this(), std::bind(&TurnAsyncSocket::RequestEntry::requestTimerExpired, this, std::placeholders::_1)));
}
void
TurnAsyncSocket::RequestEntry::stopTimer()
{
//std::cout << "RequestEntry::stopTimer() " << mRequestMessage->mHeader.magicCookieAndTid << std::endl;
// stop the request timer
mRequestTimer.cancel();
}
TurnAsyncSocket::RequestEntry::~RequestEntry()
{
//std::cout << "RequestEntry::~RequestEntry() " << mRequestMessage->mHeader.magicCookieAndTid << std::endl;
delete mRequestMessage;
delete mDest;
stopTimer();
}
void
TurnAsyncSocket::RequestEntry::requestTimerExpired(const asio::error_code& e)
{
if(!e && mRequestMessage) // Note: There is a race condition with clearing out of mRequestMessage when 401 is received - check that mRequestMessage is not 0 avoids any resulting badness
{
//std::cout << "RequestEntry::requestTimerExpired() " << mRequestMessage->mHeader.magicCookieAndTid << std::endl;
if(mTurnAsyncSocket->mLocalBinding.getTransportType() != StunTuple::UDP || mRequestsSent == mRc)
{
mTurnAsyncSocket->requestTimeout(mRequestMessage->mHeader.magicCookieAndTid);
return;
}
// timed out and should retransmit - calculate next timeout
if(mRetransIntervalMs == DEFAULT_RETRANS_INTERVAL_MS)
{
if(mRequestsSent == mRc - 1)
{
mTimeout = UDP_FINAL_REQUEST_TIME;
}
else
{
mTimeout = (mTimeout*2);
}
}
else
{
mTimeout = mRetransIntervalMs;
}
// retransmit
DebugLog(<< "RequestEntry::requestTimerExpired: retransmitting...");
mRequestsSent++;
mTurnAsyncSocket->sendStunMessage(mRequestMessage, true, UDP_MAX_RETRANSMITS, DEFAULT_RETRANS_INTERVAL_MS, mDest);
startTimer();
}
}
void
TurnAsyncSocket::requestTimeout(UInt128 tid)
{
RequestMap::iterator it = mActiveRequestMap.find(tid);
if (it != mActiveRequestMap.end())
{
const auto requestEntry = it->second;
mActiveRequestMap.erase(tid);
switch (requestEntry->mRequestMessage->mMethod)
{
case StunMessage::BindMethod:
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onBindFailure(getSocketDescriptor(), asio::error_code(reTurn::ResponseTimeout, asio::error::misc_category), (requestEntry->mDest ? *(requestEntry->mDest) : StunTuple()));
}
break;
case StunMessage::SharedSecretMethod:
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onSharedSecretFailure(getSocketDescriptor(), asio::error_code(reTurn::ResponseTimeout, asio::error::misc_category));
}
break;
case StunMessage::TurnAllocateMethod:
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onAllocationFailure(getSocketDescriptor(), asio::error_code(reTurn::ResponseTimeout, asio::error::misc_category));
}
break;
case StunMessage::TurnRefreshMethod:
{
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onRefreshFailure(getSocketDescriptor(), asio::error_code(reTurn::ResponseTimeout, asio::error::misc_category));
}
if (mCloseAfterDestroyAllocationFinishes)
{
mHaveAllocation = false;
actualClose();
}
break;
case StunMessage::TurnChannelBindMethod:
{
// Note: ChannelBind can happen after SetActiveDestination, after a sendTo, or during a channel bind refresh
RecursiveLock lock(mHandlerMutex);
if (mTurnAsyncSocketHandler) mTurnAsyncSocketHandler->onChannelBindFailure(getSocketDescriptor(), asio::error_code(reTurn::ResponseTimeout, asio::error::misc_category));
}
break;
default:
resip_assert(false);
}
}
}
void
TurnAsyncSocket::clearActiveRequestMap()
{
// Clear out active request map - !slg! TODO this really should happen from the io service thread
RequestMap::iterator it = mActiveRequestMap.begin();
for(;it != mActiveRequestMap.end(); it++)
{
it->second->stopTimer();
}
mActiveRequestMap.clear();
}
void
TurnAsyncSocket::startAllocationTimer()
{
mAllocationTimer.expires_after(seconds((mLifetime*5)/8)); // Allocation refresh should sent before 3/4 lifetime - use 5/8 lifetime
mAllocationTimer.async_wait(weak_bind<AsyncSocketBase, void(const asio::error_code&)>(mAsyncSocketBase.shared_from_this(), std::bind(&TurnAsyncSocket::allocationTimerExpired, this, std::placeholders::_1)));
}
void
TurnAsyncSocket::cancelAllocationTimer()
{
mAllocationTimer.cancel();
}
void
TurnAsyncSocket::allocationTimerExpired(const asio::error_code& e)
{
if(!e)
doRefreshAllocation(mLifetime);
}
void
TurnAsyncSocket::startChannelBindingTimer(unsigned short channel)
{
ChannelBindingTimerMap::iterator it = mChannelBindingTimers.find(channel);
if(it==mChannelBindingTimers.end())
{
std::pair<ChannelBindingTimerMap::iterator,bool> ret =
mChannelBindingTimers.insert(std::pair<unsigned short, asio::steady_timer*>(channel, new asio::steady_timer(mIOService)));
resip_assert(ret.second);
it = ret.first;
}
it->second->expires_after(seconds(TURN_CHANNEL_BINDING_REFRESH_SECONDS));
it->second->async_wait(weak_bind<AsyncSocketBase, void(const asio::error_code&)>( mAsyncSocketBase.shared_from_this(), std::bind(&TurnAsyncSocket::channelBindingTimerExpired, this, std::placeholders::_1, channel)));
}
void
TurnAsyncSocket::cancelChannelBindingTimers()
{
// Cleanup ChannelBinding Timers
ChannelBindingTimerMap::iterator it = mChannelBindingTimers.begin();
for(;it!=mChannelBindingTimers.end();it++)
{
it->second->cancel();
delete it->second;
}
mChannelBindingTimers.clear();
}
void
TurnAsyncSocket::channelBindingTimerExpired(const asio::error_code& e, unsigned short channel)
{
if(!e)
{
RemotePeer* remotePeer = mChannelManager.findRemotePeerByChannel(channel);
if(remotePeer)
{
doChannelBinding(*remotePeer);
}
}
}
void
TurnAsyncSocket::setOnBeforeSocketClosedFp(AsyncSocketBase::BeforeClosedHandler fp)
{
mAsyncSocketBase.setOnBeforeSocketClosedFp(std::move(fp));
}
} // namespace
/* ====================================================================
Copyright (c) 2023-2024, SIP Specturm, Inc. http://sipspectrum.com
Copyright (c) 2007-2008, Plantronics, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Plantronics nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==================================================================== */