mirror of
https://github.com/resiprocate/resiprocate.git
synced 2026-01-12 00:05:02 +08:00
This updates usage of Asio for compatibility with version 1.34 and newer, where a number of previously deprecated APIs were removed.
1507 lines
55 KiB
C++
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.
|
|
|
|
==================================================================== */
|