reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
AsyncSocketBase.cxx
Go to the documentation of this file.
1#include "AsyncSocketBase.hxx"
4#include <rutil/Logger.hxx>
5#include "ReTurnSubsystem.hxx"
6
7#include <functional>
8#include <iterator>
9
10#define RESIPROCATE_SUBSYSTEM ReTurnSubsystem::RETURN
11
12using namespace std;
13
14#define NO_CHANNEL ((unsigned short)-1)
15
16namespace reTurn {
17
18AsyncSocketBase::AsyncSocketBase(asio::io_context& ioService) :
19 mIOService(ioService),
20 mReceiving(false),
21 mConnected(false),
22 mAsyncSocketBaseHandler(nullptr)
23{
24}
25
30
31void
32AsyncSocketBase::send(const StunTuple& destination, const std::shared_ptr<DataBuffer>& data)
33{
34 shared_ptr<AsyncSocketBase> _this = shared_from_this();
35 asio::dispatch(mIOService, std::bind([_this, destination, data](){
36 _this->doSend(destination, data, 0);
37 }));
38}
39
40void
41AsyncSocketBase::send(const StunTuple& destination, unsigned short channel, const std::shared_ptr<DataBuffer>& data)
42{
43 shared_ptr<AsyncSocketBase> _this = shared_from_this();
44 asio::dispatch(mIOService, std::bind([_this, destination, channel, data](){
45 _this->doSend(destination, channel, data, 0);
46 }));
47}
48
49void
50AsyncSocketBase::doSend(const StunTuple& destination, const std::shared_ptr<DataBuffer>& data, const std::size_t bufferStartPos)
51{
52 doSend(destination, NO_CHANNEL, data, bufferStartPos);
53}
54
55void
56AsyncSocketBase::doSend(const StunTuple& destination, unsigned short channel, const std::shared_ptr<DataBuffer>& data, const std::size_t bufferStartPos)
57{
58 bool writeInProgress = !mSendDataQueue.empty();
59 if (channel == NO_CHANNEL)
60 {
61 mSendDataQueue.push_back(SendData(destination, nullptr, data, bufferStartPos));
62 }
63 else
64 {
65 // Add Turn Framing
66 const auto frame = allocateBuffer(4);
67 channel = htons(channel);
68 memcpy(&(*frame)[0], &channel, 2);
69 unsigned short msgsize = htons((unsigned short)data->size());
70 memcpy(&(*frame)[2], (void*)&msgsize, 2);
71 const size_t padding = destination.getTransportType() == StunTuple::UDP ? 0 : channelDataPadding(data->size());
72 mSendDataQueue.push_back(SendData(destination, frame, data, bufferStartPos, padding));
73 }
74 if (!writeInProgress)
75 {
76 sendFirstQueuedData();
77 }
78}
79
80void
81AsyncSocketBase::handleSend(const asio::error_code& e)
82{
83 if(!e)
84 {
86 }
87 else
88 {
89 DebugLog(<< "handleSend with error: " << e);
91 }
92
93 // TODO - check if closed here, and if so don't try and send more
94 // Clear this data from the queue and see if there is more data to send
95 mSendDataQueue.pop_front();
96 if (!mSendDataQueue.empty())
97 {
98 sendFirstQueuedData();
99 }
100}
101
102void
103AsyncSocketBase::sendFirstQueuedData()
104{
105 std::vector<asio::const_buffer> bufs;
106 if (mSendDataQueue.front().mFrameData) // If we have frame data
107 {
108 bufs.push_back(asio::buffer(mSendDataQueue.front().mFrameData->data(), mSendDataQueue.front().mFrameData->size()));
109 }
110 bufs.push_back(asio::buffer(mSendDataQueue.front().mData->data()+mSendDataQueue.front().mBufferStartPos, mSendDataQueue.front().mData->size()-mSendDataQueue.front().mBufferStartPos));
111 if (mSendDataQueue.front().mPadding)
112 {
113 static const char zeros[3] = { 0, 0, 0 };
114 bufs.push_back(asio::buffer(zeros, mSendDataQueue.front().mPadding));
115 }
116 transportSend(mSendDataQueue.front().mDestination, bufs);
117}
118
119void
121{
122 asio::post(mIOService, std::bind(&AsyncSocketBase::doReceive, shared_from_this()));
123}
124
125void
127{
128 if(!mReceiving)
129 {
130 mReceiving=true;
132 transportReceive();
133 }
134}
135
136void
138{
139 asio::post(mIOService, std::bind(&AsyncSocketBase::doFramedReceive, shared_from_this()));
140}
141
142void
144{
145 if(!mReceiving)
146 {
147 mReceiving=true;
149 transportFramedReceive();
150 }
151}
152
153void
154AsyncSocketBase::handleReceive(const asio::error_code& e, const size_t bytesTransferred)
155{
156 mReceiving = false;
157
158 if(!e)
159 {
160 // Handoff received buffer to appliction, and prepare receive buffer for next call
161 mReceiveBuffer->truncate(bytesTransferred);
162 onReceiveSuccess(getSenderEndpointAddress(), getSenderEndpointPort(), mReceiveBuffer);
163 }
164 else
165 {
166 DebugLog(<< "handleReceive with error: " << e);
168 }
169}
170
171void
173{
174 asio::post(mIOService, std::bind(&AsyncSocketBase::transportClose, shared_from_this()));
175}
176
177std::shared_ptr<DataBuffer>
179{
180 return std::make_shared<DataBuffer>(size);
181}
182
183void
184AsyncSocketBase::setExclusiveAddressUse(asio::ip::udp::socket& socket)
185{
186#ifdef _WIN32
187 BOOL on = TRUE;
188 if(::setsockopt(socket.native_handle(), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&on, sizeof(on)) != 0)
189 {
190 WarningLog(<< "Unable to set SO_EXCLUSIVEADDRUSE on a UDP socket, error=" << WSAGetLastError());
191 }
192#else
193 (void)socket;
194#endif
195}
196
197asio::error_code
198AsyncSocketBase::openAndBind(asio::ip::tcp::socket& socket)
199{
200 asio::error_code errorCode;
201 socket.close(errorCode);
202 socket.open(mLocalTcpEndpoint.protocol(), errorCode);
203 if(!errorCode)
204 {
205 asio::error_code ignored;
206 socket.set_option(asio::ip::tcp::no_delay(true), ignored); // ?slg? do we want this?
207 socket.set_option(asio::ip::tcp::socket::reuse_address(true), ignored);
208 socket.bind(mLocalTcpEndpoint, errorCode);
209 }
210 return errorCode;
211}
212
213void
214AsyncSocketBase::asyncConnect(asio::ip::tcp::socket& socket, const asio::ip::tcp::resolver::results_type& endpoints)
215{
216 asyncConnectFrom(socket, endpoints, endpoints.begin(), asio::error::host_not_found);
217}
218
219void
220AsyncSocketBase::asyncConnectFrom(asio::ip::tcp::socket& socket, asio::ip::tcp::resolver::results_type endpoints,
221 asio::ip::tcp::resolver::results_type::const_iterator next, asio::error_code lastError)
222{
223 if(next == endpoints.end())
224 {
225 handleConnect(lastError, asio::ip::tcp::endpoint());
226 return;
227 }
228
229 // Each attempt on a new socket, bound to the local endpoint
230 asio::error_code errorCode = openAndBind(socket);
231 if(errorCode)
232 {
233 handleConnect(errorCode, asio::ip::tcp::endpoint());
234 return;
235 }
236
237 const asio::ip::tcp::endpoint endpoint = *next;
238 std::shared_ptr<AsyncSocketBase> self = shared_from_this();
239 socket.async_connect(endpoint, [self, &socket, endpoints, next, endpoint](const asio::error_code& ec)
240 {
241 // Done if connected, or if the socket has been closed meanwhile; otherwise try the next endpoint
242 if(!ec || ec == asio::error::operation_aborted)
243 {
244 self->handleConnect(ec, endpoint);
245 }
246 else
247 {
248 self->asyncConnectFrom(socket, endpoints, std::next(next), ec);
249 }
250 });
251}
252
253void
254AsyncSocketBase::startIdleTimer(unsigned int timeoutSeconds, std::function<bool()> inUse)
255{
256 if(timeoutSeconds == 0)
257 {
258 return;
259 }
260 mIdleTimeout = std::chrono::seconds(timeoutSeconds);
261 mInUse = std::move(inUse);
262 mIdleTimer.reset(new asio::steady_timer(mIOService));
263 noteReceived();
264 waitIdle(mIdleTimeout);
265}
266
267void
269{
270 mInUse = nullptr; // in case the timer has fired already, and its handler is queued
271 if(mIdleTimer)
272 {
273 mIdleTimer->cancel();
274 }
275}
276
277void
278AsyncSocketBase::waitIdle(std::chrono::steady_clock::duration wait)
279{
280 // Rather than being set again for each message received, the timer is set for when the
281 // connection would have been idle long enough, and then checks. It holds a weak reference: it
282 // doesn't keep a stopped connection alive.
283 std::weak_ptr<AsyncSocketBase> weakSelf = shared_from_this();
284 mIdleTimer->expires_after(wait);
285 mIdleTimer->async_wait([weakSelf](const asio::error_code& e)
286 {
287 std::shared_ptr<AsyncSocketBase> self = weakSelf.lock();
288 if(e == asio::error::operation_aborted || !self || !self->mInUse)
289 {
290 return;
291 }
292 const std::chrono::steady_clock::duration idle = std::chrono::steady_clock::now() - self->mLastReceived;
293 if(self->mInUse())
294 {
295 self->waitIdle(self->mIdleTimeout); // kept for as long as it's in use
296 }
297 else if(idle < self->mIdleTimeout)
298 {
299 self->waitIdle(self->mIdleTimeout - idle);
300 }
301 else
302 {
303 InfoLog(<< "Closing the connection from " << self->mConnectedAddress.to_string() << ":" << self->mConnectedPort
304 << ": nothing received for " << std::chrono::duration_cast<std::chrono::seconds>(idle).count() << " seconds");
305 self->close();
306 }
307 });
308}
309
310} // namespace
311
312
313/* ====================================================================
314
315 Copyright (c) 2008-2026 SIP Spectrum, Inc. https://www.sipspectrum.com
316 Copyright (c) 2007-2008, Plantronics, Inc.
317 All rights reserved.
318
319 Redistribution and use in source and binary forms, with or without
320 modification, are permitted provided that the following conditions are
321 met:
322
323 1. Redistributions of source code must retain the above copyright
324 notice, this list of conditions and the following disclaimer.
325
326 2. Redistributions in binary form must reproduce the above copyright
327 notice, this list of conditions and the following disclaimer in the
328 documentation and/or other materials provided with the distribution.
329
330 3. Neither the name of Plantronics nor the names of its contributors
331 may be used to endorse or promote products derived from this
332 software without specific prior written permission.
333
334 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
335 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
336 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
337 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
338 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
339 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
340 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
341 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
342 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
343 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
344 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
345
346 ==================================================================== */
347
#define NO_CHANNEL
constexpr size_t RECEIVE_BUFFER_SIZE
#define DebugLog(args_)
Definition Logger.hxx:128
#define InfoLog(args_)
Definition Logger.hxx:131
#define WarningLog(args_)
Definition Logger.hxx:134
virtual void onSocketDestroyed()=0
virtual void handleConnect(const asio::error_code &ec, const asio::ip::tcp::endpoint &endpoint)
virtual void handleReceive(const asio::error_code &e, size_t bytesTransferred)
asio::ip::tcp::endpoint mLocalTcpEndpoint
asio::error_code openAndBind(asio::ip::tcp::socket &socket)
For TCP and TLS client sockets.
virtual void onReceiveSuccess(const asio::ip::address &address, unsigned short port, const std::shared_ptr< DataBuffer > &data)=0
static size_t channelDataPadding(size_t length)
Over TCP and TLS, a TURN ChannelData message is padded to a multiple of 4 bytes, the padding not coun...
virtual void handleSend(const asio::error_code &e)
Handle completion of a sendData operation.
static std::shared_ptr< DataBuffer > allocateBuffer(size_t size)
Utility API.
virtual void onReceiveFailure(const asio::error_code &e)=0
void asyncConnect(asio::ip::tcp::socket &socket, const asio::ip::tcp::resolver::results_type &endpoints)
virtual void onSendFailure(const asio::error_code &e)=0
virtual void onSendSuccess()=0
asio::io_context & mIOService
The io_context used to perform asynchronous operations.
virtual void doSend(const StunTuple &destination, unsigned short channel, const std::shared_ptr< DataBuffer > &data, const std::size_t bufferStartPos=0)
Use these if you already operating within the ioService thread.
std::shared_ptr< DataBuffer > mReceiveBuffer
Receive Buffer and state.
AsyncSocketBaseHandler * mAsyncSocketBaseHandler
Handlers.
AsyncSocketBase(asio::io_context &ioService)
virtual void send(const StunTuple &destination, const std::shared_ptr< DataBuffer > &data)
Note: destination is ignored for TCP and TLS connections.
void startIdleTimer(unsigned int timeoutSeconds, std::function< bool()> inUse)
For server connections (TcpConnectionIdleTimeout): the connection is closed, with close(),...
static void setExclusiveAddressUse(asio::ip::udp::socket &socket)
For a UDP socket, between open() and bind().
virtual void receive()
Overlapped calls to receive functions have no effect.
TransportType getTransportType() const
Definition StunTuple.hxx:41