mirror of
https://github.com/ossrs/srs.git
synced 2025-02-15 04:42:04 +00:00
fix https://github.com/ossrs/srs/issues/3155 Build srt-1-fit fails with `standard attributes in middle of decl-specifiers` on GCC 12,Arch Linux. See https://github.com/Haivision/srt/releases/tag/v1.5.3
4709 lines
151 KiB
C++
4709 lines
151 KiB
C++
/*
|
|
* SRT - Secure, Reliable, Transport
|
|
* Copyright (c) 2018 Haivision Systems Inc.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
*/
|
|
|
|
/*****************************************************************************
|
|
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
* Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer.
|
|
|
|
* 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.
|
|
|
|
* Neither the name of the University of Illinois
|
|
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.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
written by
|
|
Yunhong Gu, last updated 07/09/2011
|
|
modified by
|
|
Haivision Systems Inc.
|
|
*****************************************************************************/
|
|
|
|
#include "platform_sys.h"
|
|
|
|
#include <exception>
|
|
#include <stdexcept>
|
|
#include <typeinfo>
|
|
#include <iterator>
|
|
#include <vector>
|
|
|
|
#include <cstring>
|
|
#include "utilities.h"
|
|
#include "netinet_any.h"
|
|
#include "api.h"
|
|
#include "core.h"
|
|
#include "epoll.h"
|
|
#include "logging.h"
|
|
#include "threadname.h"
|
|
#include "srt.h"
|
|
#include "udt.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <win/wintime.h>
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(error : 4530)
|
|
#endif
|
|
|
|
using namespace std;
|
|
using namespace srt_logging;
|
|
using namespace srt::sync;
|
|
|
|
void srt::CUDTSocket::construct()
|
|
{
|
|
#if ENABLE_BONDING
|
|
m_GroupOf = NULL;
|
|
m_GroupMemberData = NULL;
|
|
#endif
|
|
setupMutex(m_AcceptLock, "Accept");
|
|
setupCond(m_AcceptCond, "Accept");
|
|
setupMutex(m_ControlLock, "Control");
|
|
}
|
|
|
|
srt::CUDTSocket::~CUDTSocket()
|
|
{
|
|
releaseMutex(m_AcceptLock);
|
|
releaseCond(m_AcceptCond);
|
|
releaseMutex(m_ControlLock);
|
|
}
|
|
|
|
SRT_SOCKSTATUS srt::CUDTSocket::getStatus()
|
|
{
|
|
// TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false.
|
|
// Although m_Status is still SRTS_CONNECTING, the connection is in fact to be closed due to TTL expiry.
|
|
// In this case m_bConnected is also false. Both checks are required to avoid hitting
|
|
// a regular state transition from CONNECTING to CONNECTED.
|
|
|
|
if (m_UDT.m_bBroken)
|
|
return SRTS_BROKEN;
|
|
|
|
// Connecting timed out
|
|
if ((m_Status == SRTS_CONNECTING) && !m_UDT.m_bConnecting && !m_UDT.m_bConnected)
|
|
return SRTS_BROKEN;
|
|
|
|
return m_Status;
|
|
}
|
|
|
|
// [[using locked(m_GlobControlLock)]]
|
|
void srt::CUDTSocket::breakSocket_LOCKED()
|
|
{
|
|
// This function is intended to be called from GC,
|
|
// under a lock of m_GlobControlLock.
|
|
m_UDT.m_bBroken = true;
|
|
m_UDT.m_iBrokenCounter = 0;
|
|
HLOGC(smlog.Debug, log << "@" << m_SocketID << " CLOSING AS SOCKET");
|
|
m_UDT.closeInternal();
|
|
setClosed();
|
|
}
|
|
|
|
void srt::CUDTSocket::setClosed()
|
|
{
|
|
m_Status = SRTS_CLOSED;
|
|
|
|
// a socket will not be immediately removed when it is closed
|
|
// in order to prevent other methods from accessing invalid address
|
|
// a timer is started and the socket will be removed after approximately
|
|
// 1 second
|
|
m_tsClosureTimeStamp = steady_clock::now();
|
|
}
|
|
|
|
void srt::CUDTSocket::setBrokenClosed()
|
|
{
|
|
m_UDT.m_iBrokenCounter = 60;
|
|
m_UDT.m_bBroken = true;
|
|
setClosed();
|
|
}
|
|
|
|
bool srt::CUDTSocket::readReady()
|
|
{
|
|
// TODO: Use m_RcvBufferLock here (CUDT::isRcvReadReady())?
|
|
if (m_UDT.m_bConnected && m_UDT.m_pRcvBuffer->isRcvDataReady())
|
|
return true;
|
|
|
|
if (m_UDT.m_bListening)
|
|
return !m_QueuedSockets.empty();
|
|
|
|
return broken();
|
|
}
|
|
|
|
bool srt::CUDTSocket::writeReady() const
|
|
{
|
|
return (m_UDT.m_bConnected && (m_UDT.m_pSndBuffer->getCurrBufSize() < m_UDT.m_config.iSndBufSize)) || broken();
|
|
}
|
|
|
|
bool srt::CUDTSocket::broken() const
|
|
{
|
|
return m_UDT.m_bBroken || !m_UDT.m_bConnected;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
srt::CUDTUnited::CUDTUnited()
|
|
: m_Sockets()
|
|
, m_GlobControlLock()
|
|
, m_IDLock()
|
|
, m_mMultiplexer()
|
|
, m_MultiplexerLock()
|
|
, m_pCache(NULL)
|
|
, m_bClosing(false)
|
|
, m_GCStopCond()
|
|
, m_InitLock()
|
|
, m_iInstanceCount(0)
|
|
, m_bGCStatus(false)
|
|
, m_ClosedSockets()
|
|
{
|
|
// Socket ID MUST start from a random value
|
|
m_SocketIDGenerator = genRandomInt(1, MAX_SOCKET_VAL);
|
|
m_SocketIDGenerator_init = m_SocketIDGenerator;
|
|
|
|
// XXX An unlikely exception thrown from the below calls
|
|
// might destroy the application before `main`. This shouldn't
|
|
// be a problem in general.
|
|
setupMutex(m_GCStopLock, "GCStop");
|
|
setupCond(m_GCStopCond, "GCStop");
|
|
setupMutex(m_GlobControlLock, "GlobControl");
|
|
setupMutex(m_IDLock, "ID");
|
|
setupMutex(m_InitLock, "Init");
|
|
|
|
m_pCache = new CCache<CInfoBlock>;
|
|
}
|
|
|
|
srt::CUDTUnited::~CUDTUnited()
|
|
{
|
|
// Call it if it wasn't called already.
|
|
// This will happen at the end of main() of the application,
|
|
// when the user didn't call srt_cleanup().
|
|
if (m_bGCStatus)
|
|
{
|
|
cleanup();
|
|
}
|
|
|
|
releaseMutex(m_GlobControlLock);
|
|
releaseMutex(m_IDLock);
|
|
releaseMutex(m_InitLock);
|
|
// XXX There's some weird bug here causing this
|
|
// to hangup on Windows. This might be either something
|
|
// bigger, or some problem in pthread-win32. As this is
|
|
// the application cleanup section, this can be temporarily
|
|
// tolerated with simply exit the application without cleanup,
|
|
// counting on that the system will take care of it anyway.
|
|
#ifndef _WIN32
|
|
releaseCond(m_GCStopCond);
|
|
#endif
|
|
releaseMutex(m_GCStopLock);
|
|
|
|
delete m_pCache;
|
|
}
|
|
|
|
string srt::CUDTUnited::CONID(SRTSOCKET sock)
|
|
{
|
|
if (sock == 0)
|
|
return "";
|
|
|
|
std::ostringstream os;
|
|
os << "@" << sock << ":";
|
|
return os.str();
|
|
}
|
|
|
|
int srt::CUDTUnited::startup()
|
|
{
|
|
ScopedLock gcinit(m_InitLock);
|
|
|
|
if (m_iInstanceCount++ > 0)
|
|
return 1;
|
|
|
|
// Global initialization code
|
|
#ifdef _WIN32
|
|
WORD wVersionRequested;
|
|
WSADATA wsaData;
|
|
wVersionRequested = MAKEWORD(2, 2);
|
|
|
|
if (0 != WSAStartup(wVersionRequested, &wsaData))
|
|
throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError());
|
|
#endif
|
|
|
|
CCryptoControl::globalInit();
|
|
|
|
PacketFilter::globalInit();
|
|
|
|
if (m_bGCStatus)
|
|
return 1;
|
|
|
|
m_bClosing = false;
|
|
|
|
if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC"))
|
|
return -1;
|
|
|
|
m_bGCStatus = true;
|
|
|
|
HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CUDTUnited::cleanup()
|
|
{
|
|
// IMPORTANT!!!
|
|
// In this function there must be NO LOGGING AT ALL. This function may
|
|
// potentially be called from within the global program destructor, and
|
|
// therefore some of the facilities used by the logging system - including
|
|
// the default std::cerr object bound to it by default, but also a different
|
|
// stream that the user's app has bound to it, and which got destroyed
|
|
// together with already exited main() - may be already deleted when
|
|
// executing this procedure.
|
|
ScopedLock gcinit(m_InitLock);
|
|
|
|
if (--m_iInstanceCount > 0)
|
|
return 0;
|
|
|
|
if (!m_bGCStatus)
|
|
return 0;
|
|
|
|
{
|
|
UniqueLock gclock(m_GCStopLock);
|
|
m_bClosing = true;
|
|
}
|
|
// NOTE: we can do relaxed signaling here because
|
|
// waiting on m_GCStopCond has a 1-second timeout,
|
|
// after which the m_bClosing flag is cheched, which
|
|
// is set here above. Worst case secenario, this
|
|
// pthread_join() call will block for 1 second.
|
|
CSync::notify_one_relaxed(m_GCStopCond);
|
|
m_GCThread.join();
|
|
|
|
m_bGCStatus = false;
|
|
|
|
// Global destruction code
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group)
|
|
{
|
|
ScopedLock guard(m_IDLock);
|
|
|
|
int sockval = m_SocketIDGenerator - 1;
|
|
|
|
// First problem: zero-value should be avoided by various reasons.
|
|
|
|
if (sockval <= 0)
|
|
{
|
|
// We have a rollover on the socket value, so
|
|
// definitely we haven't made the Columbus mistake yet.
|
|
m_SocketIDGenerator = MAX_SOCKET_VAL;
|
|
sockval = MAX_SOCKET_VAL;
|
|
}
|
|
|
|
// Check all sockets if any of them has this value.
|
|
// Socket IDs are begin created this way:
|
|
//
|
|
// Initial random
|
|
// |
|
|
// |
|
|
// |
|
|
// |
|
|
// ...
|
|
// The only problem might be if the number rolls over
|
|
// and reaches the same value from the opposite side.
|
|
// This is still a valid socket value, but this time
|
|
// we have to check, which sockets have been used already.
|
|
if (sockval == m_SocketIDGenerator_init)
|
|
{
|
|
// Mark that since this point on the checks for
|
|
// whether the socket ID is in use must be done.
|
|
m_SocketIDGenerator_init = 0;
|
|
}
|
|
|
|
// This is when all socket numbers have been already used once.
|
|
// This may happen after many years of running an application
|
|
// constantly when the connection breaks and gets restored often.
|
|
if (m_SocketIDGenerator_init == 0)
|
|
{
|
|
int startval = sockval;
|
|
for (;;) // Roll until an unused value is found
|
|
{
|
|
enterCS(m_GlobControlLock);
|
|
const bool exists =
|
|
#if ENABLE_BONDING
|
|
for_group
|
|
? m_Groups.count(sockval | SRTGROUP_MASK)
|
|
:
|
|
#endif
|
|
m_Sockets.count(sockval);
|
|
leaveCS(m_GlobControlLock);
|
|
|
|
if (exists)
|
|
{
|
|
// The socket value is in use.
|
|
--sockval;
|
|
if (sockval <= 0)
|
|
sockval = MAX_SOCKET_VAL;
|
|
|
|
// Before continuing, check if we haven't rolled back to start again
|
|
// This is virtually impossible, so just make an RTI error.
|
|
if (sockval == startval)
|
|
{
|
|
// Of course, we don't lack memory, but actually this is so impossible
|
|
// that a complete memory extinction is much more possible than this.
|
|
// So treat this rather as a formal fallback for something that "should
|
|
// never happen". This should make the socket creation functions, from
|
|
// socket_create and accept, return this error.
|
|
|
|
m_SocketIDGenerator = sockval + 1; // so that any next call will cause the same error
|
|
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
|
|
// try again, if this is a free socket
|
|
continue;
|
|
}
|
|
|
|
// No socket found, this ID is free to use
|
|
m_SocketIDGenerator = sockval;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_SocketIDGenerator = sockval;
|
|
}
|
|
|
|
// The socket value counter remains with the value rolled
|
|
// without the group bit set; only the returned value may have
|
|
// the group bit set.
|
|
|
|
if (for_group)
|
|
sockval = m_SocketIDGenerator | SRTGROUP_MASK;
|
|
else
|
|
sockval = m_SocketIDGenerator;
|
|
|
|
LOGC(smlog.Debug, log << "generateSocketID: " << (for_group ? "(group)" : "") << ": @" << sockval);
|
|
|
|
return sockval;
|
|
}
|
|
|
|
SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps)
|
|
{
|
|
// XXX consider using some replacement of std::unique_ptr
|
|
// so that exceptions will clean up the object without the
|
|
// need for a dedicated code.
|
|
CUDTSocket* ns = NULL;
|
|
|
|
try
|
|
{
|
|
ns = new CUDTSocket;
|
|
}
|
|
catch (...)
|
|
{
|
|
delete ns;
|
|
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
ns->m_SocketID = generateSocketID();
|
|
}
|
|
catch (...)
|
|
{
|
|
delete ns;
|
|
throw;
|
|
}
|
|
ns->m_Status = SRTS_INIT;
|
|
ns->m_ListenSocket = 0;
|
|
ns->core().m_SocketID = ns->m_SocketID;
|
|
ns->core().m_pCache = m_pCache;
|
|
|
|
try
|
|
{
|
|
HLOGC(smlog.Debug, log << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID);
|
|
|
|
// protect the m_Sockets structure.
|
|
ScopedLock cs(m_GlobControlLock);
|
|
m_Sockets[ns->m_SocketID] = ns;
|
|
}
|
|
catch (...)
|
|
{
|
|
// failure and rollback
|
|
delete ns;
|
|
ns = NULL;
|
|
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
|
|
if (pps)
|
|
*pps = ns;
|
|
|
|
return ns->m_SocketID;
|
|
}
|
|
|
|
int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
|
|
const sockaddr_any& peer,
|
|
const CPacket& hspkt,
|
|
CHandShake& w_hs,
|
|
int& w_error,
|
|
CUDT*& w_acpu)
|
|
{
|
|
CUDTSocket* ns = NULL;
|
|
w_acpu = NULL;
|
|
|
|
w_error = SRT_REJ_IPE;
|
|
|
|
// Can't manage this error through an exception because this is
|
|
// running in the listener loop.
|
|
CUDTSocket* ls = locateSocket(listen);
|
|
if (!ls)
|
|
{
|
|
LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listen << " which DOES NOT EXIST.");
|
|
return -1;
|
|
}
|
|
|
|
HLOGC(cnlog.Debug,
|
|
log << "newConnection: creating new socket after listener @" << listen
|
|
<< " contacted with backlog=" << ls->m_uiBackLog);
|
|
|
|
// if this connection has already been processed
|
|
if ((ns = locatePeer(peer, w_hs.m_iID, w_hs.m_iISN)) != NULL)
|
|
{
|
|
if (ns->core().m_bBroken)
|
|
{
|
|
// last connection from the "peer" address has been broken
|
|
ns->setClosed();
|
|
|
|
ScopedLock acceptcg(ls->m_AcceptLock);
|
|
ls->m_QueuedSockets.erase(ns->m_SocketID);
|
|
}
|
|
else
|
|
{
|
|
// connection already exist, this is a repeated connection request
|
|
// respond with existing HS information
|
|
HLOGC(cnlog.Debug, log << "newConnection: located a WORKING peer @" << w_hs.m_iID << " - ADAPTING.");
|
|
|
|
w_hs.m_iISN = ns->core().m_iISN;
|
|
w_hs.m_iMSS = ns->core().MSS();
|
|
w_hs.m_iFlightFlagSize = ns->core().m_config.iFlightFlagSize;
|
|
w_hs.m_iReqType = URQ_CONCLUSION;
|
|
w_hs.m_iID = ns->m_SocketID;
|
|
|
|
// Report the original UDT because it will be
|
|
// required to complete the HS data for conclusion response.
|
|
w_acpu = &ns->core();
|
|
|
|
return 0;
|
|
|
|
// except for this situation a new connection should be started
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(cnlog.Debug,
|
|
log << "newConnection: NOT located any peer @" << w_hs.m_iID << " - resuming with initial connection.");
|
|
}
|
|
|
|
// exceeding backlog, refuse the connection request
|
|
if (ls->m_QueuedSockets.size() >= ls->m_uiBackLog)
|
|
{
|
|
w_error = SRT_REJ_BACKLOG;
|
|
LOGC(cnlog.Note, log << "newConnection: listen backlog=" << ls->m_uiBackLog << " EXCEEDED");
|
|
return -1;
|
|
}
|
|
|
|
try
|
|
{
|
|
ns = new CUDTSocket(*ls);
|
|
// No need to check the peer, this is the address from which the request has come.
|
|
ns->m_PeerAddr = peer;
|
|
}
|
|
catch (...)
|
|
{
|
|
w_error = SRT_REJ_RESOURCE;
|
|
delete ns;
|
|
LOGC(cnlog.Error, log << "IPE: newConnection: unexpected exception (probably std::bad_alloc)");
|
|
return -1;
|
|
}
|
|
|
|
ns->core().m_RejectReason = SRT_REJ_UNKNOWN; // pre-set a universal value
|
|
|
|
try
|
|
{
|
|
ns->m_SocketID = generateSocketID();
|
|
}
|
|
catch (const CUDTException&)
|
|
{
|
|
LOGC(cnlog.Fatal, log << "newConnection: IPE: all sockets occupied? Last gen=" << m_SocketIDGenerator);
|
|
// generateSocketID throws exception, which can be naturally handled
|
|
// when the call is derived from the API call, but here it's called
|
|
// internally in response to receiving a handshake. It must be handled
|
|
// here and turned into an erroneous return value.
|
|
delete ns;
|
|
return -1;
|
|
}
|
|
|
|
ns->m_ListenSocket = listen;
|
|
ns->core().m_SocketID = ns->m_SocketID;
|
|
ns->m_PeerID = w_hs.m_iID;
|
|
ns->m_iISN = w_hs.m_iISN;
|
|
|
|
HLOGC(cnlog.Debug,
|
|
log << "newConnection: DATA: lsnid=" << listen << " id=" << ns->core().m_SocketID
|
|
<< " peerid=" << ns->core().m_PeerID << " ISN=" << ns->m_iISN);
|
|
|
|
int error = 0;
|
|
bool should_submit_to_accept = true;
|
|
|
|
// Set the error code for all prospective problems below.
|
|
// It won't be interpreted when result was successful.
|
|
w_error = SRT_REJ_RESOURCE;
|
|
|
|
// These can throw exception only when the memory allocation failed.
|
|
// CUDT::connect() translates exception into CUDTException.
|
|
// CUDT::open() may only throw original std::bad_alloc from new.
|
|
// This is only to make the library extra safe (when your machine lacks
|
|
// memory, it will continue to work, but fail to accept connection).
|
|
|
|
try
|
|
{
|
|
// This assignment must happen b4 the call to CUDT::connect() because
|
|
// this call causes sending the SRT Handshake through this socket.
|
|
// Without this mapping the socket cannot be found and therefore
|
|
// the SRT Handshake message would fail.
|
|
HLOGC(cnlog.Debug, log <<
|
|
"newConnection: incoming " << peer.str() << ", mapping socket " << ns->m_SocketID);
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
m_Sockets[ns->m_SocketID] = ns;
|
|
}
|
|
|
|
if (ls->core().m_cbAcceptHook)
|
|
{
|
|
if (!ls->core().runAcceptHook(&ns->core(), peer.get(), w_hs, hspkt))
|
|
{
|
|
w_error = ns->core().m_RejectReason;
|
|
|
|
error = 1;
|
|
goto ERR_ROLLBACK;
|
|
}
|
|
}
|
|
|
|
// bind to the same addr of listening socket
|
|
ns->core().open();
|
|
if (!updateListenerMux(ns, ls))
|
|
{
|
|
// This is highly unlikely if not impossible, but there's
|
|
// a theoretical runtime chance of failure so it should be
|
|
// handled
|
|
ns->core().m_RejectReason = SRT_REJ_IPE;
|
|
throw false; // let it jump directly into the omni exception handler
|
|
}
|
|
|
|
ns->core().acceptAndRespond(ls->m_SelfAddr, peer, hspkt, (w_hs));
|
|
}
|
|
catch (...)
|
|
{
|
|
// Extract the error that was set in this new failed entity.
|
|
w_error = ns->core().m_RejectReason;
|
|
error = 1;
|
|
goto ERR_ROLLBACK;
|
|
}
|
|
|
|
ns->m_Status = SRTS_CONNECTED;
|
|
|
|
// copy address information of local node
|
|
// Precisely, what happens here is:
|
|
// - Get the IP address and port from the system database
|
|
ns->core().m_pSndQueue->m_pChannel->getSockAddr((ns->m_SelfAddr));
|
|
// - OVERWRITE just the IP address itself by a value taken from piSelfIP
|
|
// (the family is used exactly as the one taken from what has been returned
|
|
// by getsockaddr)
|
|
CIPAddress::pton((ns->m_SelfAddr), ns->core().m_piSelfIP, peer);
|
|
|
|
{
|
|
// protect the m_PeerRec structure (and group existence)
|
|
ScopedLock glock(m_GlobControlLock);
|
|
try
|
|
{
|
|
HLOGC(cnlog.Debug, log << "newConnection: mapping peer " << ns->m_PeerID
|
|
<< " to that socket (" << ns->m_SocketID << ")");
|
|
m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOGC(cnlog.Error, log << "newConnection: error when mapping peer!");
|
|
error = 2;
|
|
}
|
|
|
|
// The access to m_GroupOf should be also protected, as the group
|
|
// could be requested deletion in the meantime. This will hold any possible
|
|
// removal from group and resetting m_GroupOf field.
|
|
|
|
#if ENABLE_BONDING
|
|
if (ns->m_GroupOf)
|
|
{
|
|
// XXX this might require another check of group type.
|
|
// For redundancy group, at least, update the status in the group
|
|
CUDTGroup* g = ns->m_GroupOf;
|
|
ScopedLock grlock(g->m_GroupLock);
|
|
if (g->m_bClosing)
|
|
{
|
|
error = 1; // "INTERNAL REJECTION"
|
|
goto ERR_ROLLBACK;
|
|
}
|
|
|
|
// Check if this is the first socket in the group.
|
|
// If so, give it up to accept, otherwise just do nothing
|
|
// The client will be informed about the newly added connection at the
|
|
// first moment when attempting to get the group status.
|
|
for (CUDTGroup::gli_t gi = g->m_Group.begin(); gi != g->m_Group.end(); ++gi)
|
|
{
|
|
if (gi->laststatus == SRTS_CONNECTED)
|
|
{
|
|
HLOGC(cnlog.Debug,
|
|
log << "Found another connected socket in the group: $" << gi->id
|
|
<< " - socket will be NOT given up for accepting");
|
|
should_submit_to_accept = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update the status in the group so that the next
|
|
// operation can include the socket in the group operation.
|
|
CUDTGroup::SocketData* gm = ns->m_GroupMemberData;
|
|
|
|
HLOGC(cnlog.Debug,
|
|
log << "newConnection(GROUP): Socket @" << ns->m_SocketID << " BELONGS TO $" << g->id() << " - will "
|
|
<< (should_submit_to_accept ? "" : "NOT ") << "report in accept");
|
|
gm->sndstate = SRT_GST_IDLE;
|
|
gm->rcvstate = SRT_GST_IDLE;
|
|
gm->laststatus = SRTS_CONNECTED;
|
|
|
|
if (!g->m_bConnected)
|
|
{
|
|
HLOGC(cnlog.Debug, log << "newConnection(GROUP): First socket connected, SETTING GROUP CONNECTED");
|
|
g->m_bConnected = true;
|
|
}
|
|
|
|
// XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily,
|
|
// but groupwise connections could be accepted from multiple listeners for the same group!
|
|
// m_listener MUST BE A CONTAINER, NOT POINTER!!!
|
|
// ALSO: Maybe checking "the same listener" is not necessary as subscruption may be done
|
|
// multiple times anyway?
|
|
if (!g->m_listener)
|
|
{
|
|
// Newly created group from the listener, which hasn't yet
|
|
// the listener set.
|
|
g->m_listener = ls;
|
|
|
|
// Listen on both first connected socket and continued sockets.
|
|
// This might help with jump-over situations, and in regular continued
|
|
// sockets the IN event won't be reported anyway.
|
|
int listener_modes = SRT_EPOLL_ACCEPT | SRT_EPOLL_UPDATE;
|
|
epoll_add_usock_INTERNAL(g->m_RcvEID, ls, &listener_modes);
|
|
|
|
// This listening should be done always when a first connected socket
|
|
// appears as accepted off the listener. This is for the sake of swait() calls
|
|
// inside the group receiving and sending functions so that they get
|
|
// interrupted when a new socket is connected.
|
|
}
|
|
|
|
// Add also per-direction subscription for the about-to-be-accepted socket.
|
|
// Both first accepted socket that makes the group-accept and every next
|
|
// socket that adds a new link.
|
|
int read_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR;
|
|
int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
|
|
epoll_add_usock_INTERNAL(g->m_RcvEID, ns, &read_modes);
|
|
epoll_add_usock_INTERNAL(g->m_SndEID, ns, &write_modes);
|
|
|
|
// With app reader, do not set groupPacketArrival (block the
|
|
// provider array feature completely for now).
|
|
|
|
/* SETUP HERE IF NEEDED
|
|
ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival);
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
HLOGC(cnlog.Debug, log << "newConnection: Socket @" << ns->m_SocketID << " is not in a group");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (should_submit_to_accept)
|
|
{
|
|
enterCS(ls->m_AcceptLock);
|
|
try
|
|
{
|
|
ls->m_QueuedSockets.insert(ns->m_SocketID);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOGC(cnlog.Error, log << "newConnection: error when queuing socket!");
|
|
error = 3;
|
|
}
|
|
leaveCS(ls->m_AcceptLock);
|
|
|
|
HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->m_SocketID << " submitted for acceptance");
|
|
// acknowledge users waiting for new connections on the listening socket
|
|
m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, true);
|
|
|
|
CGlobEvent::triggerEvent();
|
|
|
|
// XXX the exact value of 'error' is ignored
|
|
if (error > 0)
|
|
{
|
|
goto ERR_ROLLBACK;
|
|
}
|
|
|
|
// wake up a waiting accept() call
|
|
CSync::lock_notify_one(ls->m_AcceptCond, ls->m_AcceptLock);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(cnlog.Debug,
|
|
log << "ACCEPT: new socket @" << ns->m_SocketID
|
|
<< " NOT submitted to acceptance, another socket in the group is already connected");
|
|
|
|
// acknowledge INTERNAL users waiting for new connections on the listening socket
|
|
// that are reported when a new socket is connected within an already connected group.
|
|
m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_UPDATE, true);
|
|
CGlobEvent::triggerEvent();
|
|
}
|
|
|
|
ERR_ROLLBACK:
|
|
// XXX the exact value of 'error' is ignored
|
|
if (error > 0)
|
|
{
|
|
#if ENABLE_LOGGING
|
|
static const char* why[] = {
|
|
"UNKNOWN ERROR", "INTERNAL REJECTION", "IPE when mapping a socket", "IPE when inserting a socket"};
|
|
LOGC(cnlog.Warn,
|
|
log << CONID(ns->m_SocketID) << "newConnection: connection rejected due to: " << why[error] << " - "
|
|
<< RequestTypeStr(URQFailure(w_error)));
|
|
#endif
|
|
|
|
SRTSOCKET id = ns->m_SocketID;
|
|
ns->core().closeInternal();
|
|
ns->setClosed();
|
|
|
|
// The mapped socket should be now unmapped to preserve the situation that
|
|
// was in the original UDT code.
|
|
// In SRT additionally the acceptAndRespond() function (it was called probably
|
|
// connect() in UDT code) may fail, in which case this socket should not be
|
|
// further processed and should be removed.
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
|
|
#if ENABLE_BONDING
|
|
if (ns->m_GroupOf)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << ns->m_SocketID << " IS MEMBER OF $" << ns->m_GroupOf->id()
|
|
<< " - REMOVING FROM GROUP");
|
|
ns->removeFromGroup(true);
|
|
}
|
|
#endif
|
|
m_Sockets.erase(id);
|
|
m_ClosedSockets[id] = ns;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// static forwarder
|
|
int srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq)
|
|
{
|
|
return uglobal().installAcceptHook(lsn, hook, opaq);
|
|
}
|
|
|
|
int srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq)
|
|
{
|
|
try
|
|
{
|
|
CUDTSocket* s = locateSocket(lsn, ERH_THROW);
|
|
s->core().installAcceptHook(hook, opaq);
|
|
}
|
|
catch (CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return SRT_ERROR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq)
|
|
{
|
|
return uglobal().installConnectHook(lsn, hook, opaq);
|
|
}
|
|
|
|
int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq)
|
|
{
|
|
try
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
GroupKeeper k(*this, u, ERH_THROW);
|
|
k.group->installConnectHook(hook, opaq);
|
|
return 0;
|
|
}
|
|
#endif
|
|
CUDTSocket* s = locateSocket(u, ERH_THROW);
|
|
s->core().installConnectHook(hook, opaq);
|
|
}
|
|
catch (CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return SRT_ERROR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u)
|
|
{
|
|
// protects the m_Sockets structure
|
|
ScopedLock cg(m_GlobControlLock);
|
|
|
|
sockets_t::const_iterator i = m_Sockets.find(u);
|
|
|
|
if (i == m_Sockets.end())
|
|
{
|
|
if (m_ClosedSockets.find(u) != m_ClosedSockets.end())
|
|
return SRTS_CLOSED;
|
|
|
|
return SRTS_NONEXIST;
|
|
}
|
|
return i->second->getStatus();
|
|
}
|
|
|
|
int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name)
|
|
{
|
|
ScopedLock cg(s->m_ControlLock);
|
|
|
|
// cannot bind a socket more than once
|
|
if (s->m_Status != SRTS_INIT)
|
|
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
|
|
|
|
if (s->core().m_config.iIpV6Only == -1 && name.family() == AF_INET6 && name.isany())
|
|
{
|
|
// V6ONLY option must be set explicitly if you want to bind to a wildcard address in IPv6
|
|
HLOGP(smlog.Error,
|
|
"bind: when binding to :: (IPv6 wildcard), SRTO_IPV6ONLY option must be set explicitly to 0 or 1");
|
|
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
s->core().open();
|
|
updateMux(s, name);
|
|
s->m_Status = SRTS_OPENED;
|
|
|
|
// copy address information of local node
|
|
s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock)
|
|
{
|
|
ScopedLock cg(s->m_ControlLock);
|
|
|
|
// cannot bind a socket more than once
|
|
if (s->m_Status != SRTS_INIT)
|
|
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
|
|
|
|
sockaddr_any name;
|
|
socklen_t namelen = sizeof name; // max of inet and inet6
|
|
|
|
// This will preset the sa_family as well; the namelen is given simply large
|
|
// enough for any family here.
|
|
if (::getsockname(udpsock, &name.sa, &namelen) == -1)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
|
|
// Successfully extracted, so update the size
|
|
name.len = namelen;
|
|
|
|
s->core().open();
|
|
updateMux(s, name, &udpsock);
|
|
s->m_Status = SRTS_OPENED;
|
|
|
|
// copy address information of local node
|
|
s->core().m_pSndQueue->m_pChannel->getSockAddr(s->m_SelfAddr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog)
|
|
{
|
|
if (backlog <= 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
// Don't search for the socket if it's already -1;
|
|
// this never is a valid socket.
|
|
if (u == UDT::INVALID_SOCK)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
if (!s)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
ScopedLock cg(s->m_ControlLock);
|
|
|
|
// NOTE: since now the socket is protected against simultaneous access.
|
|
// In the meantime the socket might have been closed, which means that
|
|
// it could have changed the state. It could be also set listen in another
|
|
// thread, so check it out.
|
|
|
|
// do nothing if the socket is already listening
|
|
if (s->m_Status == SRTS_LISTENING)
|
|
return 0;
|
|
|
|
// a socket can listen only if is in OPENED status
|
|
if (s->m_Status != SRTS_OPENED)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0);
|
|
|
|
// [[using assert(s->m_Status == OPENED)]];
|
|
|
|
// listen is not supported in rendezvous connection setup
|
|
if (s->core().m_config.bRendezvous)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0);
|
|
|
|
s->m_uiBackLog = backlog;
|
|
|
|
// [[using assert(s->m_Status == OPENED)]]; // (still, unchanged)
|
|
|
|
s->core().setListenState(); // propagates CUDTException,
|
|
// if thrown, remains in OPENED state if so.
|
|
s->m_Status = SRTS_LISTENING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut)
|
|
{
|
|
CEPollDesc* ed = 0;
|
|
int eid = m_EPoll.create(&ed);
|
|
|
|
// Destroy it at return - this function can be interrupted
|
|
// by an exception.
|
|
struct AtReturn
|
|
{
|
|
int eid;
|
|
CUDTUnited* that;
|
|
AtReturn(CUDTUnited* t, int e)
|
|
: eid(e)
|
|
, that(t)
|
|
{
|
|
}
|
|
~AtReturn() { that->m_EPoll.release(eid); }
|
|
} l_ar(this, eid);
|
|
|
|
// Subscribe all of listeners for accept
|
|
int events = SRT_EPOLL_ACCEPT;
|
|
|
|
for (int i = 0; i < lsize; ++i)
|
|
{
|
|
srt_epoll_add_usock(eid, listeners[i], &events);
|
|
}
|
|
|
|
CEPoll::fmap_t st;
|
|
m_EPoll.swait(*ed, (st), msTimeOut, true);
|
|
|
|
if (st.empty())
|
|
{
|
|
// Sanity check
|
|
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
|
|
}
|
|
|
|
// Theoretically we can have a situation that more than one
|
|
// listener is ready for accept. In this case simply get
|
|
// only the first found.
|
|
int lsn = st.begin()->first;
|
|
sockaddr_storage dummy;
|
|
int outlen = sizeof dummy;
|
|
return accept(lsn, ((sockaddr*)&dummy), (&outlen));
|
|
}
|
|
|
|
SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen)
|
|
{
|
|
if (pw_addr && !pw_addrlen)
|
|
{
|
|
LOGC(cnlog.Error, log << "srt_accept: provided address, but address length parameter is missing");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
CUDTSocket* ls = locateSocket(listen);
|
|
|
|
if (ls == NULL)
|
|
{
|
|
LOGC(cnlog.Error, log << "srt_accept: invalid listener socket ID value: " << listen);
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
}
|
|
|
|
// the "listen" socket must be in LISTENING status
|
|
if (ls->m_Status != SRTS_LISTENING)
|
|
{
|
|
LOGC(cnlog.Error, log << "srt_accept: socket @" << listen << " is not in listening state (forgot srt_listen?)");
|
|
throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0);
|
|
}
|
|
|
|
// no "accept" in rendezvous connection setup
|
|
if (ls->core().m_config.bRendezvous)
|
|
{
|
|
LOGC(cnlog.Fatal,
|
|
log << "CUDTUnited::accept: RENDEZVOUS flag passed through check in srt_listen when it set listen state");
|
|
// This problem should never happen because `srt_listen` function should have
|
|
// checked this situation before and not set listen state in result.
|
|
// Inform the user about the invalid state in the universal way.
|
|
throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0);
|
|
}
|
|
|
|
SRTSOCKET u = CUDT::INVALID_SOCK;
|
|
bool accepted = false;
|
|
|
|
// !!only one connection can be set up each time!!
|
|
while (!accepted)
|
|
{
|
|
UniqueLock accept_lock(ls->m_AcceptLock);
|
|
CSync accept_sync(ls->m_AcceptCond, accept_lock);
|
|
|
|
if ((ls->m_Status != SRTS_LISTENING) || ls->core().m_bBroken)
|
|
{
|
|
// This socket has been closed.
|
|
accepted = true;
|
|
}
|
|
else if (ls->m_QueuedSockets.size() > 0)
|
|
{
|
|
set<SRTSOCKET>::iterator b = ls->m_QueuedSockets.begin();
|
|
u = *b;
|
|
ls->m_QueuedSockets.erase(b);
|
|
accepted = true;
|
|
}
|
|
else if (!ls->core().m_config.bSynRecving)
|
|
{
|
|
accepted = true;
|
|
}
|
|
|
|
if (!accepted && (ls->m_Status == SRTS_LISTENING))
|
|
accept_sync.wait();
|
|
|
|
if (ls->m_QueuedSockets.empty())
|
|
m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, false);
|
|
}
|
|
|
|
if (u == CUDT::INVALID_SOCK)
|
|
{
|
|
// non-blocking receiving, no connection available
|
|
if (!ls->core().m_config.bSynRecving)
|
|
{
|
|
LOGC(cnlog.Error, log << "srt_accept: no pending connection available at the moment");
|
|
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
|
|
}
|
|
|
|
LOGC(cnlog.Error, log << "srt_accept: listener socket @" << listen << " is already closed");
|
|
// listening socket is closed
|
|
throw CUDTException(MJ_SETUP, MN_CLOSED, 0);
|
|
}
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
if (s == NULL)
|
|
{
|
|
LOGC(cnlog.Error, log << "srt_accept: pending connection has unexpectedly closed");
|
|
throw CUDTException(MJ_SETUP, MN_CLOSED, 0);
|
|
}
|
|
|
|
// Set properly the SRTO_GROUPCONNECT flag
|
|
s->core().m_config.iGroupConnect = 0;
|
|
|
|
// Check if LISTENER has the SRTO_GROUPCONNECT flag set,
|
|
// and the already accepted socket has successfully joined
|
|
// the mirror group. If so, RETURN THE GROUP ID, not the socket ID.
|
|
#if ENABLE_BONDING
|
|
if (ls->core().m_config.iGroupConnect == 1 && s->m_GroupOf)
|
|
{
|
|
// Put a lock to protect the group against accidental deletion
|
|
// in the meantime.
|
|
ScopedLock glock(m_GlobControlLock);
|
|
// Check again; it's unlikely to happen, but
|
|
// it's a theoretically possible scenario
|
|
if (s->m_GroupOf)
|
|
{
|
|
u = s->m_GroupOf->m_GroupID;
|
|
s->core().m_config.iGroupConnect = 1; // should be derived from ls, but make sure
|
|
|
|
// Mark the beginning of the connection at the moment
|
|
// when the group ID is returned to the app caller
|
|
s->m_GroupOf->m_stats.tsLastSampleTime = steady_clock::now();
|
|
}
|
|
else
|
|
{
|
|
LOGC(smlog.Error, log << "accept: IPE: socket's group deleted in the meantime of accept process???");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ScopedLock cg(s->m_ControlLock);
|
|
|
|
if (pw_addr != NULL && pw_addrlen != NULL)
|
|
{
|
|
// Check if the length of the buffer to fill the name in
|
|
// was large enough.
|
|
const int len = s->m_PeerAddr.size();
|
|
if (*pw_addrlen < len)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
memcpy((pw_addr), &s->m_PeerAddr, len);
|
|
*pw_addrlen = len;
|
|
}
|
|
|
|
return u;
|
|
}
|
|
|
|
int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen)
|
|
{
|
|
// Here both srcname and tarname must be specified
|
|
if (!srcname || !tarname || namelen < int(sizeof(sockaddr_in)))
|
|
{
|
|
LOGC(aclog.Error,
|
|
log << "connect(with source): invalid call: srcname=" << srcname << " tarname=" << tarname
|
|
<< " namelen=" << namelen);
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
sockaddr_any source_addr(srcname, namelen);
|
|
if (source_addr.len == 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
sockaddr_any target_addr(tarname, namelen);
|
|
if (target_addr.len == 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
#if ENABLE_BONDING
|
|
// Check affiliation of the socket. It's now allowed for it to be
|
|
// a group or socket. For a group, add automatically a socket to
|
|
// the group.
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
GroupKeeper k(*this, u, ERH_THROW);
|
|
// Note: forced_isn is ignored when connecting a group.
|
|
// The group manages the ISN by itself ALWAYS, that is,
|
|
// it's generated anew for the very first socket, and then
|
|
// derived by all sockets in the group.
|
|
SRT_SOCKGROUPCONFIG gd[1] = {srt_prepare_endpoint(srcname, tarname, namelen)};
|
|
|
|
// When connecting to exactly one target, only this very target
|
|
// can be returned as a socket, so rewritten back array can be ignored.
|
|
return singleMemberConnect(k.group, gd);
|
|
}
|
|
#endif
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
if (s == NULL)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
// For a single socket, just do bind, then connect
|
|
bind(s, source_addr);
|
|
return connectIn(s, target_addr, SRT_SEQNO_NONE);
|
|
}
|
|
|
|
int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn)
|
|
{
|
|
if (!name || namelen < int(sizeof(sockaddr_in)))
|
|
{
|
|
LOGC(aclog.Error, log << "connect(): invalid call: name=" << name << " namelen=" << namelen);
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
sockaddr_any target_addr(name, namelen);
|
|
if (target_addr.len == 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
#if ENABLE_BONDING
|
|
// Check affiliation of the socket. It's now allowed for it to be
|
|
// a group or socket. For a group, add automatically a socket to
|
|
// the group.
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
GroupKeeper k(*this, u, ERH_THROW);
|
|
|
|
// Note: forced_isn is ignored when connecting a group.
|
|
// The group manages the ISN by itself ALWAYS, that is,
|
|
// it's generated anew for the very first socket, and then
|
|
// derived by all sockets in the group.
|
|
SRT_SOCKGROUPCONFIG gd[1] = {srt_prepare_endpoint(NULL, name, namelen)};
|
|
return singleMemberConnect(k.group, gd);
|
|
}
|
|
#endif
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
if (!s)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
return connectIn(s, target_addr, forced_isn);
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
int srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd)
|
|
{
|
|
int gstat = groupConnect(pg, gd, 1);
|
|
if (gstat == -1)
|
|
{
|
|
// We have only one element here, so refer to it.
|
|
// Sanity check
|
|
if (gd->errorcode == SRT_SUCCESS)
|
|
gd->errorcode = SRT_EINVPARAM;
|
|
|
|
CodeMajor mj = CodeMajor(gd->errorcode / 1000);
|
|
CodeMinor mn = CodeMinor(gd->errorcode % 1000);
|
|
|
|
return CUDT::APIError(mj, mn);
|
|
}
|
|
|
|
return gstat;
|
|
}
|
|
|
|
// [[using assert(pg->m_iBusy > 0)]]
|
|
int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize)
|
|
{
|
|
CUDTGroup& g = *pg;
|
|
SRT_ASSERT(g.m_iBusy > 0);
|
|
|
|
// Check and report errors on data brought in by srt_prepare_endpoint,
|
|
// as the latter function has no possibility to report errors.
|
|
for (int tii = 0; tii < arraysize; ++tii)
|
|
{
|
|
if (targets[tii].srcaddr.ss_family != targets[tii].peeraddr.ss_family)
|
|
{
|
|
LOGC(aclog.Error, log << "srt_connect/group: family differs on source and target address");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
|
|
if (targets[tii].weight > CUDT::MAX_WEIGHT)
|
|
{
|
|
LOGC(aclog.Error, log << "srt_connect/group: weight value must be between 0 and " << (+CUDT::MAX_WEIGHT));
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
}
|
|
}
|
|
|
|
// If the open state switched to OPENED, the blocking mode
|
|
// must make it wait for connecting it. Doing connect when the
|
|
// group is already OPENED returns immediately, regardless if the
|
|
// connection is going to later succeed or fail (this will be
|
|
// known in the group state information).
|
|
bool block_new_opened = !g.m_bOpened && g.m_bSynRecving;
|
|
const bool was_empty = g.groupEmpty();
|
|
|
|
// In case the group was retried connection, clear first all epoll readiness.
|
|
const int ncleared = m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_ERR, false);
|
|
if (was_empty || ncleared)
|
|
{
|
|
HLOGC(aclog.Debug,
|
|
log << "srt_connect/group: clearing IN/OUT because was_empty=" << was_empty
|
|
<< " || ncleared=" << ncleared);
|
|
// IN/OUT only in case when the group is empty, otherwise it would
|
|
// clear out correct readiness resulting from earlier calls.
|
|
// This also should happen if ERR flag was set, as IN and OUT could be set, too.
|
|
m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT, false);
|
|
}
|
|
SRTSOCKET retval = -1;
|
|
|
|
int eid = -1;
|
|
int connect_modes = SRT_EPOLL_CONNECT | SRT_EPOLL_ERR;
|
|
if (block_new_opened)
|
|
{
|
|
// Create this eid only to block-wait for the first
|
|
// connection.
|
|
eid = srt_epoll_create();
|
|
}
|
|
|
|
// Use private map to avoid searching in the
|
|
// overall map.
|
|
map<SRTSOCKET, CUDTSocket*> spawned;
|
|
|
|
HLOGC(aclog.Debug,
|
|
log << "groupConnect: will connect " << arraysize << " links and "
|
|
<< (block_new_opened ? "BLOCK until any is ready" : "leave the process in background"));
|
|
|
|
for (int tii = 0; tii < arraysize; ++tii)
|
|
{
|
|
sockaddr_any target_addr(targets[tii].peeraddr);
|
|
sockaddr_any source_addr(targets[tii].srcaddr);
|
|
SRTSOCKET& sid_rloc = targets[tii].id;
|
|
int& erc_rloc = targets[tii].errorcode;
|
|
erc_rloc = SRT_SUCCESS; // preinitialized
|
|
HLOGC(aclog.Debug, log << "groupConnect: taking on " << sockaddr_any(targets[tii].peeraddr).str());
|
|
|
|
CUDTSocket* ns = 0;
|
|
|
|
// NOTE: After calling newSocket, the socket is mapped into m_Sockets.
|
|
// It must be MANUALLY removed from this list in case we need it deleted.
|
|
SRTSOCKET sid = newSocket(&ns);
|
|
|
|
if (pg->m_cbConnectHook)
|
|
{
|
|
// Derive the connect hook by the socket, if set on the group
|
|
ns->core().m_cbConnectHook = pg->m_cbConnectHook;
|
|
}
|
|
|
|
SRT_SocketOptionObject* config = targets[tii].config;
|
|
|
|
// XXX Support non-blocking mode:
|
|
// If the group has nonblocking set for connect (SNDSYN),
|
|
// then it must set so on the socket. Then, the connection
|
|
// process is asynchronous. The socket appears first as
|
|
// GST_PENDING state, and only after the socket becomes
|
|
// connected does its status in the group turn into GST_IDLE.
|
|
|
|
// Set all options that were requested by the options set on a group
|
|
// prior to connecting.
|
|
string error_reason SRT_ATR_UNUSED;
|
|
try
|
|
{
|
|
for (size_t i = 0; i < g.m_config.size(); ++i)
|
|
{
|
|
HLOGC(aclog.Debug, log << "groupConnect: OPTION @" << sid << " #" << g.m_config[i].so);
|
|
error_reason = "setting group-derived option: #" + Sprint(g.m_config[i].so);
|
|
ns->core().setOpt(g.m_config[i].so, &g.m_config[i].value[0], (int)g.m_config[i].value.size());
|
|
}
|
|
|
|
// Do not try to set a user option if failed already.
|
|
if (config)
|
|
{
|
|
error_reason = "user option";
|
|
ns->core().applyMemberConfigObject(*config);
|
|
}
|
|
|
|
error_reason = "bound address";
|
|
// We got it. Bind the socket, if the source address was set
|
|
if (!source_addr.empty())
|
|
bind(ns, source_addr);
|
|
}
|
|
catch (CUDTException& e)
|
|
{
|
|
// Just notify the problem, but the loop must continue.
|
|
// Set the original error as reported.
|
|
targets[tii].errorcode = e.getErrorCode();
|
|
LOGC(aclog.Error, log << "srt_connect_group: failed to set " << error_reason);
|
|
}
|
|
catch (...)
|
|
{
|
|
// Set the general EINVPARAM - this error should never happen
|
|
LOGC(aclog.Error, log << "IPE: CUDT::setOpt reported unknown exception");
|
|
targets[tii].errorcode = SRT_EINVPARAM;
|
|
}
|
|
|
|
// Add socket to the group.
|
|
// Do it after setting all stored options, as some of them may
|
|
// influence some group data.
|
|
|
|
srt::groups::SocketData data = srt::groups::prepareSocketData(ns);
|
|
if (targets[tii].token != -1)
|
|
{
|
|
// Reuse the token, if specified by the caller
|
|
data.token = targets[tii].token;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise generate and write back the token
|
|
data.token = CUDTGroup::genToken();
|
|
targets[tii].token = data.token;
|
|
}
|
|
|
|
{
|
|
ScopedLock cs(m_GlobControlLock);
|
|
if (m_Sockets.count(sid) == 0)
|
|
{
|
|
HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " deleted in process");
|
|
// Someone deleted the socket in the meantime?
|
|
// Unlikely, but possible in theory.
|
|
// Don't delete anyhting - it's alreay done.
|
|
continue;
|
|
}
|
|
|
|
// There's nothing wrong with preparing the data first
|
|
// even if this happens for nothing. But now, under the lock
|
|
// and after checking that the socket still exists, check now
|
|
// if this succeeded, and then also if the group is still usable.
|
|
// The group will surely exist because it's set busy, until the
|
|
// end of this function. But it might be simultaneously requested closed.
|
|
bool proceed = true;
|
|
|
|
if (targets[tii].errorcode != SRT_SUCCESS)
|
|
{
|
|
HLOGC(aclog.Debug,
|
|
log << "srt_connect_group: not processing @" << sid << " due to error in setting options");
|
|
proceed = false;
|
|
}
|
|
|
|
if (g.m_bClosing)
|
|
{
|
|
HLOGC(aclog.Debug,
|
|
log << "srt_connect_group: not processing @" << sid << " due to CLOSED GROUP $" << g.m_GroupID);
|
|
proceed = false;
|
|
}
|
|
|
|
if (proceed)
|
|
{
|
|
CUDTGroup::SocketData* f = g.add(data);
|
|
ns->m_GroupMemberData = f;
|
|
ns->m_GroupOf = &g;
|
|
f->weight = targets[tii].weight;
|
|
HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " added to group $" << g.m_GroupID);
|
|
}
|
|
else
|
|
{
|
|
targets[tii].id = CUDT::INVALID_SOCK;
|
|
delete ns;
|
|
m_Sockets.erase(sid);
|
|
|
|
// If failed to set options, then do not continue
|
|
// neither with binding, nor with connecting.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// XXX This should be reenabled later, this should
|
|
// be probably still in use to exchange information about
|
|
// packets asymmetrically lost. But for no other purpose.
|
|
/*
|
|
ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival);
|
|
*/
|
|
|
|
int isn = g.currentSchedSequence();
|
|
|
|
// Set it the groupconnect option, as all in-group sockets should have.
|
|
ns->core().m_config.iGroupConnect = 1;
|
|
|
|
// Every group member will have always nonblocking
|
|
// (this implies also non-blocking connect/accept).
|
|
// The group facility functions will block when necessary
|
|
// using epoll_wait.
|
|
ns->core().m_config.bSynRecving = false;
|
|
ns->core().m_config.bSynSending = false;
|
|
|
|
HLOGC(aclog.Debug, log << "groupConnect: NOTIFIED AS PENDING @" << sid << " both read and write");
|
|
// If this socket is not to block the current connect process,
|
|
// it may still be needed for the further check if the redundant
|
|
// connection succeeded or failed and whether the new socket is
|
|
// ready to use or needs to be closed.
|
|
epoll_add_usock_INTERNAL(g.m_SndEID, ns, &connect_modes);
|
|
epoll_add_usock_INTERNAL(g.m_RcvEID, ns, &connect_modes);
|
|
|
|
// Adding a socket on which we need to block to BOTH these tracking EIDs
|
|
// and the blocker EID. We'll simply remove from them later all sockets that
|
|
// got connected state or were broken.
|
|
|
|
if (block_new_opened)
|
|
{
|
|
HLOGC(aclog.Debug, log << "groupConnect: WILL BLOCK on @" << sid << " until connected");
|
|
epoll_add_usock_INTERNAL(eid, ns, &connect_modes);
|
|
}
|
|
|
|
// And connect
|
|
try
|
|
{
|
|
HLOGC(aclog.Debug, log << "groupConnect: connecting a new socket with ISN=" << isn);
|
|
connectIn(ns, target_addr, isn);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
LOGC(aclog.Error,
|
|
log << "groupConnect: socket @" << sid << " in group " << pg->id() << " failed to connect");
|
|
// We know it does belong to a group.
|
|
// Remove it first because this involves a mutex, and we want
|
|
// to avoid locking more than one mutex at a time.
|
|
erc_rloc = e.getErrorCode();
|
|
targets[tii].errorcode = e.getErrorCode();
|
|
targets[tii].id = CUDT::INVALID_SOCK;
|
|
|
|
ScopedLock cl(m_GlobControlLock);
|
|
ns->removeFromGroup(false);
|
|
m_Sockets.erase(ns->m_SocketID);
|
|
// Intercept to delete the socket on failure.
|
|
delete ns;
|
|
continue;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOGC(aclog.Fatal, log << "groupConnect: IPE: UNKNOWN EXCEPTION from connectIn");
|
|
targets[tii].errorcode = SRT_ESYSOBJ;
|
|
targets[tii].id = CUDT::INVALID_SOCK;
|
|
ScopedLock cl(m_GlobControlLock);
|
|
ns->removeFromGroup(false);
|
|
m_Sockets.erase(ns->m_SocketID);
|
|
// Intercept to delete the socket on failure.
|
|
delete ns;
|
|
|
|
// Do not use original exception, it may crash off a C API.
|
|
throw CUDTException(MJ_SYSTEMRES, MN_OBJECT);
|
|
}
|
|
|
|
SRT_SOCKSTATUS st;
|
|
{
|
|
ScopedLock grd(ns->m_ControlLock);
|
|
st = ns->getStatus();
|
|
}
|
|
|
|
{
|
|
// NOTE: Not applying m_GlobControlLock because the group is now
|
|
// set busy, so it won't be deleted, even if it was requested to be closed.
|
|
ScopedLock grd(g.m_GroupLock);
|
|
|
|
if (!ns->m_GroupOf)
|
|
{
|
|
// The situation could get changed between the unlock and lock of m_GroupLock.
|
|
// This must be checked again.
|
|
// If a socket has been removed from group, it means that some other thread is
|
|
// currently trying to delete the socket. Therefore it doesn't have, and even shouldn't,
|
|
// be deleted here. Just exit with error report.
|
|
LOGC(aclog.Error, log << "groupConnect: self-created member socket deleted during process, SKIPPING.");
|
|
|
|
// Do not report the error from here, just ignore this socket.
|
|
continue;
|
|
}
|
|
|
|
// If m_GroupOf is not NULL, the m_IncludedIter is still valid.
|
|
CUDTGroup::SocketData* f = ns->m_GroupMemberData;
|
|
|
|
// Now under a group lock, we need to make sure the group isn't being closed
|
|
// in order not to add a socket to a dead group.
|
|
if (g.m_bClosing)
|
|
{
|
|
LOGC(aclog.Error, log << "groupConnect: group deleted while connecting; breaking the process");
|
|
|
|
// Set the status as pending so that the socket is taken care of later.
|
|
// Note that all earlier sockets that were processed in this loop were either
|
|
// set BROKEN or PENDING.
|
|
f->sndstate = SRT_GST_PENDING;
|
|
f->rcvstate = SRT_GST_PENDING;
|
|
retval = -1;
|
|
break;
|
|
}
|
|
|
|
HLOGC(aclog.Debug,
|
|
log << "groupConnect: @" << sid << " connection successful, setting group OPEN (was "
|
|
<< (g.m_bOpened ? "ALREADY" : "NOT") << "), will " << (block_new_opened ? "" : "NOT ")
|
|
<< "block the connect call, status:" << SockStatusStr(st));
|
|
|
|
// XXX OPEN OR CONNECTED?
|
|
// BLOCK IF NOT OPEN OR BLOCK IF NOT CONNECTED?
|
|
//
|
|
// What happens to blocking when there are 2 connections
|
|
// pending, about to be broken, and srt_connect() is called again?
|
|
// SHOULD BLOCK the latter, even though is OPEN.
|
|
// Or, OPEN should be removed from here and srt_connect(_group)
|
|
// should block always if the group doesn't have neither 1 conencted link
|
|
g.m_bOpened = true;
|
|
|
|
g.m_stats.tsLastSampleTime = steady_clock::now();
|
|
|
|
f->laststatus = st;
|
|
// Check the socket status and update it.
|
|
// Turn the group state of the socket to IDLE only if
|
|
// connection is established or in progress
|
|
f->agent = source_addr;
|
|
f->peer = target_addr;
|
|
|
|
if (st >= SRTS_BROKEN)
|
|
{
|
|
f->sndstate = SRT_GST_BROKEN;
|
|
f->rcvstate = SRT_GST_BROKEN;
|
|
epoll_remove_socket_INTERNAL(g.m_SndEID, ns);
|
|
epoll_remove_socket_INTERNAL(g.m_RcvEID, ns);
|
|
}
|
|
else
|
|
{
|
|
f->sndstate = SRT_GST_PENDING;
|
|
f->rcvstate = SRT_GST_PENDING;
|
|
spawned[sid] = ns;
|
|
|
|
sid_rloc = sid;
|
|
erc_rloc = 0;
|
|
retval = sid;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (retval == -1)
|
|
{
|
|
HLOGC(aclog.Debug, log << "groupConnect: none succeeded as background-spawn, exit with error");
|
|
block_new_opened = false; // Avoid executing further while loop
|
|
}
|
|
|
|
vector<SRTSOCKET> broken;
|
|
|
|
while (block_new_opened)
|
|
{
|
|
if (spawned.empty())
|
|
{
|
|
// All were removed due to errors.
|
|
retval = -1;
|
|
break;
|
|
}
|
|
HLOGC(aclog.Debug, log << "groupConnect: first connection, applying EPOLL WAITING.");
|
|
int len = (int)spawned.size();
|
|
vector<SRTSOCKET> ready(spawned.size());
|
|
const int estat = srt_epoll_wait(eid,
|
|
NULL,
|
|
NULL, // IN/ACCEPT
|
|
&ready[0],
|
|
&len, // OUT/CONNECT
|
|
-1, // indefinitely (FIXME Check if it needs to REGARD CONNECTION TIMEOUT!)
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
// Sanity check. Shouldn't happen if subs are in sync with spawned.
|
|
if (estat == -1)
|
|
{
|
|
#if ENABLE_LOGGING
|
|
CUDTException& x = CUDT::getlasterror();
|
|
if (x.getErrorCode() != SRT_EPOLLEMPTY)
|
|
{
|
|
LOGC(aclog.Error,
|
|
log << "groupConnect: srt_epoll_wait failed not because empty, unexpected IPE:"
|
|
<< x.getErrorMessage());
|
|
}
|
|
#endif
|
|
HLOGC(aclog.Debug, log << "groupConnect: srt_epoll_wait failed - breaking the wait loop");
|
|
retval = -1;
|
|
break;
|
|
}
|
|
|
|
// At the moment when you are going to work with real sockets,
|
|
// lock the groups so that no one messes up with something here
|
|
// in the meantime.
|
|
|
|
ScopedLock lock(*g.exp_groupLock());
|
|
|
|
// NOTE: UNDER m_GroupLock, NO API FUNCTION CALLS DARE TO HAPPEN BELOW!
|
|
|
|
// Check first if a socket wasn't closed in the meantime. It will be
|
|
// automatically removed from all EIDs, but there's no sense in keeping
|
|
// them in 'spawned' map.
|
|
for (map<SRTSOCKET, CUDTSocket*>::iterator y = spawned.begin(); y != spawned.end(); ++y)
|
|
{
|
|
SRTSOCKET sid = y->first;
|
|
if (y->second->getStatus() >= SRTS_BROKEN)
|
|
{
|
|
HLOGC(aclog.Debug,
|
|
log << "groupConnect: Socket @" << sid
|
|
<< " got BROKEN in the meantine during the check, remove from candidates");
|
|
// Remove from spawned and try again
|
|
broken.push_back(sid);
|
|
|
|
epoll_remove_socket_INTERNAL(eid, y->second);
|
|
epoll_remove_socket_INTERNAL(g.m_SndEID, y->second);
|
|
epoll_remove_socket_INTERNAL(g.m_RcvEID, y->second);
|
|
}
|
|
}
|
|
|
|
// Remove them outside the loop because this can't be done
|
|
// while iterating over the same container.
|
|
for (size_t i = 0; i < broken.size(); ++i)
|
|
spawned.erase(broken[i]);
|
|
|
|
// Check the sockets if they were reported due
|
|
// to have connected or due to have failed.
|
|
// Distill successful ones. If distilled nothing, return -1.
|
|
// If not all sockets were reported in this instance, repeat
|
|
// the call until you get information about all of them.
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
map<SRTSOCKET, CUDTSocket*>::iterator x = spawned.find(ready[i]);
|
|
if (x == spawned.end())
|
|
{
|
|
// Might be removed above - ignore it.
|
|
continue;
|
|
}
|
|
|
|
SRTSOCKET sid = x->first;
|
|
CUDTSocket* s = x->second;
|
|
|
|
// Check status. If failed, remove from spawned
|
|
// and try again.
|
|
SRT_SOCKSTATUS st = s->getStatus();
|
|
if (st >= SRTS_BROKEN)
|
|
{
|
|
HLOGC(aclog.Debug,
|
|
log << "groupConnect: Socket @" << sid
|
|
<< " got BROKEN during background connect, remove & TRY AGAIN");
|
|
// Remove from spawned and try again
|
|
if (spawned.erase(sid))
|
|
broken.push_back(sid);
|
|
|
|
epoll_remove_socket_INTERNAL(eid, s);
|
|
epoll_remove_socket_INTERNAL(g.m_SndEID, s);
|
|
epoll_remove_socket_INTERNAL(g.m_RcvEID, s);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (st == SRTS_CONNECTED)
|
|
{
|
|
HLOGC(aclog.Debug,
|
|
log << "groupConnect: Socket @" << sid << " got CONNECTED as first in the group - reporting");
|
|
retval = sid;
|
|
g.m_bConnected = true;
|
|
block_new_opened = false; // Interrupt also rolling epoll (outer loop)
|
|
|
|
// Remove this socket from SND EID because it doesn't need to
|
|
// be connection-tracked anymore. Don't remove from the RCV EID
|
|
// however because RCV procedure relies on epoll also for reading
|
|
// and when found this socket connected it will "upgrade" it to
|
|
// read-ready tracking only.
|
|
epoll_remove_socket_INTERNAL(g.m_SndEID, s);
|
|
break;
|
|
}
|
|
|
|
// Spurious?
|
|
HLOGC(aclog.Debug,
|
|
log << "groupConnect: Socket @" << sid << " got spurious wakeup in " << SockStatusStr(st)
|
|
<< " TRY AGAIN");
|
|
}
|
|
// END of m_GroupLock CS - you can safely use API functions now.
|
|
}
|
|
// Finished, delete epoll.
|
|
if (eid != -1)
|
|
{
|
|
HLOGC(aclog.Debug, log << "connect FIRST IN THE GROUP finished, removing E" << eid);
|
|
srt_epoll_release(eid);
|
|
}
|
|
|
|
for (vector<SRTSOCKET>::iterator b = broken.begin(); b != broken.end(); ++b)
|
|
{
|
|
CUDTSocket* s = locateSocket(*b, ERH_RETURN);
|
|
if (!s)
|
|
continue;
|
|
|
|
// This will also automatically remove it from the group and all eids
|
|
close(s);
|
|
}
|
|
|
|
// There's no possibility to report a problem on every connection
|
|
// separately in case when every single connection has failed. What
|
|
// is more interesting, it's only a matter of luck that all connections
|
|
// fail at exactly the same time. OTOH if all are to fail, this
|
|
// function will still be polling sockets to determine the last man
|
|
// standing. Each one could, however, break by a different reason,
|
|
// for example, one by timeout, another by wrong passphrase. Check
|
|
// the `errorcode` field to determine the reaon for particular link.
|
|
if (retval == -1)
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
|
|
return retval;
|
|
}
|
|
#endif
|
|
|
|
int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn)
|
|
{
|
|
ScopedLock cg(s->m_ControlLock);
|
|
// a socket can "connect" only if it is in the following states:
|
|
// - OPENED: assume the socket binding parameters are configured
|
|
// - INIT: configure binding parameters here
|
|
// - any other (meaning, already connected): report error
|
|
|
|
if (s->m_Status == SRTS_INIT)
|
|
{
|
|
if (s->core().m_config.bRendezvous)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0);
|
|
|
|
// If bind() was done first on this socket, then the
|
|
// socket will not perform this step. This actually does the
|
|
// same thing as bind() does, just with empty address so that
|
|
// the binding parameters are autoselected.
|
|
|
|
s->core().open();
|
|
sockaddr_any autoselect_sa(target_addr.family());
|
|
// This will create such a sockaddr_any that
|
|
// will return true from empty().
|
|
updateMux(s, autoselect_sa); // <<---- updateMux
|
|
// -> C(Snd|Rcv)Queue::init
|
|
// -> pthread_create(...C(Snd|Rcv)Queue::worker...)
|
|
s->m_Status = SRTS_OPENED;
|
|
}
|
|
else
|
|
{
|
|
if (s->m_Status != SRTS_OPENED)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
// status = SRTS_OPENED, so family should be known already.
|
|
if (target_addr.family() != s->m_SelfAddr.family())
|
|
{
|
|
LOGP(cnlog.Error, "srt_connect: socket is bound to a different family than target address");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
}
|
|
|
|
// connect_complete() may be called before connect() returns.
|
|
// So we need to update the status before connect() is called,
|
|
// otherwise the status may be overwritten with wrong value
|
|
// (CONNECTED vs. CONNECTING).
|
|
s->m_Status = SRTS_CONNECTING;
|
|
|
|
/*
|
|
* In blocking mode, connect can block for up to 30 seconds for
|
|
* rendez-vous mode. Holding the s->m_ControlLock prevent close
|
|
* from cancelling the connect
|
|
*/
|
|
try
|
|
{
|
|
// record peer address
|
|
s->m_PeerAddr = target_addr;
|
|
s->core().startConnect(target_addr, forced_isn);
|
|
}
|
|
catch (const CUDTException&) // Interceptor, just to change the state.
|
|
{
|
|
s->m_Status = SRTS_OPENED;
|
|
throw;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int srt::CUDTUnited::close(const SRTSOCKET u)
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
GroupKeeper k(*this, u, ERH_THROW);
|
|
k.group->close();
|
|
deleteGroup(k.group);
|
|
return 0;
|
|
}
|
|
#endif
|
|
CUDTSocket* s = locateSocket(u);
|
|
if (!s)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
return close(s);
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
void srt::CUDTUnited::deleteGroup(CUDTGroup* g)
|
|
{
|
|
using srt_logging::gmlog;
|
|
|
|
srt::sync::ScopedLock cg(m_GlobControlLock);
|
|
return deleteGroup_LOCKED(g);
|
|
}
|
|
|
|
// [[using locked(m_GlobControlLock)]]
|
|
void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g)
|
|
{
|
|
SRT_ASSERT(g->groupEmpty());
|
|
|
|
// After that the group is no longer findable by GroupKeeper
|
|
m_Groups.erase(g->m_GroupID);
|
|
m_ClosedGroups[g->m_GroupID] = g;
|
|
|
|
// Paranoid check: since the group is in m_ClosedGroups
|
|
// it may potentially be deleted. Make sure no socket points
|
|
// to it. Actually all sockets should have been already removed
|
|
// from the group container, so if any does, it's invalid.
|
|
for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i)
|
|
{
|
|
CUDTSocket* s = i->second;
|
|
if (s->m_GroupOf == g)
|
|
{
|
|
HLOGC(smlog.Debug, log << "deleteGroup: IPE: existing @" << s->m_SocketID << " points to a dead group!");
|
|
s->m_GroupOf = NULL;
|
|
s->m_GroupMemberData = NULL;
|
|
}
|
|
}
|
|
|
|
// Just in case, do it in closed sockets, too, although this should be
|
|
// always done before moving to it.
|
|
for (sockets_t::iterator i = m_ClosedSockets.begin(); i != m_ClosedSockets.end(); ++i)
|
|
{
|
|
CUDTSocket* s = i->second;
|
|
if (s->m_GroupOf == g)
|
|
{
|
|
HLOGC(smlog.Debug, log << "deleteGroup: IPE: closed @" << s->m_SocketID << " points to a dead group!");
|
|
s->m_GroupOf = NULL;
|
|
s->m_GroupMemberData = NULL;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int srt::CUDTUnited::close(CUDTSocket* s)
|
|
{
|
|
HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSE. Acquiring control lock");
|
|
ScopedLock socket_cg(s->m_ControlLock);
|
|
HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSING (removing from listening, closing CUDT)");
|
|
|
|
const bool synch_close_snd = s->core().m_config.bSynSending;
|
|
|
|
SRTSOCKET u = s->m_SocketID;
|
|
|
|
if (s->m_Status == SRTS_LISTENING)
|
|
{
|
|
if (s->core().m_bBroken)
|
|
return 0;
|
|
|
|
s->m_tsClosureTimeStamp = steady_clock::now();
|
|
s->core().m_bBroken = true;
|
|
|
|
// Change towards original UDT:
|
|
// Leave all the closing activities for garbageCollect to happen,
|
|
// however remove the listener from the RcvQueue IMMEDIATELY.
|
|
// Even though garbageCollect would eventually remove the listener
|
|
// as well, there would be some time interval between now and the
|
|
// moment when it's done, and during this time the application will
|
|
// be unable to bind to this port that the about-to-delete listener
|
|
// is currently occupying (due to blocked slot in the RcvQueue).
|
|
|
|
HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSING (removing listener immediately)");
|
|
s->core().notListening();
|
|
s->m_Status = SRTS_CLOSING;
|
|
|
|
// broadcast all "accept" waiting
|
|
CSync::lock_notify_all(s->m_AcceptCond, s->m_AcceptLock);
|
|
}
|
|
else
|
|
{
|
|
s->m_Status = SRTS_CLOSING;
|
|
// Note: this call may be done on a socket that hasn't finished
|
|
// sending all packets scheduled for sending, which means, this call
|
|
// may block INDEFINITELY. As long as it's acceptable to block the
|
|
// call to srt_close(), and all functions in all threads where this
|
|
// very socket is used, this shall not block the central database.
|
|
s->core().closeInternal();
|
|
|
|
// synchronize with garbage collection.
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << u << "U::close done. GLOBAL CLOSE: " << s->core().CONID()
|
|
<< "Acquiring GLOBAL control lock");
|
|
ScopedLock manager_cg(m_GlobControlLock);
|
|
// since "s" is located before m_GlobControlLock, locate it again in case
|
|
// it became invalid
|
|
// XXX This is very weird; if we state that the CUDTSocket object
|
|
// could not be deleted between locks, then definitely it couldn't
|
|
// also change the pointer value. There's no other reason for getting
|
|
// this iterator but to obtain the 's' pointer, which is impossible to
|
|
// be different than previous 's' (m_Sockets is a map that stores pointers
|
|
// transparently). This iterator isn't even later used to delete the socket
|
|
// from the container, though it would be more efficient.
|
|
// FURTHER RESEARCH REQUIRED.
|
|
sockets_t::iterator i = m_Sockets.find(u);
|
|
if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED))
|
|
{
|
|
HLOGC(smlog.Debug, log << "@" << u << "U::close: NOT AN ACTIVE SOCKET, returning.");
|
|
return 0;
|
|
}
|
|
s = i->second;
|
|
s->setClosed();
|
|
|
|
#if ENABLE_BONDING
|
|
if (s->m_GroupOf)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP");
|
|
s->removeFromGroup(true);
|
|
}
|
|
#endif
|
|
|
|
m_Sockets.erase(s->m_SocketID);
|
|
m_ClosedSockets[s->m_SocketID] = s;
|
|
HLOGC(smlog.Debug, log << "@" << u << "U::close: Socket MOVED TO CLOSED for collecting later.");
|
|
|
|
CGlobEvent::triggerEvent();
|
|
}
|
|
|
|
HLOGC(smlog.Debug, log << "@" << u << ": GLOBAL: CLOSING DONE");
|
|
|
|
// Check if the ID is still in closed sockets before you access it
|
|
// (the last triggerEvent could have deleted it).
|
|
if (synch_close_snd)
|
|
{
|
|
#if SRT_ENABLE_CLOSE_SYNCH
|
|
|
|
HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sync-waiting for releasing sender resources...");
|
|
for (;;)
|
|
{
|
|
CSndBuffer* sb = s->core().m_pSndBuffer;
|
|
|
|
// Disconnected from buffer - nothing more to check.
|
|
if (!sb)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << u << " GLOBAL CLOSING: sending buffer disconnected. Allowed to close.");
|
|
break;
|
|
}
|
|
|
|
// Sender buffer empty
|
|
if (sb->getCurrBufSize() == 0)
|
|
{
|
|
HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sending buffer depleted. Allowed to close.");
|
|
break;
|
|
}
|
|
|
|
// Ok, now you are keeping GC thread hands off the internal data.
|
|
// You can check then if it has already deleted the socket or not.
|
|
// The socket is either in m_ClosedSockets or is already gone.
|
|
|
|
// Done the other way, but still done. You can stop waiting.
|
|
bool isgone = false;
|
|
{
|
|
ScopedLock manager_cg(m_GlobControlLock);
|
|
isgone = m_ClosedSockets.count(u) == 0;
|
|
}
|
|
if (!isgone)
|
|
{
|
|
isgone = !s->core().m_bOpened;
|
|
}
|
|
if (isgone)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << u << " GLOBAL CLOSING: ... gone in the meantime, whatever. Exiting close().");
|
|
break;
|
|
}
|
|
|
|
HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: ... still waiting for any update.");
|
|
// How to handle a possible error here?
|
|
CGlobEvent::waitForEvent();
|
|
|
|
// Continue waiting in case when an event happened or 1s waiting time passed for checkpoint.
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
This code is PUT ASIDE for now.
|
|
Most likely this will be never required.
|
|
It had to hold the closing activity until the time when the receiver buffer is depleted.
|
|
However the closing of the socket should only happen when the receiver has received
|
|
an information about that the reading is no longer possible (error report from recv/recvfile).
|
|
When this happens, the receiver buffer is definitely depleted already and there's no need to check
|
|
anything.
|
|
|
|
Should there appear any other conditions in future under which the closing process should be
|
|
delayed until the receiver buffer is empty, this code can be filled here.
|
|
|
|
if ( synch_close_rcv )
|
|
{
|
|
...
|
|
}
|
|
*/
|
|
CSync::notify_one_relaxed(m_GCStopCond);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen)
|
|
{
|
|
if (!pw_name || !pw_namelen)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
if (getStatus(u) != SRTS_CONNECTED)
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
|
|
if (!s)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
if (!s->core().m_bConnected || s->core().m_bBroken)
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
const int len = s->m_PeerAddr.size();
|
|
if (*pw_namelen < len)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
memcpy((pw_name), &s->m_PeerAddr.sa, len);
|
|
*pw_namelen = len;
|
|
}
|
|
|
|
void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen)
|
|
{
|
|
if (!pw_name || !pw_namelen)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
|
|
if (!s)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
if (s->core().m_bBroken)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
|
|
if (s->m_Status == SRTS_INIT)
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
const int len = s->m_SelfAddr.size();
|
|
if (*pw_namelen < len)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
memcpy((pw_name), &s->m_SelfAddr.sa, len);
|
|
*pw_namelen = len;
|
|
}
|
|
|
|
int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout)
|
|
{
|
|
const steady_clock::time_point entertime = steady_clock::now();
|
|
|
|
const int64_t timeo_us = timeout ? static_cast<int64_t>(timeout->tv_sec) * 1000000 + timeout->tv_usec : -1;
|
|
const steady_clock::duration timeo(microseconds_from(timeo_us));
|
|
|
|
// initialize results
|
|
int count = 0;
|
|
set<SRTSOCKET> rs, ws, es;
|
|
|
|
// retrieve related UDT sockets
|
|
vector<CUDTSocket*> ru, wu, eu;
|
|
CUDTSocket* s;
|
|
if (readfds)
|
|
for (set<SRTSOCKET>::iterator i1 = readfds->begin(); i1 != readfds->end(); ++i1)
|
|
{
|
|
if (getStatus(*i1) == SRTS_BROKEN)
|
|
{
|
|
rs.insert(*i1);
|
|
++count;
|
|
}
|
|
else if (!(s = locateSocket(*i1)))
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
else
|
|
ru.push_back(s);
|
|
}
|
|
if (writefds)
|
|
for (set<SRTSOCKET>::iterator i2 = writefds->begin(); i2 != writefds->end(); ++i2)
|
|
{
|
|
if (getStatus(*i2) == SRTS_BROKEN)
|
|
{
|
|
ws.insert(*i2);
|
|
++count;
|
|
}
|
|
else if (!(s = locateSocket(*i2)))
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
else
|
|
wu.push_back(s);
|
|
}
|
|
if (exceptfds)
|
|
for (set<SRTSOCKET>::iterator i3 = exceptfds->begin(); i3 != exceptfds->end(); ++i3)
|
|
{
|
|
if (getStatus(*i3) == SRTS_BROKEN)
|
|
{
|
|
es.insert(*i3);
|
|
++count;
|
|
}
|
|
else if (!(s = locateSocket(*i3)))
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
else
|
|
eu.push_back(s);
|
|
}
|
|
|
|
do
|
|
{
|
|
// query read sockets
|
|
for (vector<CUDTSocket*>::iterator j1 = ru.begin(); j1 != ru.end(); ++j1)
|
|
{
|
|
s = *j1;
|
|
|
|
if (s->readReady() || s->m_Status == SRTS_CLOSED)
|
|
{
|
|
rs.insert(s->m_SocketID);
|
|
++count;
|
|
}
|
|
}
|
|
|
|
// query write sockets
|
|
for (vector<CUDTSocket*>::iterator j2 = wu.begin(); j2 != wu.end(); ++j2)
|
|
{
|
|
s = *j2;
|
|
|
|
if (s->writeReady() || s->m_Status == SRTS_CLOSED)
|
|
{
|
|
ws.insert(s->m_SocketID);
|
|
++count;
|
|
}
|
|
}
|
|
|
|
// query exceptions on sockets
|
|
for (vector<CUDTSocket*>::iterator j3 = eu.begin(); j3 != eu.end(); ++j3)
|
|
{
|
|
// check connection request status, not supported now
|
|
}
|
|
|
|
if (0 < count)
|
|
break;
|
|
|
|
CGlobEvent::waitForEvent();
|
|
} while (timeo > steady_clock::now() - entertime);
|
|
|
|
if (readfds)
|
|
*readfds = rs;
|
|
|
|
if (writefds)
|
|
*writefds = ws;
|
|
|
|
if (exceptfds)
|
|
*exceptfds = es;
|
|
|
|
return count;
|
|
}
|
|
|
|
int srt::CUDTUnited::selectEx(const vector<SRTSOCKET>& fds,
|
|
vector<SRTSOCKET>* readfds,
|
|
vector<SRTSOCKET>* writefds,
|
|
vector<SRTSOCKET>* exceptfds,
|
|
int64_t msTimeOut)
|
|
{
|
|
const steady_clock::time_point entertime = steady_clock::now();
|
|
|
|
const int64_t timeo_us = msTimeOut >= 0 ? msTimeOut * 1000 : -1;
|
|
const steady_clock::duration timeo(microseconds_from(timeo_us));
|
|
|
|
// initialize results
|
|
int count = 0;
|
|
if (readfds)
|
|
readfds->clear();
|
|
if (writefds)
|
|
writefds->clear();
|
|
if (exceptfds)
|
|
exceptfds->clear();
|
|
|
|
do
|
|
{
|
|
for (vector<SRTSOCKET>::const_iterator i = fds.begin(); i != fds.end(); ++i)
|
|
{
|
|
CUDTSocket* s = locateSocket(*i);
|
|
|
|
if ((!s) || s->core().m_bBroken || (s->m_Status == SRTS_CLOSED))
|
|
{
|
|
if (exceptfds)
|
|
{
|
|
exceptfds->push_back(*i);
|
|
++count;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (readfds)
|
|
{
|
|
if ((s->core().m_bConnected && s->core().m_pRcvBuffer->isRcvDataReady()) ||
|
|
(s->core().m_bListening && (s->m_QueuedSockets.size() > 0)))
|
|
{
|
|
readfds->push_back(s->m_SocketID);
|
|
++count;
|
|
}
|
|
}
|
|
|
|
if (writefds)
|
|
{
|
|
if (s->core().m_bConnected &&
|
|
(s->core().m_pSndBuffer->getCurrBufSize() < s->core().m_config.iSndBufSize))
|
|
{
|
|
writefds->push_back(s->m_SocketID);
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count > 0)
|
|
break;
|
|
|
|
CGlobEvent::waitForEvent();
|
|
} while (timeo > steady_clock::now() - entertime);
|
|
|
|
return count;
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_create()
|
|
{
|
|
return m_EPoll.create();
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_clear_usocks(int eid)
|
|
{
|
|
return m_EPoll.clear_usocks(eid);
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events)
|
|
{
|
|
int ret = -1;
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
GroupKeeper k(*this, u, ERH_THROW);
|
|
ret = m_EPoll.update_usock(eid, u, events);
|
|
k.group->addEPoll(eid);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
CUDTSocket* s = locateSocket(u);
|
|
if (s)
|
|
{
|
|
ret = epoll_add_usock_INTERNAL(eid, s, events);
|
|
}
|
|
else
|
|
{
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// NOTE: WILL LOCK (serially):
|
|
// - CEPoll::m_EPollLock
|
|
// - CUDT::m_RecvLock
|
|
int srt::CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events)
|
|
{
|
|
int ret = m_EPoll.update_usock(eid, s->m_SocketID, events);
|
|
s->core().addEPoll(eid);
|
|
return ret;
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events)
|
|
{
|
|
return m_EPoll.add_ssock(eid, s, events);
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events)
|
|
{
|
|
return m_EPoll.update_ssock(eid, s, events);
|
|
}
|
|
|
|
template <class EntityType>
|
|
int srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent)
|
|
{
|
|
// XXX Not sure if this is anyhow necessary because setting readiness
|
|
// to false doesn't actually trigger any action. Further research needed.
|
|
HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING readiness on E" << eid << " of @" << ent->id());
|
|
ent->removeEPollEvents(eid);
|
|
|
|
// First remove the EID from the subscribed in the socket so that
|
|
// a possible call to update_events:
|
|
// - if happens before this call, can find the epoll bit update possible
|
|
// - if happens after this call, will not strike this EID
|
|
HLOGC(ealog.Debug, log << "epoll_remove_usock: REMOVING E" << eid << " from back-subscirbers in @" << ent->id());
|
|
ent->removeEPollID(eid);
|
|
|
|
HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING subscription on E" << eid << " of @" << ent->id());
|
|
int no_events = 0;
|
|
int ret = m_EPoll.update_usock(eid, ent->id(), &no_events);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Needed internal access!
|
|
int srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s)
|
|
{
|
|
return epoll_remove_entity(eid, &s->core());
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
int srt::CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g)
|
|
{
|
|
return epoll_remove_entity(eid, g);
|
|
}
|
|
#endif
|
|
|
|
int srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u)
|
|
{
|
|
CUDTSocket* s = 0;
|
|
|
|
#if ENABLE_BONDING
|
|
CUDTGroup* g = 0;
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
GroupKeeper k(*this, u, ERH_THROW);
|
|
g = k.group;
|
|
return epoll_remove_entity(eid, g);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
s = locateSocket(u);
|
|
if (s)
|
|
return epoll_remove_entity(eid, &s->core());
|
|
}
|
|
|
|
LOGC(ealog.Error,
|
|
log << "remove_usock: @" << u << " not found as either socket or group. Removing only from epoll system.");
|
|
int no_events = 0;
|
|
return m_EPoll.update_usock(eid, u, &no_events);
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s)
|
|
{
|
|
return m_EPoll.remove_ssock(eid, s);
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut)
|
|
{
|
|
return m_EPoll.uwait(eid, fdsSet, fdsSize, msTimeOut);
|
|
}
|
|
|
|
int32_t srt::CUDTUnited::epoll_set(int eid, int32_t flags)
|
|
{
|
|
return m_EPoll.setflags(eid, flags);
|
|
}
|
|
|
|
int srt::CUDTUnited::epoll_release(const int eid)
|
|
{
|
|
return m_EPoll.release(eid);
|
|
}
|
|
|
|
srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh)
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
CUDTSocket* s = locateSocket_LOCKED(u);
|
|
if (!s)
|
|
{
|
|
if (erh == ERH_RETURN)
|
|
return NULL;
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
// [[using locked(m_GlobControlLock)]];
|
|
srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u)
|
|
{
|
|
sockets_t::iterator i = m_Sockets.find(u);
|
|
|
|
if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return i->second;
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh)
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
|
|
const groups_t::iterator i = m_Groups.find(u);
|
|
if (i == m_Groups.end())
|
|
{
|
|
if (erh == ERH_THROW)
|
|
throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
return NULL;
|
|
}
|
|
|
|
ScopedLock cgroup(*i->second->exp_groupLock());
|
|
i->second->apiAcquire();
|
|
return i->second;
|
|
}
|
|
|
|
srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s)
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
CUDTGroup* g = s->m_GroupOf;
|
|
if (!g)
|
|
return NULL;
|
|
|
|
// With m_GlobControlLock locked, we are sure the group
|
|
// still exists, if it wasn't removed from this socket.
|
|
g->apiAcquire();
|
|
return g;
|
|
}
|
|
#endif
|
|
|
|
srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn)
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
|
|
map<int64_t, set<SRTSOCKET> >::iterator i = m_PeerRec.find(CUDTSocket::getPeerSpec(id, isn));
|
|
if (i == m_PeerRec.end())
|
|
return NULL;
|
|
|
|
for (set<SRTSOCKET>::iterator j = i->second.begin(); j != i->second.end(); ++j)
|
|
{
|
|
sockets_t::iterator k = m_Sockets.find(*j);
|
|
// this socket might have been closed and moved m_ClosedSockets
|
|
if (k == m_Sockets.end())
|
|
continue;
|
|
|
|
if (k->second->m_PeerAddr == peer)
|
|
{
|
|
return k->second;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void srt::CUDTUnited::checkBrokenSockets()
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
|
|
#if ENABLE_BONDING
|
|
vector<SRTSOCKET> delgids;
|
|
|
|
for (groups_t::iterator i = m_ClosedGroups.begin(); i != m_ClosedGroups.end(); ++i)
|
|
{
|
|
// isStillBusy requires lock on the group, so only after an API
|
|
// function that uses it returns, and so clears the busy flag,
|
|
// a new API function won't be called anyway until it can acquire
|
|
// GlobControlLock, and all functions that have already seen this
|
|
// group as closing will not continue with the API and return.
|
|
// If we caught some API function still using the closed group,
|
|
// it's not going to wait, will be checked next time.
|
|
if (i->second->isStillBusy())
|
|
continue;
|
|
|
|
delgids.push_back(i->first);
|
|
delete i->second;
|
|
i->second = NULL; // just for a case, avoid a dangling pointer
|
|
}
|
|
|
|
for (vector<SRTSOCKET>::iterator di = delgids.begin(); di != delgids.end(); ++di)
|
|
{
|
|
m_ClosedGroups.erase(*di);
|
|
}
|
|
#endif
|
|
|
|
// set of sockets To Be Closed and To Be Removed
|
|
vector<SRTSOCKET> tbc;
|
|
vector<SRTSOCKET> tbr;
|
|
|
|
for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i)
|
|
{
|
|
CUDTSocket* s = i->second;
|
|
if (!s->core().m_bBroken)
|
|
continue;
|
|
|
|
if (s->m_Status == SRTS_LISTENING)
|
|
{
|
|
const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp;
|
|
// A listening socket should wait an extra 3 seconds
|
|
// in case a client is connecting.
|
|
if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS))
|
|
continue;
|
|
}
|
|
else if ((s->core().m_pRcvBuffer != NULL)
|
|
// FIXED: calling isRcvDataAvailable() just to get the information
|
|
// whether there are any data waiting in the buffer,
|
|
// NOT WHETHER THEY ARE ALSO READY TO PLAY at the time when
|
|
// this function is called (isRcvDataReady also checks if the
|
|
// available data is "ready to play").
|
|
&& s->core().m_pRcvBuffer->hasAvailablePackets())
|
|
{
|
|
const int bc = s->core().m_iBrokenCounter.load();
|
|
if (bc > 0)
|
|
{
|
|
// if there is still data in the receiver buffer, wait longer
|
|
s->core().m_iBrokenCounter.store(bc - 1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
if (s->m_GroupOf)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP");
|
|
s->removeFromGroup(true);
|
|
}
|
|
#endif
|
|
|
|
HLOGC(smlog.Debug, log << "checkBrokenSockets: moving BROKEN socket to CLOSED: @" << i->first);
|
|
|
|
// close broken connections and start removal timer
|
|
s->setClosed();
|
|
tbc.push_back(i->first);
|
|
m_ClosedSockets[i->first] = s;
|
|
|
|
// remove from listener's queue
|
|
sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket);
|
|
if (ls == m_Sockets.end())
|
|
{
|
|
ls = m_ClosedSockets.find(s->m_ListenSocket);
|
|
if (ls == m_ClosedSockets.end())
|
|
continue;
|
|
}
|
|
|
|
enterCS(ls->second->m_AcceptLock);
|
|
ls->second->m_QueuedSockets.erase(s->m_SocketID);
|
|
leaveCS(ls->second->m_AcceptLock);
|
|
}
|
|
|
|
for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j)
|
|
{
|
|
// HLOGC(smlog.Debug, log << "checking CLOSED socket: " << j->first);
|
|
if (!is_zero(j->second->core().m_tsLingerExpiration))
|
|
{
|
|
// asynchronous close:
|
|
if ((!j->second->core().m_pSndBuffer) || (0 == j->second->core().m_pSndBuffer->getCurrBufSize()) ||
|
|
(j->second->core().m_tsLingerExpiration <= steady_clock::now()))
|
|
{
|
|
HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED qualified @" << j->second->m_SocketID);
|
|
j->second->core().m_tsLingerExpiration = steady_clock::time_point();
|
|
j->second->core().m_bClosing = true;
|
|
j->second->m_tsClosureTimeStamp = steady_clock::now();
|
|
}
|
|
}
|
|
|
|
// timeout 1 second to destroy a socket AND it has been removed from
|
|
// RcvUList
|
|
const steady_clock::time_point now = steady_clock::now();
|
|
const steady_clock::duration closed_ago = now - j->second->m_tsClosureTimeStamp;
|
|
if (closed_ago > seconds_from(1))
|
|
{
|
|
CRNode* rnode = j->second->core().m_pRNode;
|
|
if (!rnode || !rnode->m_bOnList)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "checkBrokenSockets: @" << j->second->m_SocketID << " closed "
|
|
<< FormatDuration(closed_ago) << " ago and removed from RcvQ - will remove");
|
|
|
|
// HLOGC(smlog.Debug, log << "will unref socket: " << j->first);
|
|
tbr.push_back(j->first);
|
|
}
|
|
}
|
|
}
|
|
|
|
// move closed sockets to the ClosedSockets structure
|
|
for (vector<SRTSOCKET>::iterator k = tbc.begin(); k != tbc.end(); ++k)
|
|
m_Sockets.erase(*k);
|
|
|
|
// remove those timeout sockets
|
|
for (vector<SRTSOCKET>::iterator l = tbr.begin(); l != tbr.end(); ++l)
|
|
removeSocket(*l);
|
|
|
|
HLOGC(smlog.Debug, log << "checkBrokenSockets: after removal: m_ClosedSockets.size()=" << m_ClosedSockets.size());
|
|
}
|
|
|
|
// [[using locked(m_GlobControlLock)]]
|
|
void srt::CUDTUnited::removeSocket(const SRTSOCKET u)
|
|
{
|
|
sockets_t::iterator i = m_ClosedSockets.find(u);
|
|
|
|
// invalid socket ID
|
|
if (i == m_ClosedSockets.end())
|
|
return;
|
|
|
|
CUDTSocket* const s = i->second;
|
|
|
|
// The socket may be in the trashcan now, but could
|
|
// still be under processing in the sender/receiver worker
|
|
// threads. If that's the case, SKIP IT THIS TIME. The
|
|
// socket will be checked next time the GC rollover starts.
|
|
CSNode* sn = s->core().m_pSNode;
|
|
if (sn && sn->m_iHeapLoc != -1)
|
|
return;
|
|
|
|
CRNode* rn = s->core().m_pRNode;
|
|
if (rn && rn->m_bOnList)
|
|
return;
|
|
|
|
#if ENABLE_BONDING
|
|
if (s->m_GroupOf)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP");
|
|
s->removeFromGroup(true);
|
|
}
|
|
#endif
|
|
// decrease multiplexer reference count, and remove it if necessary
|
|
const int mid = s->m_iMuxID;
|
|
|
|
{
|
|
ScopedLock cg(s->m_AcceptLock);
|
|
|
|
// if it is a listener, close all un-accepted sockets in its queue
|
|
// and remove them later
|
|
for (set<SRTSOCKET>::iterator q = s->m_QueuedSockets.begin(); q != s->m_QueuedSockets.end(); ++q)
|
|
{
|
|
sockets_t::iterator si = m_Sockets.find(*q);
|
|
if (si == m_Sockets.end())
|
|
{
|
|
// gone in the meantime
|
|
LOGC(smlog.Error,
|
|
log << "removeSocket: IPE? socket @" << (*q) << " being queued for listener socket @"
|
|
<< s->m_SocketID << " is GONE in the meantime ???");
|
|
continue;
|
|
}
|
|
|
|
CUDTSocket* as = si->second;
|
|
|
|
as->breakSocket_LOCKED();
|
|
m_ClosedSockets[*q] = as;
|
|
m_Sockets.erase(*q);
|
|
}
|
|
}
|
|
|
|
// remove from peer rec
|
|
map<int64_t, set<SRTSOCKET> >::iterator j = m_PeerRec.find(s->getPeerSpec());
|
|
if (j != m_PeerRec.end())
|
|
{
|
|
j->second.erase(u);
|
|
if (j->second.empty())
|
|
m_PeerRec.erase(j);
|
|
}
|
|
|
|
/*
|
|
* Socket may be deleted while still having ePoll events set that would
|
|
* remains forever causing epoll_wait to unblock continuously for inexistent
|
|
* sockets. Get rid of all events for this socket.
|
|
*/
|
|
m_EPoll.update_events(u, s->core().m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, false);
|
|
|
|
// delete this one
|
|
m_ClosedSockets.erase(i);
|
|
|
|
HLOGC(smlog.Debug, log << "GC/removeSocket: closing associated UDT @" << u);
|
|
s->core().closeInternal();
|
|
HLOGC(smlog.Debug, log << "GC/removeSocket: DELETING SOCKET @" << u);
|
|
delete s;
|
|
HLOGC(smlog.Debug, log << "GC/removeSocket: socket @" << u << " DELETED. Checking muxer.");
|
|
|
|
if (mid == -1)
|
|
{
|
|
HLOGC(smlog.Debug, log << "GC/removeSocket: no muxer found, finishing.");
|
|
return;
|
|
}
|
|
|
|
map<int, CMultiplexer>::iterator m;
|
|
m = m_mMultiplexer.find(mid);
|
|
if (m == m_mMultiplexer.end())
|
|
{
|
|
LOGC(smlog.Fatal, log << "IPE: For socket @" << u << " MUXER id=" << mid << " NOT FOUND!");
|
|
return;
|
|
}
|
|
|
|
CMultiplexer& mx = m->second;
|
|
|
|
mx.m_iRefCount--;
|
|
HLOGC(smlog.Debug, log << "unrefing underlying muxer " << mid << " for @" << u << ", ref=" << mx.m_iRefCount);
|
|
if (0 == mx.m_iRefCount)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "MUXER id=" << mid << " lost last socket @" << u << " - deleting muxer bound to port "
|
|
<< mx.m_pChannel->bindAddressAny().hport());
|
|
// The channel has no access to the queues and
|
|
// it looks like the multiplexer is the master of all of them.
|
|
// The queues must be silenced before closing the channel
|
|
// because this will cause error to be returned in any operation
|
|
// being currently done in the queues, if any.
|
|
mx.m_pSndQueue->setClosing();
|
|
mx.m_pRcvQueue->setClosing();
|
|
mx.destroy();
|
|
m_mMultiplexer.erase(m);
|
|
}
|
|
}
|
|
|
|
void srt::CUDTUnited::configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af)
|
|
{
|
|
w_m.m_mcfg = s->core().m_config;
|
|
w_m.m_iIPversion = af;
|
|
w_m.m_iRefCount = 1;
|
|
w_m.m_iID = s->m_SocketID;
|
|
}
|
|
|
|
uint16_t srt::CUDTUnited::installMuxer(CUDTSocket* w_s, CMultiplexer& fw_sm)
|
|
{
|
|
w_s->core().m_pSndQueue = fw_sm.m_pSndQueue;
|
|
w_s->core().m_pRcvQueue = fw_sm.m_pRcvQueue;
|
|
w_s->m_iMuxID = fw_sm.m_iID;
|
|
sockaddr_any sa;
|
|
fw_sm.m_pChannel->getSockAddr((sa));
|
|
w_s->m_SelfAddr = sa; // Will be also completed later, but here it's needed for later checks
|
|
return sa.hport();
|
|
}
|
|
|
|
bool srt::CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer,
|
|
const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket)
|
|
{
|
|
if (muxaddr.family() != AF_INET6)
|
|
return true; // Don't check - the family has been checked already
|
|
|
|
if (reqaddr.isany())
|
|
{
|
|
if (cfgSocket.iIpV6Only == -1) // Treat as "adaptive"
|
|
return true;
|
|
|
|
// If set explicitly, then it must be equal to the one of found muxer.
|
|
return cfgSocket.iIpV6Only == cfgMuxer.iIpV6Only;
|
|
}
|
|
|
|
// If binding to the certain IPv6 address, then this setting doesn't matter.
|
|
return true;
|
|
}
|
|
|
|
bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket)
|
|
{
|
|
if (!cfgMuxer.bReuseAddr)
|
|
{
|
|
HLOGP(smlog.Debug, "channelSettingsMatch: fail: the multiplexer is not reusable");
|
|
return false;
|
|
}
|
|
|
|
if (cfgMuxer.isCompatWith(cfgSocket))
|
|
return true;
|
|
|
|
HLOGP(smlog.Debug, "channelSettingsMatch: fail: some options have different values");
|
|
return false;
|
|
}
|
|
|
|
void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/)
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
|
|
// If udpsock is provided, then this socket will be simply
|
|
// taken for binding as a good deal. It would be nice to make
|
|
// a sanity check to see if this UDP socket isn't already installed
|
|
// in some multiplexer, but we state this UDP socket isn't accessible
|
|
// anyway so this wouldn't be possible.
|
|
if (!udpsock)
|
|
{
|
|
// If not, we need to see if there exist already a multiplexer bound
|
|
// to the same endpoint.
|
|
const int port = reqaddr.hport();
|
|
const CSrtConfig& cfgSocket = s->core().m_config;
|
|
|
|
// This loop is going to check the attempted binding of
|
|
// address:port and socket settings against every existing
|
|
// multiplexer. Possible results of the check are:
|
|
|
|
// 1. MATCH: identical address - reuse it and quit.
|
|
// 2. CONFLICT: report error: the binding partially overlaps
|
|
// so it neither can be reused nor is free to bind.
|
|
// 3. PASS: different and not overlapping - continue searching.
|
|
|
|
// In this function the convention is:
|
|
// MATCH: do nothing and proceed with binding reusage, THEN break.
|
|
// CONFLICT: throw an exception.
|
|
// PASS: use 'continue' to pass to the next element.
|
|
|
|
bool reuse_attempt = false;
|
|
for (map<int, CMultiplexer>::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i)
|
|
{
|
|
CMultiplexer& m = i->second;
|
|
|
|
// First, we need to find a multiplexer with the same port.
|
|
if (m.m_iPort != port)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "bind: muxer @" << m.m_iID << " found, but for port " << m.m_iPort
|
|
<< " (requested port: " << port << ")");
|
|
continue;
|
|
}
|
|
|
|
// If this is bound to the wildcard address, it can be reused if:
|
|
// - reqaddr is also a wildcard
|
|
// - channel settings match
|
|
// Otherwise it's a conflict.
|
|
sockaddr_any mux_addr;
|
|
m.m_pChannel->getSockAddr((mux_addr));
|
|
|
|
HLOGC(smlog.Debug,
|
|
log << "bind: Found existing muxer @" << m.m_iID << " : " << mux_addr.str() << " - check against "
|
|
<< reqaddr.str());
|
|
|
|
if (mux_addr.isany())
|
|
{
|
|
if (mux_addr.family() == AF_INET6)
|
|
{
|
|
// With IPv6 we need to research two possibilities:
|
|
// iIpV6Only == 1 -> This means that it binds only :: wildcard, but not 0.0.0.0
|
|
// iIpV6Only == 0 -> This means that it binds both :: and 0.0.0.0.
|
|
// iIpV6Only == -1 -> Hard to say what to do, but treat it as a potential conflict in any doubtful case.
|
|
|
|
if (m.m_mcfg.iIpV6Only == 1)
|
|
{
|
|
// PASS IF: candidate is IPv4, no matter the address
|
|
// MATCH IF: candidate is IPv6 with only=1
|
|
// CONFLICT IF: candidate is IPv6 with only != 1 or IPv6 non-wildcard.
|
|
|
|
if (reqaddr.family() == AF_INET)
|
|
{
|
|
HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID
|
|
<< " is :: v6only - requested IPv4 ANY is NOT IN THE WAY. Searching on.");
|
|
continue;
|
|
}
|
|
|
|
// Candidate is AF_INET6
|
|
|
|
if (cfgSocket.iIpV6Only != 1 || !reqaddr.isany())
|
|
{
|
|
// CONFLICT:
|
|
// 1. attempting to make a wildcard IPv4 + IPv6
|
|
// while the multiplexer for wildcard IPv6 exists.
|
|
// 2. If binding to a given address, it conflicts with the wildcard
|
|
LOGC(smlog.Error,
|
|
log << "bind: Address: " << reqaddr.str()
|
|
<< " conflicts with existing IPv6 wildcard binding: " << mux_addr.str());
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
|
|
// Otherwise, MATCH.
|
|
}
|
|
else if (m.m_mcfg.iIpV6Only == 0)
|
|
{
|
|
// Muxer's address is a wildcard for :: and 0.0.0.0 at once.
|
|
// This way only IPv6 wildcard with v6only=0 is a perfect match and everything
|
|
// else is a conflict.
|
|
|
|
if (reqaddr.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == 0)
|
|
{
|
|
// MATCH
|
|
}
|
|
else
|
|
{
|
|
// CONFLICT: attempting to make a wildcard IPv4 + IPv6 while
|
|
// the multiplexer for wildcard IPv6 exists.
|
|
LOGC(smlog.Error,
|
|
log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only
|
|
<< " conflicts with existing IPv6 + IPv4 wildcard binding: " << mux_addr.str());
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
}
|
|
else // Case -1, by unknown reason. Accept only with -1 setting, others are conflict.
|
|
{
|
|
if (reqaddr.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == -1)
|
|
{
|
|
// MATCH
|
|
}
|
|
else
|
|
{
|
|
LOGC(smlog.Error,
|
|
log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only
|
|
<< " conflicts with existing IPv6 v6only=unknown wildcard binding: " << mux_addr.str());
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
}
|
|
}
|
|
else // muxer is IPv4 wildcard
|
|
{
|
|
// Then only IPv4 wildcard is a match and:
|
|
// - IPv6 with only=true is PASS (not a conflict)
|
|
// - IPv6 with only=false is CONFLICT
|
|
// - IPv6 with only=undefined is CONFLICT
|
|
// REASON: we need to make a potential conflict a conflict as there will be
|
|
// no bind() call to check if this wouldn't be a conflict in result. If you want
|
|
// to have a binding to IPv6 that should avoid conflict with IPv4 wildcard binding,
|
|
// then SRTO_IPV6ONLY option must be explicitly set before binding.
|
|
// Also:
|
|
if (reqaddr.family() == AF_INET)
|
|
{
|
|
if (reqaddr.isany())
|
|
{
|
|
// MATCH
|
|
}
|
|
else
|
|
{
|
|
LOGC(smlog.Error,
|
|
log << "bind: Address: " << reqaddr.str()
|
|
<< " conflicts with existing IPv4 wildcard binding: " << mux_addr.str());
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
}
|
|
else // AF_INET6
|
|
{
|
|
if (cfgSocket.iIpV6Only == 1 || !reqaddr.isany())
|
|
{
|
|
// PASS
|
|
HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID
|
|
<< " is IPv4 wildcard - requested " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only
|
|
<< " is NOT IN THE WAY. Searching on.");
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
LOGC(smlog.Error,
|
|
log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only
|
|
<< " conflicts with existing IPv4 wildcard binding: " << mux_addr.str());
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
reuse_attempt = true;
|
|
HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable");
|
|
}
|
|
// Muxer address is NOT a wildcard, so conflicts only with WILDCARD of the same type
|
|
else if (reqaddr.isany() && reqaddr.family() == mux_addr.family())
|
|
{
|
|
LOGC(smlog.Error,
|
|
log << "bind: Wildcard address: " << reqaddr.str()
|
|
<< " conflicts with existting IP binding: " << mux_addr.str());
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
// If this is bound to a certain address, AND:
|
|
else if (mux_addr.equal_address(reqaddr))
|
|
{
|
|
// - the address is the same as reqaddr
|
|
reuse_attempt = true;
|
|
HLOGC(smlog.Debug, log << "bind: same IP address - multiplexer reusable");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(smlog.Debug, log << "bind: IP addresses differ - ALLOWED to create a new multiplexer");
|
|
continue;
|
|
}
|
|
// Otherwise:
|
|
// - the address is different than reqaddr
|
|
// - the address can't be reused, but this can go on with new one.
|
|
|
|
// If this is a reusage attempt:
|
|
if (reuse_attempt)
|
|
{
|
|
// - if the channel settings match, it can be reused
|
|
if (channelSettingsMatch(m.m_mcfg, cfgSocket) && inet6SettingsCompat(mux_addr, m.m_mcfg, reqaddr, cfgSocket))
|
|
{
|
|
HLOGC(smlog.Debug, log << "bind: reusing multiplexer for port " << port);
|
|
// reuse the existing multiplexer
|
|
++i->second.m_iRefCount;
|
|
installMuxer((s), (i->second));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// - if not, it's a conflict
|
|
LOGC(smlog.Error,
|
|
log << "bind: Address: " << reqaddr.str() << " conflicts with binding: " << mux_addr.str()
|
|
<< " due to channel settings");
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
|
|
}
|
|
}
|
|
// If not, proceed to the next one, and when there are no reusage
|
|
// candidates, proceed with creating a new multiplexer.
|
|
|
|
// Note that a binding to a different IP address is not treated
|
|
// as a candidate for either reusage or conflict.
|
|
LOGC(smlog.Fatal, log << "SHOULD NOT GET HERE!!!");
|
|
SRT_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
// a new multiplexer is needed
|
|
CMultiplexer m;
|
|
configureMuxer((m), s, reqaddr.family());
|
|
|
|
try
|
|
{
|
|
m.m_pChannel = new CChannel();
|
|
m.m_pChannel->setConfig(m.m_mcfg);
|
|
|
|
if (udpsock)
|
|
{
|
|
// In this case, reqaddr contains the address
|
|
// that has been extracted already from the
|
|
// given socket
|
|
m.m_pChannel->attach(*udpsock, reqaddr);
|
|
}
|
|
else if (reqaddr.empty())
|
|
{
|
|
// The case of previously used case of a NULL address.
|
|
// This here is used to pass family only, in this case
|
|
// just automatically bind to the "0" address to autoselect
|
|
// everything.
|
|
m.m_pChannel->open(reqaddr.family());
|
|
}
|
|
else
|
|
{
|
|
// If at least the IP address is specified, then bind to that
|
|
// address, but still possibly autoselect the outgoing port, if the
|
|
// port was specified as 0.
|
|
m.m_pChannel->open(reqaddr);
|
|
}
|
|
|
|
// AFTER OPENING, check the matter of IPV6_V6ONLY option,
|
|
// as it decides about the fact that the occupied binding address
|
|
// in case of wildcard is both :: and 0.0.0.0, or only ::.
|
|
if (reqaddr.family() == AF_INET6 && m.m_mcfg.iIpV6Only == -1)
|
|
{
|
|
// XXX We don't know how probable it is to get the error here
|
|
// and resulting -1 value. As a fallback for that case, the value -1
|
|
// is honored here, just all side-bindings for other sockes will be
|
|
// rejected as a potential conflict, even if binding would be accepted
|
|
// in these circumstances. Only a perfect match in case of potential
|
|
// overlapping will be accepted on the same port.
|
|
m.m_mcfg.iIpV6Only = m.m_pChannel->sockopt(IPPROTO_IPV6, IPV6_V6ONLY, -1);
|
|
}
|
|
|
|
m.m_pTimer = new CTimer;
|
|
m.m_pSndQueue = new CSndQueue;
|
|
m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer);
|
|
m.m_pRcvQueue = new CRcvQueue;
|
|
m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer);
|
|
|
|
// Rewrite the port here, as it might be only known upon return
|
|
// from CChannel::open.
|
|
m.m_iPort = installMuxer((s), m);
|
|
m_mMultiplexer[m.m_iID] = m;
|
|
}
|
|
catch (const CUDTException&)
|
|
{
|
|
m.destroy();
|
|
throw;
|
|
}
|
|
catch (...)
|
|
{
|
|
m.destroy();
|
|
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
|
|
HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << m.m_iPort);
|
|
}
|
|
|
|
// This function is going to find a multiplexer for the port contained
|
|
// in the 'ls' listening socket. The multiplexer must exist when the listener
|
|
// exists, otherwise the dispatching procedure wouldn't even call this
|
|
// function. By historical reasons there's also a fallback for a case when the
|
|
// multiplexer wasn't found by id, the search by port number continues.
|
|
bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls)
|
|
{
|
|
ScopedLock cg(m_GlobControlLock);
|
|
const int port = ls->m_SelfAddr.hport();
|
|
|
|
HLOGC(smlog.Debug,
|
|
log << "updateListenerMux: finding muxer of listener socket @" << ls->m_SocketID << " muxid=" << ls->m_iMuxID
|
|
<< " bound=" << ls->m_SelfAddr.str() << " FOR @" << s->m_SocketID << " addr=" << s->m_SelfAddr.str()
|
|
<< "_->_" << s->m_PeerAddr.str());
|
|
|
|
// First thing that should be certain here is that there should exist
|
|
// a muxer with the ID written in the listener socket's mux ID.
|
|
|
|
CMultiplexer* mux = map_getp(m_mMultiplexer, ls->m_iMuxID);
|
|
|
|
// NOTE:
|
|
// THIS BELOW CODE is only for a highly unlikely situation when the listener
|
|
// socket has been closed in the meantime when the accepted socket is being
|
|
// processed. This procedure is different than updateMux because this time we
|
|
// only want to have a multiplexer socket to be assigned to the accepted socket.
|
|
// It is also unlikely that the listener socket is garbage-collected so fast, so
|
|
// this procedure will most likely find the multiplexer of the zombie listener socket,
|
|
// which no longer accepts new connections (the listener is withdrawn immediately from
|
|
// the port) that wasn't yet completely deleted.
|
|
CMultiplexer* fallback = NULL;
|
|
if (!mux)
|
|
{
|
|
LOGC(smlog.Error, log << "updateListenerMux: IPE? listener muxer not found by ID, trying by port");
|
|
|
|
// To be used as first found with different IP version
|
|
|
|
// find the listener's address
|
|
for (map<int, CMultiplexer>::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i)
|
|
{
|
|
CMultiplexer& m = i->second;
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
ostringstream that_muxer;
|
|
that_muxer << "id=" << m.m_iID << " port=" << m.m_iPort
|
|
<< " ip=" << (m.m_iIPversion == AF_INET ? "v4" : "v6");
|
|
#endif
|
|
|
|
if (m.m_iPort == port)
|
|
{
|
|
HLOGC(smlog.Debug, log << "updateListenerMux: reusing muxer: " << that_muxer.str());
|
|
if (m.m_iIPversion == s->m_PeerAddr.family())
|
|
{
|
|
mux = &m; // best match
|
|
break;
|
|
}
|
|
else if (m.m_iIPversion == AF_INET6)
|
|
{
|
|
// Allowed fallback case when we only need an accepted socket.
|
|
fallback = &m;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(smlog.Debug, log << "updateListenerMux: SKIPPING muxer: " << that_muxer.str());
|
|
}
|
|
}
|
|
|
|
if (!mux && fallback)
|
|
{
|
|
// It is allowed to reuse this multiplexer, but the socket must allow both IPv4 and IPv6
|
|
if (fallback->m_mcfg.iIpV6Only == 0)
|
|
{
|
|
HLOGC(smlog.Warn, log << "updateListenerMux: reusing multiplexer from different family");
|
|
mux = fallback;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checking again because the above procedure could have set it
|
|
if (mux)
|
|
{
|
|
// reuse the existing multiplexer
|
|
++mux->m_iRefCount;
|
|
s->core().m_pSndQueue = mux->m_pSndQueue;
|
|
s->core().m_pRcvQueue = mux->m_pRcvQueue;
|
|
s->m_iMuxID = mux->m_iID;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void* srt::CUDTUnited::garbageCollect(void* p)
|
|
{
|
|
CUDTUnited* self = (CUDTUnited*)p;
|
|
|
|
THREAD_STATE_INIT("SRT:GC");
|
|
|
|
UniqueLock gclock(self->m_GCStopLock);
|
|
|
|
while (!self->m_bClosing)
|
|
{
|
|
INCREMENT_THREAD_ITERATIONS();
|
|
self->checkBrokenSockets();
|
|
|
|
HLOGC(inlog.Debug, log << "GC: sleep 1 s");
|
|
self->m_GCStopCond.wait_for(gclock, seconds_from(1));
|
|
}
|
|
|
|
// remove all sockets and multiplexers
|
|
HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock...");
|
|
|
|
{
|
|
ScopedLock glock(self->m_GlobControlLock);
|
|
|
|
for (sockets_t::iterator i = self->m_Sockets.begin(); i != self->m_Sockets.end(); ++i)
|
|
{
|
|
CUDTSocket* s = i->second;
|
|
s->breakSocket_LOCKED();
|
|
|
|
#if ENABLE_BONDING
|
|
if (s->m_GroupOf)
|
|
{
|
|
HLOGC(smlog.Debug,
|
|
log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id()
|
|
<< " (IPE?) - REMOVING FROM GROUP");
|
|
s->removeFromGroup(false);
|
|
}
|
|
#endif
|
|
self->m_ClosedSockets[i->first] = s;
|
|
|
|
// remove from listener's queue
|
|
sockets_t::iterator ls = self->m_Sockets.find(s->m_ListenSocket);
|
|
if (ls == self->m_Sockets.end())
|
|
{
|
|
ls = self->m_ClosedSockets.find(s->m_ListenSocket);
|
|
if (ls == self->m_ClosedSockets.end())
|
|
continue;
|
|
}
|
|
|
|
enterCS(ls->second->m_AcceptLock);
|
|
ls->second->m_QueuedSockets.erase(s->m_SocketID);
|
|
leaveCS(ls->second->m_AcceptLock);
|
|
}
|
|
self->m_Sockets.clear();
|
|
|
|
for (sockets_t::iterator j = self->m_ClosedSockets.begin(); j != self->m_ClosedSockets.end(); ++j)
|
|
{
|
|
j->second->m_tsClosureTimeStamp = steady_clock::time_point();
|
|
}
|
|
}
|
|
|
|
HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets.");
|
|
while (true)
|
|
{
|
|
self->checkBrokenSockets();
|
|
|
|
enterCS(self->m_GlobControlLock);
|
|
bool empty = self->m_ClosedSockets.empty();
|
|
leaveCS(self->m_GlobControlLock);
|
|
|
|
if (empty)
|
|
break;
|
|
|
|
HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep");
|
|
srt::sync::this_thread::sleep_for(milliseconds_from(1));
|
|
}
|
|
|
|
THREAD_EXIT();
|
|
return NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int srt::CUDT::startup()
|
|
{
|
|
return uglobal().startup();
|
|
}
|
|
|
|
int srt::CUDT::cleanup()
|
|
{
|
|
return uglobal().cleanup();
|
|
}
|
|
|
|
SRTSOCKET srt::CUDT::socket()
|
|
{
|
|
if (!uglobal().m_bGCStatus)
|
|
uglobal().startup();
|
|
|
|
try
|
|
{
|
|
return uglobal().newSocket();
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return INVALID_SOCK;
|
|
}
|
|
catch (const bad_alloc&)
|
|
{
|
|
SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0));
|
|
return INVALID_SOCK;
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "socket: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0));
|
|
return INVALID_SOCK;
|
|
}
|
|
}
|
|
|
|
srt::CUDT::APIError::APIError(const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
}
|
|
|
|
srt::CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr)
|
|
{
|
|
SetThreadLocalError(CUDTException(mj, mn, syserr));
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
// This is an internal function; 'type' should be pre-checked if it has a correct value.
|
|
// This doesn't have argument of GroupType due to header file conflicts.
|
|
|
|
// [[using locked(s_UDTUnited.m_GlobControlLock)]]
|
|
srt::CUDTGroup& srt::CUDT::newGroup(const int type)
|
|
{
|
|
const SRTSOCKET id = uglobal().generateSocketID(true);
|
|
|
|
// Now map the group
|
|
return uglobal().addGroup(id, SRT_GROUP_TYPE(type)).set_id(id);
|
|
}
|
|
|
|
SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt)
|
|
{
|
|
// Doing the same lazy-startup as with srt_create_socket()
|
|
if (!uglobal().m_bGCStatus)
|
|
uglobal().startup();
|
|
|
|
try
|
|
{
|
|
srt::sync::ScopedLock globlock(uglobal().m_GlobControlLock);
|
|
return newGroup(gt).id();
|
|
// Note: potentially, after this function exits, the group
|
|
// could be deleted, immediately, from a separate thread (tho
|
|
// unlikely because the other thread would need some handle to
|
|
// keep it). But then, the first call to any API function would
|
|
// return invalid ID error.
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (...)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
|
|
return SRT_INVALID_SOCK;
|
|
}
|
|
|
|
// [[using locked(m_ControlLock)]]
|
|
// [[using locked(CUDT::s_UDTUnited.m_GlobControlLock)]]
|
|
void srt::CUDTSocket::removeFromGroup(bool broken)
|
|
{
|
|
CUDTGroup* g = m_GroupOf;
|
|
if (g)
|
|
{
|
|
// Reset group-related fields immediately. They won't be accessed
|
|
// in the below calls, while the iterator will be invalidated for
|
|
// a short moment between removal from the group container and the end,
|
|
// while the GroupLock would be already taken out. It is safer to reset
|
|
// it to a NULL iterator before removal.
|
|
m_GroupOf = NULL;
|
|
m_GroupMemberData = NULL;
|
|
|
|
bool still_have = g->remove(m_SocketID);
|
|
if (broken)
|
|
{
|
|
// Activate the SRT_EPOLL_UPDATE event on the group
|
|
// if it was because of a socket that was earlier connected
|
|
// and became broken. This is not to be sent in case when
|
|
// it is a failure during connection, or the socket was
|
|
// explicitly removed from the group.
|
|
g->activateUpdateEvent(still_have);
|
|
}
|
|
|
|
HLOGC(smlog.Debug,
|
|
log << "removeFromGroup: socket @" << m_SocketID << " NO LONGER A MEMBER of $" << g->id() << "; group is "
|
|
<< (still_have ? "still ACTIVE" : "now EMPTY"));
|
|
}
|
|
}
|
|
|
|
SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket)
|
|
{
|
|
// Lock this for the whole function as we need the group
|
|
// to persist the call.
|
|
ScopedLock glock(uglobal().m_GlobControlLock);
|
|
CUDTSocket* s = uglobal().locateSocket_LOCKED(socket);
|
|
if (!s || !s->m_GroupOf)
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
return s->m_GroupOf->id();
|
|
}
|
|
|
|
int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize)
|
|
{
|
|
if ((groupid & SRTGROUP_MASK) == 0 || !psize)
|
|
{
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
CUDTUnited::GroupKeeper k(uglobal(), groupid, CUDTUnited::ERH_RETURN);
|
|
if (!k.group)
|
|
{
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
// To get only the size of the group pdata=NULL can be used
|
|
return k.group->getGroupData(pdata, psize);
|
|
}
|
|
#endif
|
|
|
|
int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen)
|
|
{
|
|
try
|
|
{
|
|
sockaddr_any sa(name, namelen);
|
|
if (sa.len == 0)
|
|
{
|
|
// This happens if the namelen check proved it to be
|
|
// too small for particular family, or that family is
|
|
// not recognized (is none of AF_INET, AF_INET6).
|
|
// This is a user error.
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
CUDTSocket* s = uglobal().locateSocket(u);
|
|
if (!s)
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
return uglobal().bind(s, sa);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "bind: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock)
|
|
{
|
|
try
|
|
{
|
|
CUDTSocket* s = uglobal().locateSocket(u);
|
|
if (!s)
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
return uglobal().bind(s, udpsock);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "bind/udp: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::listen(SRTSOCKET u, int backlog)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().listen(u, backlog);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "listen: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().accept_bond(listeners, lsize, msTimeOut);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return INVALID_SOCK;
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0));
|
|
return INVALID_SOCK;
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "accept_bond: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0));
|
|
return INVALID_SOCK;
|
|
}
|
|
}
|
|
|
|
SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().accept(u, addr, addrlen);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return INVALID_SOCK;
|
|
}
|
|
catch (const bad_alloc&)
|
|
{
|
|
SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0));
|
|
return INVALID_SOCK;
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "accept: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0));
|
|
return INVALID_SOCK;
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().connect(u, name, tname, namelen);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize)
|
|
{
|
|
if (arraysize <= 0)
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
if ((grp & SRTGROUP_MASK) == 0)
|
|
{
|
|
// connectLinks accepts only GROUP id, not socket id.
|
|
return APIError(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), grp, CUDTUnited::ERH_THROW);
|
|
return uglobal().groupConnect(k.group, targets, arraysize);
|
|
}
|
|
catch (CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().connect(u, name, namelen, forced_isn);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::close(SRTSOCKET u)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().close(u);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "close: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen)
|
|
{
|
|
try
|
|
{
|
|
uglobal().getpeername(u, name, namelen);
|
|
return 0;
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "getpeername: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen)
|
|
{
|
|
try
|
|
{
|
|
uglobal().getsockname(u, name, namelen);
|
|
return 0;
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "getsockname: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen)
|
|
{
|
|
if (!pw_optval || !pw_optlen)
|
|
{
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW);
|
|
k.group->getOpt(optname, (pw_optval), (*pw_optlen));
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core();
|
|
udt.getOpt(optname, (pw_optval), (*pw_optlen));
|
|
return 0;
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "getsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen)
|
|
{
|
|
if (!optval)
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
try
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW);
|
|
k.group->setOpt(optname, optval, optlen);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core();
|
|
udt.setOpt(optname, optval, optlen);
|
|
return 0;
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "setsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
return sendmsg2(u, buf, len, (mctrl));
|
|
}
|
|
|
|
// --> CUDT::recv moved down
|
|
|
|
int srt::CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
mctrl.msgttl = ttl;
|
|
mctrl.inorder = inorder;
|
|
mctrl.srctime = srctime;
|
|
return sendmsg2(u, buf, len, (mctrl));
|
|
}
|
|
|
|
int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m)
|
|
{
|
|
try
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW);
|
|
return k.group->send(buf, len, (w_m));
|
|
}
|
|
#endif
|
|
|
|
return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().sendmsg2(buf, len, (w_m));
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::recv(SRTSOCKET u, char* buf, int len, int)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
int ret = recvmsg2(u, buf, len, (mctrl));
|
|
return ret;
|
|
}
|
|
|
|
int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
int ret = recvmsg2(u, buf, len, (mctrl));
|
|
srctime = mctrl.srctime;
|
|
return ret;
|
|
}
|
|
|
|
int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m)
|
|
{
|
|
try
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW);
|
|
return k.group->recv(buf, len, (w_m));
|
|
}
|
|
#endif
|
|
|
|
return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().recvmsg2(buf, len, (w_m));
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block)
|
|
{
|
|
try
|
|
{
|
|
CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core();
|
|
return udt.sendfile(ifs, offset, size, block);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "sendfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().recvfile(ofs, offset, size, block);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "recvfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout)
|
|
{
|
|
if ((!readfds) && (!writefds) && (!exceptfds))
|
|
{
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
return uglobal().select(readfds, writefds, exceptfds, timeout);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "select: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::selectEx(const vector<SRTSOCKET>& fds,
|
|
vector<SRTSOCKET>* readfds,
|
|
vector<SRTSOCKET>* writefds,
|
|
vector<SRTSOCKET>* exceptfds,
|
|
int64_t msTimeOut)
|
|
{
|
|
if ((!readfds) && (!writefds) && (!exceptfds))
|
|
{
|
|
return APIError(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
try
|
|
{
|
|
return uglobal().selectEx(fds, readfds, writefds, exceptfds, msTimeOut);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (bad_alloc&)
|
|
{
|
|
return APIError(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "selectEx: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_create()
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_create();
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_create: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_clear_usocks(int eid)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_clear_usocks(eid);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal,
|
|
log << "epoll_clear_usocks: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_add_usock(eid, u, events);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_add_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_add_ssock(eid, s, events);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_add_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_add_usock(eid, u, events);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal,
|
|
log << "epoll_update_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_update_ssock(eid, s, events);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal,
|
|
log << "epoll_update_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_remove_usock(eid, u);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal,
|
|
log << "epoll_remove_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_remove_ssock(eid, s);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal,
|
|
log << "epoll_remove_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_wait(const int eid,
|
|
set<SRTSOCKET>* readfds,
|
|
set<SRTSOCKET>* writefds,
|
|
int64_t msTimeOut,
|
|
set<SYSSOCKET>* lrfds,
|
|
set<SYSSOCKET>* lwfds)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_ref().wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_wait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_uwait(eid, fdsSet, fdsSize, msTimeOut);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_uwait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int32_t srt::CUDT::epoll_set(const int eid, int32_t flags)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_set(eid, flags);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_set: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
int srt::CUDT::epoll_release(const int eid)
|
|
{
|
|
try
|
|
{
|
|
return uglobal().epoll_release(eid);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "epoll_release: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
srt::CUDTException& srt::CUDT::getlasterror()
|
|
{
|
|
return GetThreadLocalError();
|
|
}
|
|
|
|
int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous)
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (u & SRTGROUP_MASK)
|
|
return groupsockbstats(u, perf, clear);
|
|
#endif
|
|
|
|
try
|
|
{
|
|
CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core();
|
|
udt.bstats(perf, clear, instantaneous);
|
|
return 0;
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
return APIError(e);
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
return APIError(MJ_UNKNOWN, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
#if ENABLE_BONDING
|
|
int srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear)
|
|
{
|
|
try
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW);
|
|
k.group->bstatsSocket(perf, clear);
|
|
return 0;
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return ERROR;
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0));
|
|
return ERROR;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u)
|
|
{
|
|
try
|
|
{
|
|
return &uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core();
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return NULL;
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "getUDTHandle: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
vector<SRTSOCKET> srt::CUDT::existingSockets()
|
|
{
|
|
vector<SRTSOCKET> out;
|
|
for (CUDTUnited::sockets_t::iterator i = uglobal().m_Sockets.begin(); i != uglobal().m_Sockets.end(); ++i)
|
|
{
|
|
out.push_back(i->first);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u)
|
|
{
|
|
try
|
|
{
|
|
#if ENABLE_BONDING
|
|
if (isgroup(u))
|
|
{
|
|
CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW);
|
|
return k.group->getStatus();
|
|
}
|
|
#endif
|
|
return uglobal().getStatus(u);
|
|
}
|
|
catch (const CUDTException& e)
|
|
{
|
|
SetThreadLocalError(e);
|
|
return SRTS_NONEXIST;
|
|
}
|
|
catch (const std::exception& ee)
|
|
{
|
|
LOGC(aclog.Fatal, log << "getsockstate: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what());
|
|
SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0));
|
|
return SRTS_NONEXIST;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace UDT
|
|
{
|
|
|
|
int startup()
|
|
{
|
|
return srt::CUDT::startup();
|
|
}
|
|
|
|
int cleanup()
|
|
{
|
|
return srt::CUDT::cleanup();
|
|
}
|
|
|
|
int bind(SRTSOCKET u, const struct sockaddr* name, int namelen)
|
|
{
|
|
return srt::CUDT::bind(u, name, namelen);
|
|
}
|
|
|
|
int bind2(SRTSOCKET u, UDPSOCKET udpsock)
|
|
{
|
|
return srt::CUDT::bind(u, udpsock);
|
|
}
|
|
|
|
int listen(SRTSOCKET u, int backlog)
|
|
{
|
|
return srt::CUDT::listen(u, backlog);
|
|
}
|
|
|
|
SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen)
|
|
{
|
|
return srt::CUDT::accept(u, addr, addrlen);
|
|
}
|
|
|
|
int connect(SRTSOCKET u, const struct sockaddr* name, int namelen)
|
|
{
|
|
return srt::CUDT::connect(u, name, namelen, SRT_SEQNO_NONE);
|
|
}
|
|
|
|
int close(SRTSOCKET u)
|
|
{
|
|
return srt::CUDT::close(u);
|
|
}
|
|
|
|
int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen)
|
|
{
|
|
return srt::CUDT::getpeername(u, name, namelen);
|
|
}
|
|
|
|
int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen)
|
|
{
|
|
return srt::CUDT::getsockname(u, name, namelen);
|
|
}
|
|
|
|
int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen)
|
|
{
|
|
return srt::CUDT::getsockopt(u, level, optname, optval, optlen);
|
|
}
|
|
|
|
int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen)
|
|
{
|
|
return srt::CUDT::setsockopt(u, level, optname, optval, optlen);
|
|
}
|
|
|
|
// DEVELOPER API
|
|
|
|
int connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn)
|
|
{
|
|
return srt::CUDT::connect(u, name, namelen, forced_isn);
|
|
}
|
|
|
|
int send(SRTSOCKET u, const char* buf, int len, int flags)
|
|
{
|
|
return srt::CUDT::send(u, buf, len, flags);
|
|
}
|
|
|
|
int recv(SRTSOCKET u, char* buf, int len, int flags)
|
|
{
|
|
return srt::CUDT::recv(u, buf, len, flags);
|
|
}
|
|
|
|
int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime)
|
|
{
|
|
return srt::CUDT::sendmsg(u, buf, len, ttl, inorder, srctime);
|
|
}
|
|
|
|
int recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime)
|
|
{
|
|
return srt::CUDT::recvmsg(u, buf, len, srctime);
|
|
}
|
|
|
|
int recvmsg(SRTSOCKET u, char* buf, int len)
|
|
{
|
|
int64_t srctime;
|
|
return srt::CUDT::recvmsg(u, buf, len, srctime);
|
|
}
|
|
|
|
int64_t sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block)
|
|
{
|
|
return srt::CUDT::sendfile(u, ifs, offset, size, block);
|
|
}
|
|
|
|
int64_t recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block)
|
|
{
|
|
return srt::CUDT::recvfile(u, ofs, offset, size, block);
|
|
}
|
|
|
|
int64_t sendfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block)
|
|
{
|
|
fstream ifs(path, ios::binary | ios::in);
|
|
int64_t ret = srt::CUDT::sendfile(u, ifs, *offset, size, block);
|
|
ifs.close();
|
|
return ret;
|
|
}
|
|
|
|
int64_t recvfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block)
|
|
{
|
|
fstream ofs(path, ios::binary | ios::out);
|
|
int64_t ret = srt::CUDT::recvfile(u, ofs, *offset, size, block);
|
|
ofs.close();
|
|
return ret;
|
|
}
|
|
|
|
int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout)
|
|
{
|
|
return srt::CUDT::select(nfds, readfds, writefds, exceptfds, timeout);
|
|
}
|
|
|
|
int selectEx(const vector<SRTSOCKET>& fds,
|
|
vector<SRTSOCKET>* readfds,
|
|
vector<SRTSOCKET>* writefds,
|
|
vector<SRTSOCKET>* exceptfds,
|
|
int64_t msTimeOut)
|
|
{
|
|
return srt::CUDT::selectEx(fds, readfds, writefds, exceptfds, msTimeOut);
|
|
}
|
|
|
|
int epoll_create()
|
|
{
|
|
return srt::CUDT::epoll_create();
|
|
}
|
|
|
|
int epoll_clear_usocks(int eid)
|
|
{
|
|
return srt::CUDT::epoll_clear_usocks(eid);
|
|
}
|
|
|
|
int epoll_add_usock(int eid, SRTSOCKET u, const int* events)
|
|
{
|
|
return srt::CUDT::epoll_add_usock(eid, u, events);
|
|
}
|
|
|
|
int epoll_add_ssock(int eid, SYSSOCKET s, const int* events)
|
|
{
|
|
return srt::CUDT::epoll_add_ssock(eid, s, events);
|
|
}
|
|
|
|
int epoll_update_usock(int eid, SRTSOCKET u, const int* events)
|
|
{
|
|
return srt::CUDT::epoll_update_usock(eid, u, events);
|
|
}
|
|
|
|
int epoll_update_ssock(int eid, SYSSOCKET s, const int* events)
|
|
{
|
|
return srt::CUDT::epoll_update_ssock(eid, s, events);
|
|
}
|
|
|
|
int epoll_remove_usock(int eid, SRTSOCKET u)
|
|
{
|
|
return srt::CUDT::epoll_remove_usock(eid, u);
|
|
}
|
|
|
|
int epoll_remove_ssock(int eid, SYSSOCKET s)
|
|
{
|
|
return srt::CUDT::epoll_remove_ssock(eid, s);
|
|
}
|
|
|
|
int epoll_wait(int eid,
|
|
set<SRTSOCKET>* readfds,
|
|
set<SRTSOCKET>* writefds,
|
|
int64_t msTimeOut,
|
|
set<SYSSOCKET>* lrfds,
|
|
set<SYSSOCKET>* lwfds)
|
|
{
|
|
return srt::CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds);
|
|
}
|
|
|
|
template <class SOCKTYPE>
|
|
inline void set_result(set<SOCKTYPE>* val, int* num, SOCKTYPE* fds)
|
|
{
|
|
if (!val || !num || !fds)
|
|
return;
|
|
|
|
if (*num > int(val->size()))
|
|
*num = int(val->size()); // will get 0 if val->empty()
|
|
int count = 0;
|
|
|
|
// This loop will run 0 times if val->empty()
|
|
for (typename set<SOCKTYPE>::const_iterator it = val->begin(); it != val->end(); ++it)
|
|
{
|
|
if (count >= *num)
|
|
break;
|
|
fds[count++] = *it;
|
|
}
|
|
}
|
|
|
|
int epoll_wait2(int eid,
|
|
SRTSOCKET* readfds,
|
|
int* rnum,
|
|
SRTSOCKET* writefds,
|
|
int* wnum,
|
|
int64_t msTimeOut,
|
|
SYSSOCKET* lrfds,
|
|
int* lrnum,
|
|
SYSSOCKET* lwfds,
|
|
int* lwnum)
|
|
{
|
|
// This API is an alternative format for epoll_wait, created for
|
|
// compatibility with other languages. Users need to pass in an array
|
|
// for holding the returned sockets, with the maximum array length
|
|
// stored in *rnum, etc., which will be updated with returned number
|
|
// of sockets.
|
|
|
|
set<SRTSOCKET> readset;
|
|
set<SRTSOCKET> writeset;
|
|
set<SYSSOCKET> lrset;
|
|
set<SYSSOCKET> lwset;
|
|
set<SRTSOCKET>* rval = NULL;
|
|
set<SRTSOCKET>* wval = NULL;
|
|
set<SYSSOCKET>* lrval = NULL;
|
|
set<SYSSOCKET>* lwval = NULL;
|
|
if ((readfds != NULL) && (rnum != NULL))
|
|
rval = &readset;
|
|
if ((writefds != NULL) && (wnum != NULL))
|
|
wval = &writeset;
|
|
if ((lrfds != NULL) && (lrnum != NULL))
|
|
lrval = &lrset;
|
|
if ((lwfds != NULL) && (lwnum != NULL))
|
|
lwval = &lwset;
|
|
|
|
int ret = srt::CUDT::epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval);
|
|
if (ret > 0)
|
|
{
|
|
// set<SRTSOCKET>::const_iterator i;
|
|
// SET_RESULT(rval, rnum, readfds, i);
|
|
set_result(rval, rnum, readfds);
|
|
// SET_RESULT(wval, wnum, writefds, i);
|
|
set_result(wval, wnum, writefds);
|
|
|
|
// set<SYSSOCKET>::const_iterator j;
|
|
// SET_RESULT(lrval, lrnum, lrfds, j);
|
|
set_result(lrval, lrnum, lrfds);
|
|
// SET_RESULT(lwval, lwnum, lwfds, j);
|
|
set_result(lwval, lwnum, lwfds);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut)
|
|
{
|
|
return srt::CUDT::epoll_uwait(eid, fdsSet, fdsSize, msTimeOut);
|
|
}
|
|
|
|
int epoll_release(int eid)
|
|
{
|
|
return srt::CUDT::epoll_release(eid);
|
|
}
|
|
|
|
ERRORINFO& getlasterror()
|
|
{
|
|
return srt::CUDT::getlasterror();
|
|
}
|
|
|
|
int getlasterror_code()
|
|
{
|
|
return srt::CUDT::getlasterror().getErrorCode();
|
|
}
|
|
|
|
const char* getlasterror_desc()
|
|
{
|
|
return srt::CUDT::getlasterror().getErrorMessage();
|
|
}
|
|
|
|
int getlasterror_errno()
|
|
{
|
|
return srt::CUDT::getlasterror().getErrno();
|
|
}
|
|
|
|
// Get error string of a given error code
|
|
const char* geterror_desc(int code, int err)
|
|
{
|
|
srt::CUDTException e(CodeMajor(code / 1000), CodeMinor(code % 1000), err);
|
|
return (e.getErrorMessage());
|
|
}
|
|
|
|
int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear)
|
|
{
|
|
return srt::CUDT::bstats(u, perf, clear);
|
|
}
|
|
|
|
SRT_SOCKSTATUS getsockstate(SRTSOCKET u)
|
|
{
|
|
return srt::CUDT::getsockstate(u);
|
|
}
|
|
|
|
} // namespace UDT
|
|
|
|
namespace srt
|
|
{
|
|
|
|
void setloglevel(LogLevel::type ll)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.max_level = ll;
|
|
}
|
|
|
|
void addlogfa(LogFA fa)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.enabled_fa.set(fa, true);
|
|
}
|
|
|
|
void dellogfa(LogFA fa)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.enabled_fa.set(fa, false);
|
|
}
|
|
|
|
void resetlogfa(set<LogFA> fas)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
for (int i = 0; i <= SRT_LOGFA_LASTNONE; ++i)
|
|
srt_logger_config.enabled_fa.set(i, fas.count(i));
|
|
}
|
|
|
|
void resetlogfa(const int* fara, size_t fara_size)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.enabled_fa.reset();
|
|
for (const int* i = fara; i != fara + fara_size; ++i)
|
|
srt_logger_config.enabled_fa.set(*i, true);
|
|
}
|
|
|
|
void setlogstream(std::ostream& stream)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.log_stream = &stream;
|
|
}
|
|
|
|
void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.loghandler_opaque = opaque;
|
|
srt_logger_config.loghandler_fn = handler;
|
|
}
|
|
|
|
void setlogflags(int flags)
|
|
{
|
|
ScopedLock gg(srt_logger_config.mutex);
|
|
srt_logger_config.flags = flags;
|
|
}
|
|
|
|
SRT_API bool setstreamid(SRTSOCKET u, const std::string& sid)
|
|
{
|
|
return CUDT::setstreamid(u, sid);
|
|
}
|
|
SRT_API std::string getstreamid(SRTSOCKET u)
|
|
{
|
|
return CUDT::getstreamid(u);
|
|
}
|
|
|
|
int getrejectreason(SRTSOCKET u)
|
|
{
|
|
return CUDT::rejectReason(u);
|
|
}
|
|
|
|
int setrejectreason(SRTSOCKET u, int value)
|
|
{
|
|
return CUDT::rejectReason(u, value);
|
|
}
|
|
|
|
} // namespace srt
|