/* * 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 02/28/2012 modified by Haivision Systems Inc. *****************************************************************************/ // Set this to 5 to fake "lost" handshake packets 5 times in a row #define SRT_ENABLE_FAKE_LOSS_HS 0 #include "platform_sys.h" #include "srt_attr_defs.h" // Linux specific #ifdef SRT_ENABLE_BINDTODEVICE #include #endif #if !HAVE_CXX11 // for pthread_once #include #endif // Again, just in case when some "smart guy" provided such a global macro #ifdef min #undef min #endif #ifdef max #undef max #endif #include #include #include #include #include #include "srt.h" #include "access_control.h" // Required for SRT_REJX_FALLBACK #include "queue.h" #include "api.h" #include "core.h" #include "logging.h" #include "crypto.h" #include "logging_api.h" // Required due to containing extern srt_logger_config #include "logger_fas.h" using namespace std; using namespace srt::sync; using namespace srt::logging; using namespace hvu; // ofmt #if HVU_ENABLE_LOGGING static const char* const s_hs_side[] = { "DRAW", "INITIATOR", "RESPONDER" }; #endif namespace srt { static inline char fmt_onoff(bool val) { return val ? '+' : '-'; } // Mark unused because it's only used in HLOGC SRT_ATR_UNUSED static inline const char* fmt_yesno(bool val) { return val ? "yes" : "no"; } const size_t SRT_CMD_HSREQ_MINSZ = 8; // Minimum Compatible (1.x.x) packet size (bytes) const size_t SRT_CMD_HSREQ_SZ = 12; // Current version packet size SRT_STATIC_ASSERT(SRT_CMD_HSREQ_SZ <= SRT_CMD_MAXSZ, "error: SRT_CMD_MAXSZ too small"); // Handshake Request (Network Order) // 0[31..0]: SRT version SRT_DEF_VERSION // 1[31..0]: Options 0 [ | SRT_OPT_TSBPDSND ][ | SRT_OPT_HAICRYPT ] // 2[31..16]: TsbPD resv 0 // 2[15..0]: TsbPD delay [0..60000] msec // //#define SRT_CMD_HSRSP 2 /* SRT Handshake Response (receiver) */ const size_t SRT_CMD_HSRSP_MINSZ = 8; // Minimum Compatible (1.x.x) packet size (bytes) const size_t SRT_CMD_HSRSP_SZ = 12; // Current version packet size */ SRT_STATIC_ASSERT(SRT_CMD_HSRSP_SZ <= SRT_CMD_MAXSZ, " error: SRT_CMD_MAXSZ too small"); // Handshake Response (Network Order) // 0[31..0]: SRT version SRT_DEF_VERSION // 1[31..0]: Options 0 [ | SRT_OPT_TSBPDRCV [| SRT_OPT_TLPKTDROP ]][ | SRT_OPT_HAICRYPT] // [ | SRT_OPT_NAKREPORT ] [ | SRT_OPT_REXMITFLG ] // 2[31..16]: TsbPD resv 0 // 2[15..0]: TsbPD delay [0..60000] msec // // IMPORTANT!!! This array must be ordered by value, because std::binary_search is performed on it! extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = { SRTO_SNDSYN, SRTO_RCVSYN, SRTO_LINGER, SRTO_SNDTIMEO, SRTO_RCVTIMEO, SRTO_MAXBW, SRTO_INPUTBW, SRTO_OHEADBW, SRTO_SNDDROPDELAY, SRTO_DRIFTTRACER, SRTO_MININPUTBW, SRTO_LOSSMAXTTL, SRTO_MAXREXMITBW }; const int32_t SRTO_R_PREBIND = BIT(0), //< cannot be modified after srt_bind() SRTO_R_PRE = BIT(1), //< cannot be modified after connection is established SRTO_POST_SPEC = BIT(2); //< executes some action after setting the option struct SrtOptionAction { int flags[SRTO_E_SIZE]; std::map private_default; SrtOptionAction() { // Set everything to 0 to clear all flags // When an option isn't present here, it means that: // * it is not settable, or // * the option is POST (non-restricted) // * it has no post-actions // The post-action may be defined independently on restrictions. memset(flags, 0, sizeof flags); flags[SRTO_MSS] = SRTO_R_PREBIND; flags[SRTO_FC] = SRTO_R_PRE; flags[SRTO_SNDBUF] = SRTO_R_PREBIND; flags[SRTO_RCVBUF] = SRTO_R_PREBIND; flags[SRTO_UDP_SNDBUF] = SRTO_R_PREBIND; flags[SRTO_UDP_RCVBUF] = SRTO_R_PREBIND; flags[SRTO_RENDEZVOUS] = SRTO_R_PRE; flags[SRTO_REUSEADDR] = SRTO_R_PREBIND; flags[SRTO_MAXBW] = SRTO_POST_SPEC; flags[SRTO_SENDER] = SRTO_R_PRE; flags[SRTO_TSBPDMODE] = SRTO_R_PRE; flags[SRTO_LATENCY] = SRTO_R_PRE; flags[SRTO_INPUTBW] = SRTO_POST_SPEC; flags[SRTO_MININPUTBW] = SRTO_POST_SPEC; flags[SRTO_OHEADBW] = SRTO_POST_SPEC; flags[SRTO_PASSPHRASE] = SRTO_R_PRE; flags[SRTO_PBKEYLEN] = SRTO_R_PRE; flags[SRTO_IPTTL] = SRTO_R_PREBIND; flags[SRTO_IPTOS] = SRTO_R_PREBIND; flags[SRTO_TLPKTDROP] = SRTO_R_PRE; flags[SRTO_SNDDROPDELAY] = SRTO_POST_SPEC; flags[SRTO_NAKREPORT] = SRTO_R_PRE; flags[SRTO_VERSION] = SRTO_R_PRE; flags[SRTO_CONNTIMEO] = SRTO_R_PRE; flags[SRTO_LOSSMAXTTL] = SRTO_POST_SPEC; flags[SRTO_RCVLATENCY] = SRTO_R_PRE; flags[SRTO_PEERLATENCY] = SRTO_R_PRE; flags[SRTO_MINVERSION] = SRTO_R_PRE; flags[SRTO_STREAMID] = SRTO_R_PRE; flags[SRTO_CONGESTION] = SRTO_R_PRE; flags[SRTO_MESSAGEAPI] = SRTO_R_PRE; flags[SRTO_PAYLOADSIZE] = SRTO_R_PRE; flags[SRTO_TRANSTYPE] = SRTO_R_PREBIND; flags[SRTO_KMREFRESHRATE] = SRTO_R_PRE; flags[SRTO_KMPREANNOUNCE] = SRTO_R_PRE; flags[SRTO_ENFORCEDENCRYPTION] = SRTO_R_PRE; flags[SRTO_IPV6ONLY] = SRTO_R_PREBIND; flags[SRTO_PEERIDLETIMEO] = SRTO_R_PRE; #ifdef SRT_ENABLE_BINDTODEVICE flags[SRTO_BINDTODEVICE] = SRTO_R_PREBIND; #endif #if SRT_ENABLE_BONDING flags[SRTO_GROUPCONNECT] = SRTO_R_PRE; flags[SRTO_GROUPMINSTABLETIMEO]= SRTO_R_PRE; #endif flags[SRTO_PACKETFILTER] = SRTO_R_PRE; flags[SRTO_RETRANSMITALGO] = SRTO_R_PRE; #ifdef SRT_ENABLE_AEAD flags[SRTO_CRYPTOMODE] = SRTO_R_PRE; #endif // For "private" options (not derived from the listener // socket by an accepted socket) provide below private_default // to which these options will be reset after blindly // copying the option object from the listener socket. // Note that this option cannot have runtime-dependent // default value, like options affected by SRTO_TRANSTYPE. // Options may be of different types, but this value should be only // used as a source of the value. For example, in case of int64_t you'd // have to place here a string of 8 characters. It should be copied // always in the hardware order, as this is what will be directly // passed to a setting function. private_default[SRTO_STREAMID] = string(); } }; const SrtOptionAction s_sockopt_action; CUDTUnited& CUDT::uglobal() { static CUDTUnited instance; return instance; } #ifdef SRT_ENABLE_RATE_MEASUREMENT void RateMeasurement::pickup(const clock_time& time) { // Check if the time has passed the period. clock_interval diff; if (!passedInterval(time, (diff))) { HLOGC(bslog.Debug, log << "RateMeasurement: too early, " << FormatDuration(diff) << " with begin TS=" << FormatTime(m_beginTime) << " now=" << FormatTime(time)); // Still collect data for the current period. return; } // Doesn't matter what `diff` really is. Enough that we have // this time aligned with 10ms resolution calls 10 times. Slice slice; { // Lock for the moment of taking the data sync::ScopedLock lk (m_lock); slice.packets = m_nInstaPackets; slice.bytes = m_nInstaBytes; slice.begin_time = m_beginTime; m_nInstaPackets = 0; m_nInstaBytes = 0; m_beginTime = time; } HLOGC(bslog.Debug, log << "RateMeasurement: passed " << FormatDuration(diff) << " extracted packets=" << slice.packets << " bytes=" << slice.bytes << " now beginTS=" << FormatTime(m_beginTime)); // Ok, now stickpile the slice. // If the container is empty, do not stockpile empty slices. if (m_slices.empty() && slice.bytes == 0) { HLOGC(bslog.Debug, log << "... NOT COLLECTING packet with " << slice.bytes << " bytes to " << m_slices.size() << " total"); // Empty-to-empty: DO NOT stockpile. // NOTE: the container is never cleared when it has // any older data, so this situation is only possible: // - in the beginning // - in the long run if it happened that 10 slices in // a row were empty. // So in this case we simply wait for having the first // packets with something notified. We also know that // the rate was initially 0 and it remains so now. return; } // We either have something and have pushed an empty slice // or we have pushed filled slice onto the empty container. // Still, we need at least 5 samples to start measuring. m_slices.push_back(slice); // NOTE: this is also a situation that might have happened // in the beginning, or after a long period without sending. // If that period happens, the sending rate is also cleared, // in any case we need to wait for the minimum samples. if (m_slices.size() < MIN_SLICES) { HLOGC(bslog.Debug, log << "... NOT MEASURING: still " << m_slices.size() << " < 5 slices"); return; } // Keep track of only 10 last slices if (m_slices.size() > MAX_SLICES) { m_slices.pop_front(); // After a slice was removed, there's already a chance // that all other slices are also 0. Check the minimum // number of slices + 1 and drop all that are empty size_t end_range = 0; // default: nothing to delete for (size_t i = 0; i < m_slices.size(); ++i) { if (i > MIN_SLICES + 1) break; if (m_slices[i].bytes > 0) break; // This can be skipped and check the next one. end_range = i; } // PARANOID if (end_range >= m_slices.size()) end_range = m_slices.size() - 1; // at least 1 element must stay m_slices.erase(m_slices.begin(), m_slices.begin() + end_range); m_beginTime = m_slices[0].begin_time; HLOGC(bslog.Debug, log << "... Dropping 1 oldest + " << end_range << " empty slices, TS=" << FormatTime(m_beginTime)); } // Check also if the begin time is older than 2* period; if so, // reset it to that value if (m_slices.size() > 1) // with 1 always m_beginTime = m_slices[0].begin_time { clock_time earliest = m_slices[1].begin_time - milliseconds_from(SLICE_INTERVAL_MS*2); if (m_beginTime < earliest) { HLOGC(bslog.Debug, log << "... BEGIN TIME too old TS=" << FormatTime(m_beginTime) << " - fixed to " << FormatTime(earliest)); m_beginTime = earliest; m_slices[0].begin_time = earliest; } } // Ok, so we have enough samples to record the measurement. // (so we have at least one) Summary sum (m_slices[0].begin_time); for (size_t i = 0; i < m_slices.size(); ++i) { sum.consume(m_slices[i]); } m_currentPktRate = sum.ratePackets(time); m_currentByteRate = sum.rateBytes(time, m_SysHeaderSize + CPacket::HDR_SIZE); HLOGC(bslog.Debug, log << "... CALCULATED from " << m_slices.size() << " slices: total TD=" << FormatDuration(time - m_slices[0].begin_time) << " packets=" << sum.packets << " bytes=" << sum.bytes << " pkt-rate=" << m_currentPktRate << " byte-rate=" << m_currentByteRate); } #endif SRT_TSA_DISABLED void CUDT::construct() { m_pSndBuffer = NULL; m_pRcvBuffer = NULL; m_pSndLossList = NULL; m_pRcvLossList = NULL; m_iReorderTolerance = 0; // How many times so far the packet considered lost has been received // before TTL expires. m_iConsecEarlyDelivery = 0; m_iConsecOrderedDelivery = 0; m_pMuxer = NULL; m_MuxNode = SocketHolder::none(); // Mark that it doesn't belong to any multiplexer m_TransferIPVersion = AF_UNSPEC; // Will be set after connection // Will be reset to 0 for HSv5, this value is important for HSv4. m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; m_PeerID = SRT_SOCKID_CONNREQ; m_bOpened = false; m_bListening = false; m_bConnecting = false; m_bConnected = false; m_bClosing = false; m_bShutdown = false; m_bBreaking = false; m_bBroken = false; m_bBreakAsUnstable = false; // TODO: m_iBrokenCounter should be still set to some default. m_bPeerHealth = true; m_RejectReason = SRT_REJ_UNKNOWN; m_tsLastReqTime.store(steady_clock::time_point()); m_SrtHsSide = HSD_DRAW; m_uPeerSrtVersion = 0; // Not defined until connected. m_iTsbPdDelay_ms = 0; m_iPeerTsbPdDelay_ms = 0; m_bPeerTsbPd = false; m_bTsbPd = false; m_bTsbPdNeedsWakeup = false; m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; m_bBufferWasFull = false; m_bManaged = false; // Will be updated on first send #ifdef SRT_ENABLE_MAXREXMITBW m_zSndAveragePacketSize = 0; m_zSndMaxPacketSize = 0; #endif // Initialize mutex and condition variables. initSynch(); // TODO: Uncomment when the callback is implemented. // m_cbPacketArrival.set(this, &CUDT::defaultPacketArrival); } CUDT::CUDT(CUDTSocket* parent) : m_parent(parent) #ifdef SRT_ENABLE_MAXREXMITBW // , m_SndRexmitRate(sync::steady_clock::now()) , m_SndRexmitShaper(CSrtConfig::DEF_MSS) #endif , m_iISN(-1) , m_iPeerISN(-1) // Explicitly calling constructors of Mutex for the sake of thread sanitizer // (without this it cannot precisely declare, where particular mutex was created // and makes it hard to identify the mutex it reported). , m_RcvTsbPdStartupLock() , m_ConnectionLock() , m_SendBlockLock() , m_RcvBufferLock() , m_RecvAckLock() , m_RecvLock() , m_SendLock() , m_RcvLossLock() , m_StatsLock() { construct(); (void)SRT_DEF_VERSION; // Runtime fields #if SRT_ENABLE_BONDING m_HSGroupType = SRT_GTYPE_UNDEFINED; #endif m_bTLPktDrop = true; // Too-late Packet Drop m_pCache = NULL; // This is in order to set it ANY kind of initial value, however // this value should not be used when not connected and should be // updated in the handshake. When this value is 0, it means that // packets shall not be sent, as the other party doesn't have a // room to receive and store it. Therefore this value should be // overridden before any sending happens. m_iFlowWindowSize = 0; } CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) : m_parent(parent) #ifdef SRT_ENABLE_MAXREXMITBW // , m_SndRexmitRate(sync::steady_clock::now()) , m_SndRexmitShaper(CSrtConfig::DEF_MSS) #endif , m_iISN(-1) , m_iPeerISN(-1) // Explicit mutex , m_RcvTsbPdStartupLock() , m_ConnectionLock() , m_SendBlockLock() , m_RcvBufferLock() , m_RecvAckLock() , m_RecvLock() , m_SendLock() , m_RcvLossLock() , m_StatsLock() { construct(); // XXX Consider all below fields (except m_bReuseAddr) to be put // into a separate class for easier copying. m_config = ancestor.m_config; // Reset values that shall not be derived to default ones. // These declarations should be consistent with SRTO_R_PRIVATE flag. for (size_t i = 0; i < Size(s_sockopt_action.flags); ++i) { const string* pdef = map_getp(s_sockopt_action.private_default, SRT_SOCKOPT(i)); if (pdef) { try { // Ignore errors here - this is a development-time granted // value, not user-provided value. m_config.set(SRT_SOCKOPT(i), pdef->data(), (int) pdef->size()); } catch (...) { LOGC(gglog.Error, log << "IPE: failed to set a declared default option!"); } } } m_SrtHsSide = ancestor.m_SrtHsSide; // actually it sets it to HSD_RESPONDER m_bTLPktDrop = ancestor.m_bTLPktDrop; m_iReorderTolerance = m_config.iMaxReorderTolerance; // Initialize with maximum value // Runtime m_pCache = ancestor.m_pCache; } CUDT::~CUDT() { // release mutex/condition variables destroySynch(); // destroy the data structures delete m_pSndBuffer; delete m_pRcvBuffer; delete m_pSndLossList; delete m_pRcvLossList; } void CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); // Match check (confirm optName as index for s_sockopt_action) if (int(optName) < 0 || int(optName) >= int(SRTO_E_SIZE)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); // Restriction check const int oflags = s_sockopt_action.flags[optName]; ScopedLock cg (m_ConnectionLock); ScopedLock sendguard (m_SendLock); ScopedLock recvguard (m_RecvLock); HLOGC(aclog.Debug, log << CONID() << "OPTION: #" << optName << " value:" << FormatBinaryString((uint8_t*)optval, optlen)); if (IsSet(oflags, SRTO_R_PREBIND) && m_bOpened) throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); if (IsSet(oflags, SRTO_R_PRE) && (m_bConnected || m_bConnecting || m_bListening)) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // Option execution. If this returns -1, there's no such option. const int status = m_config.set(optName, optval, optlen); if (status == -1) { LOGC(aclog.Error, log << CONID() << "OPTION: #" << optName << " UNKNOWN"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } // Post-action, if applicable if (IsSet(oflags, SRTO_POST_SPEC) && m_bConnected) { switch (optName) { case SRTO_MAXBW: updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET)); break; case SRTO_INPUTBW: case SRTO_MININPUTBW: updateCC(TEV_INIT, EventVariant(TEV_INIT_INPUTBW)); break; case SRTO_OHEADBW: updateCC(TEV_INIT, EventVariant(TEV_INIT_OHEADBW)); break; case SRTO_LOSSMAXTTL: m_iReorderTolerance = m_config.iMaxReorderTolerance; default: break; } } } void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) { ScopedLock cg(m_ConnectionLock); switch (optName) { case SRTO_MSS: *(int *)optval = m_config.iMSS; optlen = sizeof(int); break; case SRTO_SNDSYN: *(bool *)optval = m_config.bSynSending; optlen = sizeof(bool); break; case SRTO_RCVSYN: *(bool *)optval = m_config.bSynRecving; optlen = sizeof(bool); break; case SRTO_ISN: *(int *)optval = m_iISN; optlen = sizeof(int); break; case SRTO_FC: *(int *)optval = m_config.iFlightFlagSize; optlen = sizeof(int); break; // For SNDBUF/RCVBUF values take the variant that uses more memory. // It is not possible to make sure what "family" is in use without // checking if the socket is bound. This will also be the exact size // of the memory in use. case SRTO_SNDBUF: *(int *)optval = m_config.iSndBufSize * m_config.bytesPerPkt(); optlen = sizeof(int); break; case SRTO_RCVBUF: *(int *)optval = m_config.iRcvBufSize * m_config.bytesPerPkt(); optlen = sizeof(int); break; case SRTO_LINGER: if (optlen < (int)(sizeof(linger))) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(linger *)optval = m_config.Linger; optlen = sizeof(linger); break; case SRTO_UDP_SNDBUF: *(int *)optval = m_config.iUDPSndBufSize; optlen = sizeof(int); break; case SRTO_UDP_RCVBUF: *(int *)optval = m_config.iUDPRcvBufSize; optlen = sizeof(int); break; case SRTO_RENDEZVOUS: *(bool *)optval = m_config.bRendezvous; optlen = sizeof(bool); break; case SRTO_SNDTIMEO: *(int *)optval = m_config.iSndTimeOut; optlen = sizeof(int); break; case SRTO_RCVTIMEO: *(int *)optval = m_config.iRcvTimeOut; optlen = sizeof(int); break; case SRTO_REUSEADDR: *(bool *)optval = m_config.bReuseAddr; optlen = sizeof(bool); break; case SRTO_MAXBW: if (size_t(optlen) < sizeof(m_config.llMaxBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t *)optval = m_config.llMaxBW; optlen = sizeof(int64_t); break; case SRTO_INPUTBW: if (size_t(optlen) < sizeof(m_config.llInputBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t*)optval = m_config.llInputBW; optlen = sizeof(int64_t); break; case SRTO_MININPUTBW: if (size_t(optlen) < sizeof (m_config.llMinInputBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t*)optval = m_config.llMinInputBW; optlen = sizeof(int64_t); break; case SRTO_OHEADBW: *(int32_t *)optval = m_config.iOverheadBW; optlen = sizeof(int32_t); break; #ifdef SRT_ENABLE_MAXREXMITBW case SRTO_MAXREXMITBW: if (size_t(optlen) < sizeof(m_config.llMaxRexmitBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); *(int64_t*)optval = m_config.llMaxRexmitBW; optlen = sizeof(int64_t); break; #endif case SRTO_STATE: *(int32_t *)optval = uglobal().getStatus(m_SocketID); optlen = sizeof(int32_t); break; case SRTO_EVENT: { int32_t event = 0; if (m_bBroken) event |= SRT_EPOLL_ERR; else { enterCS(m_RecvLock); if (m_pRcvBuffer && isRcvBufferReady()) event |= SRT_EPOLL_IN; leaveCS(m_RecvLock); if (m_pSndBuffer && (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize())) event |= SRT_EPOLL_OUT; } *(int32_t *)optval = event; optlen = sizeof(int32_t); break; } case SRTO_SNDDATA: if (m_pSndBuffer) *(int32_t *)optval = m_pSndBuffer->getCurrBufSize(); else *(int32_t *)optval = 0; optlen = sizeof(int32_t); break; case SRTO_RCVDATA: if (m_pRcvBuffer) { ScopedLock lck(m_RcvBufferLock); *(int32_t *)optval = m_pRcvBuffer->getRcvDataSize(); } else *(int32_t *)optval = 0; optlen = sizeof(int32_t); break; case SRTO_IPTTL: if (m_bOpened) *(int32_t *)optval = channel()->getIpTTL(); else *(int32_t *)optval = m_config.iIpTTL; optlen = sizeof(int32_t); break; case SRTO_IPTOS: if (m_bOpened) *(int32_t *)optval = channel()->getIpToS(); else *(int32_t *)optval = m_config.iIpToS; optlen = sizeof(int32_t); break; case SRTO_BINDTODEVICE: #ifdef SRT_ENABLE_BINDTODEVICE if (optlen < IFNAMSIZ) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); if (m_bOpened && channel()->getBind(((char*)optval), optlen)) { optlen = strlen((char*)optval); break; } // Fallback: return from internal data optlen = (int)m_config.sBindToDevice.copy((char*)optval, (size_t)optlen - 1); ((char*)optval)[optlen] = '\0'; #else LOGC(smlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif break; case SRTO_SENDER: *(bool *)optval = m_config.bDataSender; optlen = sizeof(bool); break; case SRTO_TSBPDMODE: *(bool *)optval = m_config.bTSBPD; optlen = sizeof(bool); break; case SRTO_LATENCY: case SRTO_RCVLATENCY: if (m_bConnected) *(int32_t *)optval = m_iTsbPdDelay_ms; else *(int32_t *)optval = m_config.iRcvLatency; optlen = sizeof(int32_t); break; case SRTO_PEERLATENCY: if (m_bConnected) *(int32_t *)optval = m_iPeerTsbPdDelay_ms; else *(int32_t *)optval = m_config.iPeerLatency; optlen = sizeof(int32_t); break; case SRTO_TLPKTDROP: if (m_bConnected) *(bool *)optval = m_bTLPktDrop; else *(bool *)optval = m_config.bTLPktDrop; optlen = sizeof(bool); break; case SRTO_SNDDROPDELAY: *(int32_t *)optval = m_config.iSndDropDelay; optlen = sizeof(int32_t); break; case SRTO_PBKEYLEN: *(int32_t *)optval = (int32_t) m_CryptoControl.keylen(); // Running Key length.(may be 0) optlen = sizeof(int32_t); break; case SRTO_KMSTATE: if (m_config.bDataSender) *(int32_t *)optval = m_CryptoControl.kmState().snd; else *(int32_t *)optval = m_CryptoControl.kmState().rcv; optlen = sizeof(int32_t); break; case SRTO_SNDKMSTATE: // State imposed by Agent depending on PW and KMX *(int32_t *)optval = m_CryptoControl.kmState().snd; optlen = sizeof(int32_t); break; case SRTO_RCVKMSTATE: // State returned by Peer as informed during KMX *(int32_t *)optval = m_CryptoControl.kmState().rcv; optlen = sizeof(int32_t); break; case SRTO_LOSSMAXTTL: *(int32_t*)optval = m_config.iMaxReorderTolerance; optlen = sizeof(int32_t); break; case SRTO_NAKREPORT: *(bool *)optval = m_config.bRcvNakReport; optlen = sizeof(bool); break; case SRTO_VERSION: *(int32_t *)optval = m_config.uSrtVersion; optlen = sizeof(int32_t); break; case SRTO_PEERVERSION: *(int32_t *)optval = m_uPeerSrtVersion; optlen = sizeof(int32_t); break; case SRTO_CONNTIMEO: *(int*)optval = (int) count_milliseconds(m_config.tdConnTimeOut); optlen = sizeof(int); break; case SRTO_DRIFTTRACER: *(bool*)optval = m_config.bDriftTracer; optlen = sizeof(bool); break; case SRTO_MINVERSION: *(uint32_t *)optval = m_config.uMinimumPeerSrtVersion; optlen = sizeof(uint32_t); break; case SRTO_STREAMID: if (size_t(optlen) < m_config.sStreamName.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); optlen = (int)m_config.sStreamName.copy((char*)optval, (size_t)optlen - 1); ((char*)optval)[optlen] = '\0'; break; case SRTO_CONGESTION: if (size_t(optlen) < m_config.sCongestion.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); optlen = (int)m_config.sCongestion.copy((char*)optval, (size_t)optlen - 1); ((char*)optval)[optlen] = '\0'; break; case SRTO_MESSAGEAPI: optlen = sizeof(bool); *(bool *)optval = m_config.bMessageAPI; break; case SRTO_PAYLOADSIZE: optlen = sizeof(int); *(int *)optval = (int) payloadSize(); break; case SRTO_KMREFRESHRATE: optlen = sizeof(int); *(int*)optval = (int)m_config.uKmRefreshRatePkt; break; case SRTO_KMPREANNOUNCE: optlen = sizeof(int); *(int*)optval = (int)m_config.uKmPreAnnouncePkt; break; #if SRT_ENABLE_BONDING case SRTO_GROUPCONNECT: optlen = sizeof (int); *(int*)optval = m_config.iGroupConnect; break; case SRTO_GROUPMINSTABLETIMEO: optlen = sizeof(int); *(int*)optval = (int)m_config.uMinStabilityTimeout_ms; break; case SRTO_GROUPTYPE: optlen = sizeof (int); *(int*)optval = m_HSGroupType; break; #endif case SRTO_ENFORCEDENCRYPTION: optlen = sizeof(bool); *(bool *)optval = m_config.bEnforcedEnc; break; case SRTO_IPV6ONLY: optlen = sizeof(int); *(int *)optval = m_config.iIpV6Only; break; case SRTO_PEERIDLETIMEO: *(int *)optval = m_config.iPeerIdleTimeout_ms; optlen = sizeof(int); break; case SRTO_PACKETFILTER: if (size_t(optlen) < m_config.sPacketFilterConfig.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); optlen = (int)m_config.sPacketFilterConfig.copy((char*)optval, (size_t)optlen - 1); ((char*)optval)[optlen] = '\0'; break; case SRTO_RETRANSMITALGO: *(int32_t *)optval = m_config.iRetransmitAlgo; optlen = sizeof(int32_t); break; #ifdef SRT_ENABLE_AEAD case SRTO_CRYPTOMODE: *(int32_t*)optval = m_CryptoControl.getCryptoMode(); optlen = sizeof(int32_t); break; #endif default: throw CUDTException(MJ_NOTSUP, MN_NONE, 0); } } #if SRT_ENABLE_BONDING SRT_ERRNO CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt) { SRT_SOCKOPT this_opt = SRTO_VERSION; for (size_t i = 0; i < opt.options.size(); ++i) { SRT_SocketOptionObject::SingleOption* o = opt.options[i]; HLOGC(smlog.Debug, log << CONID() << "applyMemberConfigObject: OPTION @" << m_SocketID << " #" << o->option); this_opt = SRT_SOCKOPT(o->option); setOpt(this_opt, o->storage, o->length); } return SRT_SUCCESS; } #endif bool CUDT::setstreamid(SRTSOCKET u, const std::string &sid) { CUDT *that = getUDTHandle(u); if (!that) return false; if (sid.size() > CSrtConfig::MAX_SID_LENGTH) return false; if (that->m_bConnected) return false; that->m_config.sStreamName.set(sid); return true; } string CUDT::getstreamid(SRTSOCKET u) { CUDT *that = getUDTHandle(u); if (!that) return ""; return that->m_config.sStreamName.str(); } // XXX REFACTOR: Make common code for CUDT constructor and clearData, // possibly using CUDT::construct. // Initial sequence number, loss, acknowledgement, etc. void CUDT::clearData() { const size_t full_hdr_size = CPacket::udpHeaderSize(AF_INET) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); m_SndTimeWindow.initialize(full_hdr_size, m_iMaxSRTPayloadSize); m_RcvTimeWindow.initialize(full_hdr_size, m_iMaxSRTPayloadSize); m_iEXPCount = 1; m_iBandwidth = 1; // pkts/sec // XXX use some constant for this 16 m_iDeliveryRate = 16; m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize; m_iAckSeqNo = 0; m_tsLastAckTime = steady_clock::now(); // trace information { ScopedLock stat_lock(m_StatsLock); m_stats.tsStartTime = steady_clock::now(); m_stats.sndr.reset(); m_stats.rcvr.reset(); m_stats.tsLastSampleTime = steady_clock::now(); m_stats.traceReorderDistance = 0; m_stats.traceBelatedTime = 0; m_stats.sndDuration = m_stats.m_sndDurationTotal = 0; } // Resetting these data because this happens when agent isn't connected. m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; // TSBPD as state should be set to FALSE here. // Only when the HSREQ handshake is exchanged, // should they be set to possibly true. m_bTsbPd = false; m_bGroupTsbPd = false; m_iTsbPdDelay_ms = m_config.iRcvLatency; m_bTLPktDrop = m_config.bTLPktDrop; m_bPeerTLPktDrop = false; m_bPeerNakReport = false; m_bPeerRexmitFlag = false; m_RdvState = CHandShake::RDV_INVALID; m_tsRcvPeerStartTime = steady_clock::time_point(); } //This function is being called at the moment when no // thread-related facilities for this socket are running. // Hence thread checking is disabled. SRT_TSA_DISABLED void CUDT::open() { // XXX The above comment says there are no other threads // for that socket running at the moment...? ScopedLock cg(m_ConnectionLock); clearData(); // Set initial values of smoothed RTT and RTT variance. m_iSRTT = INITIAL_RTT; m_iRTTVar = INITIAL_RTTVAR; m_bIsFirstRTTReceived = false; // set minimum NAK and EXP timeout to 300ms m_tdMinNakInterval = milliseconds_from(300); m_tdMinExpInterval = milliseconds_from(300); m_tdACKInterval = microseconds_from(COMM_SYN_INTERVAL_US); m_tdNAKInterval = m_tdMinNakInterval; const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime.store(currtime); m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); #if SRT_ENABLE_BONDING m_tsUnstableSince = steady_clock::time_point(); m_tsFreshActivation = steady_clock::time_point(); m_tsWarySince = steady_clock::time_point(); #endif m_iReXmitCount = 1; memset(&m_aSuppressedMsg, 0, sizeof m_aSuppressedMsg); m_iPktCount = 0; m_iLightACKCount = 1; m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = microseconds_from(0); // Now UDT is opened. m_bOpened = true; } void CUDT::setListenState() { if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); if (m_bConnecting || m_bConnected) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // listen can be called more than once // If two threads call srt_listen at the same time, only // one will pass this condition; others will be rejected. // If it was called ever once, none will pass. for (;;) { if (m_bListening.compare_exchange(false, true)) { // if there is already another socket listening on the same port if (!m_pMuxer->setListener(this)) { // Failed here, so m_bListening = false; throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); } } else { // Ok, this thread could have been blocked access, // but still the other thread that attempted to set // the listener could have failed. Therefore check // again if the listener was set successfully, and // if the listening point is still free, try again. CUDT* current = m_pMuxer->getListener(); if (current == NULL) { continue; } else if (current != this) { // Some other listener already set it throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); } // If it was you who set this, just return with no exception. } break; } } size_t CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version) { if (srtlen < SRT_HS_E_SIZE) { LOGC(cnlog.Fatal, log << CONID() << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS_E_SIZE << ")"); return 0; } srtlen = SRT_HS_E_SIZE; // We use only that much space. memset((aw_srtdata), 0, sizeof(uint32_t) * srtlen); /* Current version (1.x.x) SRT handshake */ aw_srtdata[SRT_HS_VERSION] = m_config.uSrtVersion; /* Required version */ aw_srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities(); switch (msgtype) { case SRT_CMD_HSREQ: return fillSrtHandshake_HSREQ((aw_srtdata), srtlen, hs_version); case SRT_CMD_HSRSP: return fillSrtHandshake_HSRSP((aw_srtdata), srtlen, hs_version); default: LOGC(cnlog.Fatal, log << CONID() << "IPE: fillSrtHandshake/sendSrtMsg called with value " << msgtype); return 0; } } size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // INITIATOR sends HSREQ. // The TSBPD(SND|RCV) options are being set only if the TSBPD is set in the current agent. // The agent has a decisive power only in the range of RECEIVING the data, however it can // also influence the peer's latency. If agent doesn't set TSBPD mode, it doesn't send any // latency flags, although the peer might still want to do Rx with TSBPD. When agent sets // TsbPd mode, it defines latency values for Rx (itself) and Tx (peer's Rx). If peer does // not set TsbPd mode, it will simply ignore the proposed latency (PeerTsbPdDelay), although // if it has received the Rx latency as well, it must honor it and respond accordingly // (the latter is only in case of HSv5 and bidirectional connection). if (m_config.bTSBPD) { m_iTsbPdDelay_ms = m_config.iRcvLatency; m_iPeerTsbPdDelay_ms = m_config.iPeerLatency; /* * Sent data is real-time, use Time-based Packet Delivery, * set option bit and configured delay */ aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; if (hs_version < CUDT::HS_VERSION_SRT1) { // HSv4 - this uses only one value. aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay_ms); } else { // HSv5 - this will be understood only since this version when this exists. aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); // And in the reverse direction. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; aw_srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); // This wasn't there for HSv4, this setting is only for the receiver. // HSv5 is bidirectional, so every party is a receiver. if (m_bTLPktDrop) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; } } if (m_config.bRcvNakReport) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; // I support SRT_OPT_REXMITFLG. Do you? aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; // Declare the API used. The flag is set for "stream" API because // the older versions will never set this flag, but all old SRT versions use message API. if (!m_config.bMessageAPI) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM; HLOGC(cnlog.Debug, log << CONID() << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(aw_srtdata[SRT_HS_LATENCY]) << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(aw_srtdata[SRT_HS_LATENCY]) << "] FLAGS[" << SrtFlagString(aw_srtdata[SRT_HS_FLAGS]) << "]"); return 3; } size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // Setting m_tsRcvPeerStartTime is done in processSrtMsg_HSREQ(), so // this condition will be skipped only if this function is called without // getting first received HSREQ. Doesn't look possible in both HSv4 and HSv5. if (is_zero(m_tsRcvPeerStartTime)) { LOGC(cnlog.Fatal, log << CONID() << "IPE: fillSrtHandshake_HSRSP: m_tsRcvPeerStartTime NOT SET!"); return 0; } // If Agent doesn't set TSBPD, it will not set the TSBPD flag back to the Peer. // The peer doesn't have be disturbed by it anyway. if (isOPT_TsbPd()) { /* * We got and transposed peer start time (HandShake request timestamp), * we can support Timestamp-based Packet Delivery */ aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; if (hs_version < HS_VERSION_SRT1) { // HSv4 - this uses only one value aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay_ms); } else { // HSv5 - this puts "agent's" latency into RCV field and "peer's" - // into SND field. aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); } } else { HLOGC(cnlog.Debug, log << CONID() << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag."); } // Hsv5, only when peer has declared TSBPD mode. // The flag was already set, and the value already "maximized" in processSrtMsg_HSREQ(). if (m_bPeerTsbPd && hs_version >= HS_VERSION_SRT1) { // HSv5 is bidirectional - so send the TSBPDSND flag, and place also the // peer's latency into SND field. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; aw_srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); HLOGC(cnlog.Debug, log << CONID() << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms); } else { HLOGC(cnlog.Debug, log << CONID() << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5) << " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND"); } if (m_bTLPktDrop) aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; if (m_config.bRcvNakReport) { // HSv5: Note that this setting is independent on the value of // m_bPeerNakReport, which represent this setting in the peer. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; /* * NAK Report is so efficient at controlling bandwidth that sender TLPktDrop * is not needed. SRT 1.0.5 to 1.0.7 sender TLPktDrop combined with SRT 1.0 * Timestamp-Based Packet Delivery was not well implemented and could drop * big I-Frame tail before sending once on low latency setups. * Disabling TLPktDrop in the receiver SRT Handshake Reply prevents the sender * from enabling Too-Late Packet Drop. */ if (m_uPeerSrtVersion <= SrtVersion(1, 0, 7)) aw_srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP; } if (m_config.uSrtVersion >= SrtVersion(1, 2, 0)) { if (!m_bPeerRexmitFlag) { // Peer does not request to use rexmit flag, if so, // we won't use as well. HLOGC(cnlog.Debug, log << CONID() << "HSRSP/snd: AGENT understands REXMIT flag, but PEER DOES NOT. NOT setting."); } else { // Request that the rexmit bit be used as a part of msgno. aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; HLOGP(cnlog.Debug, "HSRSP/snd: AGENT UNDERSTANDS REXMIT flag and PEER reported that it does, too."); } } else { // Since this is now in the code, it can occur only in case when you change the // version specification in the build configuration. HLOGP(cnlog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag"); } HLOGC(cnlog.Debug, log << CONID() << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(aw_srtdata[SRT_HS_LATENCY]) << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(aw_srtdata[SRT_HS_LATENCY]) << "] FLAGS[" << SrtFlagString(aw_srtdata[SRT_HS_FLAGS]) << "]"); return 3; } size_t CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) { size_t srtlen = fillSrtHandshake(srtdata, size, cmd, handshakeVersion()); HLOGC(cnlog.Debug, log << "CMD:" << MessageTypeStr(UMSG_EXT, cmd) << "(" << cmd << ") Len:" << int(srtlen * sizeof(int32_t)) << " Version: " << SrtVersionString(srtdata[SRT_HS_VERSION]) << " Flags: " << srtdata[SRT_HS_FLAGS] << " (" << SrtFlagString(srtdata[SRT_HS_FLAGS]) << ") sdelay:" << srtdata[SRT_HS_LATENCY]); return srtlen; } void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, size_t srtlen_in) { CPacket srtpkt; int32_t srtcmd = (int32_t)cmd; SRT_STATIC_ASSERT(SRTDATA_MAXSIZE >= SRT_HS_E_SIZE, "SRT_CMD_MAXSZ is too small to hold all the data"); // This will be effectively larger than SRT_HS_E_SIZE, but it will be also used for incoming data. uint32_t srtdata[SRTDATA_MAXSIZE]; size_t srtlen = 0; if (cmd == SRT_CMD_REJECT) { // This is a value returned by processSrtMsg underlying layer, potentially // to be reported here. Should this happen, just send a rejection message. cmd = SRT_CMD_HSRSP; srtdata[SRT_HS_VERSION] = 0; } switch (cmd) { case SRT_CMD_HSREQ: case SRT_CMD_HSRSP: srtlen = prepareSrtHsMsg(cmd, srtdata, SRTDATA_MAXSIZE); break; case SRT_CMD_KMREQ: // Sender case SRT_CMD_KMRSP: // Receiver srtlen = srtlen_in; /* Msg already in network order * But CChannel:sendto will swap again (assuming 32-bit fields) * Pre-swap to cancel it. */ HtoNLA(srtdata, srtdata_in, srtlen); m_CryptoControl.updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT break; default: LOGC(cnlog.Error, log << "sndSrtMsg: IPE: cmd=" << cmd << " unsupported"); break; } if (srtlen > 0) { /* srtpkt.pack will set message data in network order */ srtpkt.pack(UMSG_EXT, &srtcmd, srtdata, srtlen * sizeof(int32_t)); addressAndSend(srtpkt); } } size_t CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const string& str) { uint32_t* space = pcmdspec + 1; size_t wordsize = (str.size() + 3) / 4; size_t aligned_bytesize = wordsize * 4; memset((space), 0, aligned_bytesize); memcpy((space), str.data(), str.size()); // Preswap to little endian (in place due to possible padding zeros) HtoILA((space), space, wordsize); *pcmdspec = HS_CMDSPEC_CMD::wrap(cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) wordsize); return wordsize; } #if SRT_ENABLE_BONDING // [[using locked(m_parent->m_ControlLock)]] // [[using locked(s_UDTUnited.m_GlobControlLock)]] size_t CUDT::fillHsExtGroup(uint32_t* pcmdspec) { SRT_ASSERT(m_parent->m_GroupOf != NULL); uint32_t* space = pcmdspec + 1; SRTSOCKET id = m_parent->m_GroupOf->id(); SRT_GROUP_TYPE tp = m_parent->m_GroupOf->type(); uint32_t flags = 0; // NOTE: this code remains as is for historical reasons. // The initial implementation stated that the peer id be // extracted so that it can be reported and possibly the // start time somehow encoded and written into the group // extension, but it was later seen not necessary. Therefore // this code remains, but now it's informational only. #if HVU_ENABLE_HEAVY_LOGGING m_parent->m_GroupOf->debugMasterData(m_SocketID); #endif // See CUDT::interpretGroup() uint32_t dataword = 0 | SrtHSRequest::HS_GROUP_TYPE::wrap(tp) | SrtHSRequest::HS_GROUP_FLAGS::wrap(flags) | SrtHSRequest::HS_GROUP_WEIGHT::wrap(m_parent->m_GroupMemberData->weight); const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(int(id)), dataword }; memcpy((space), storedata, sizeof storedata); const size_t ra_size = Size(storedata); *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_GROUP) | HS_CMDSPEC_SIZE::wrap(ra_size); return ra_size; } #endif size_t CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) { uint32_t* space = pcmdspec + 1; size_t msglen = m_CryptoControl.getKmMsg_size(ki); // Make ra_size back in element unit // Add one extra word if the size isn't aligned to 32-bit. size_t ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0); // Store the CMD + SIZE in the next field *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMREQ) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); // Copy the key - do the endian inversion because another endian inversion // will be done for every control message before sending, and this KM message // is ALREADY in network order. const uint32_t* keydata = reinterpret_cast(m_CryptoControl.getKmMsg_data(ki)); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size << " words (KmMsg_size=" << msglen << ")"); // XXX INSECURE ": [" << FormatBinaryString((uint8_t*)keydata, msglen) << "]"; // Yes, I know HtoNLA and NtoHLA do exactly the same operation, but I want // to be clear about the true intention. NtoHLA((space), keydata, ra_size); return ra_size; } size_t CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, size_t kmdata_wordsize) { uint32_t* space = pcmdspec + 1; const uint32_t failure_kmrsp[] = {SRT_KM_S_UNSECURED}; const uint32_t* keydata = 0; // Shift the starting point with the value of previously added block, // to start with the new one. size_t ra_size; if (kmdata_wordsize == 0) { LOGC(cnlog.Warn, log << CONID() << "createSrtHandshake: Agent has PW, but Peer sent no KMREQ. Sending error KMRSP response"); ra_size = 1; keydata = failure_kmrsp; // Update the KM state as well // NOTE: these fields are made atomic so that they can be safely written, // but formally this should require lock on m_ConnectionLock. // Agent has PW, but Peer won't decrypt; Peer won't encrypt as well. m_CryptoControl.setKMState(SRT_KM_S_NOSECRET, SRT_KM_S_UNSECURED); } else { if (!kmdata) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << CONID() << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!"); return 0; } ra_size = kmdata_wordsize; keydata = reinterpret_cast(kmdata); } *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMRSP) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: KMRSP: applying returned key length=" << ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata, // kmdata_wordsize*sizeof(uint32_t)) << "]"; NtoHLA((space), keydata, ra_size); return ra_size; } // PREREQUISITE: // pkt must be set the buffer and configured for UMSG_HANDSHAKE. // Note that this function replaces also serialization for the HSv4. bool CUDT::createSrtHandshake( int srths_cmd, int srtkm_cmd, const uint32_t* kmdata, size_t kmdata_wordsize, // IN WORDS, NOT BYTES!!! CPacket& w_pkt, CHandShake& w_hs) { // This function might be called before the opposite version was recognized. // Check if the version is exactly 4 because this means that the peer has already // sent something - asynchronously, and usually in rendezvous - and we already know // that the peer is version 4. In this case, agent must behave as HSv4, til the end. if (m_ConnRes.m_iVersion == HS_VERSION_UDT4) { w_hs.m_iVersion = HS_VERSION_UDT4; w_hs.m_iType = UDT_DGRAM; if (w_hs.m_extensionType != 0) { // Should be impossible LOGC(cnlog.Error, log << CONID() << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing..."); w_hs.m_extensionType = 0; } } else { w_hs.m_iType = 0; // Prepare it for flags } HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: buf size=" << w_pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd) << " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize << " version=" << w_hs.m_iVersion); // Once you are certain that the version is HSv5, set the enc type flags // to advertise pbkeylen. Otherwise make sure that the old interpretation // will correctly pick up the type field. PBKEYLEN should be advertised // regardless of what URQ stage the handshake is (note that in case of rendezvous // CONCLUSION might be the FIRST MESSAGE EVER RECEIVED by a party). if (w_hs.m_iVersion > HS_VERSION_UDT4) { // Check if there was a failure to receie HSREQ before trying to craft HSRSP. // If fillSrtHandshake_HSRSP catches the condition of m_tsRcvPeerStartTime == steady_clock::zero(), // it will return size 0, which will mess up with further extension procedures; // PREVENT THIS HERE. if (w_hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && is_zero(m_tsRcvPeerStartTime)) { LOGC(cnlog.Error, log << CONID() << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. " "BLOCKING extensions."); w_hs.m_extensionType = 0; } // The situation when this function is called without requested extensions // is URQ_CONCLUSION in rendezvous mode in some of the transitions. // In this case for version 5 just clear the m_iType field, as it has // different meaning in HSv5 and contains extension flags. // // Keep 0 in the SRT_HSTYPE_HSFLAGS field, but still advertise PBKEYLEN // in the SRT_HSTYPE_ENCFLAGS field. w_hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_config.iSndCryptoKeyLen); IF_HEAVY_LOGGING(bool whether = m_config.iSndCryptoKeyLen != 0); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); // Note: This is required only when sending a HS message without SRT extensions. // When this is to be sent with SRT extensions, then KMREQ will be attached here // and the PBKEYLEN will be extracted from it. If this is going to attach KMRSP // here, it's already too late (it should've been advertised before getting the first // handshake message with KMREQ). } else { w_hs.m_iType = UDT_DGRAM; } // values > URQ_CONCLUSION include also error types // if (w_hs.m_iVersion == HS_VERSION_UDT4 || w_hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and // it's only valid for caller-listener mode if (w_hs.m_extensionType == 0) { // Serialize only the basic handshake, if this is predicted for // Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND. size_t hs_size = w_pkt.getLength(); w_hs.store_to((w_pkt.m_pcData), (hs_size)); w_pkt.setLength(hs_size); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << w_hs.show()); return true; } // Sanity check, applies to HSv5 only cases. if (srths_cmd == SRT_CMD_HSREQ && m_SrtHsSide == HSD_RESPONDER) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << CONID() << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!"); return false; // should cause rejection } ostringstream logext; logext << "HSX"; // Install the SRT extensions w_hs.m_iType |= CHandShake::HS_EXT_HSREQ; bool have_sid = false; if (srths_cmd == SRT_CMD_HSREQ && !m_config.sStreamName.empty()) { have_sid = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",SID"; } // If this is a response, we have also information // on the peer. If Peer is NOT filter capable, don't // put filter config, even if agent is capable. bool peer_filter_capable = true; if (srths_cmd == SRT_CMD_HSRSP) { if (m_sPeerPktFilterConfigString != "") { peer_filter_capable = true; } else if (IsSet(m_uPeerSrtFlags, SRT_OPT_FILTERCAP)) { peer_filter_capable = true; } else { peer_filter_capable = false; } } // Now, if this is INITIATOR, then it has its // filter config already set, if configured, otherwise // it should not attach the filter config extension. // If this is a RESPONDER, then it has already received // the filter config string from the peer and therefore // possibly confronted with the contents of m_OPT_FECConfigString, // and if it decided to go with filter, it will be nonempty. bool have_filter = false; if (peer_filter_capable && !m_config.sPacketFilterConfig.empty()) { have_filter = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",filter"; } bool have_congctl = false; const string sm = m_config.sCongestion.str(); if (sm != "" && sm != "live") { have_congctl = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",CONGCTL"; } bool have_kmreq = false; // Prevent adding KMRSP only in case when BOTH: // - Agent has set no password // - no KMREQ has arrived from Peer // KMRSP must be always sent when: // - Agent set a password, Peer did not send KMREQ: Agent sets snd=NOSECRET. // - Agent set no password, but Peer sent KMREQ: Ageng sets rcv=NOSECRET. if (m_config.CryptoSecret.len > 0 || kmdata_wordsize > 0) { have_kmreq = true; w_hs.m_iType |= CHandShake::HS_EXT_KMREQ; logext << ",KMX"; } #if SRT_ENABLE_BONDING bool have_group = false; { // Needs locking only in order to prevent unordered reading of the field. // If the group is being closed simultaneously, it will then go on as if // it was without the group, but wrong situation will be also detected later. SharedLock gsh (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { // Whatever group this socket belongs to, the information about // the group is always sent the same way with the handshake. have_group = true; w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; logext << ",GROUP"; } } #endif HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: (ext: " << logext.str() << ") data: " << w_hs.show()); // NOTE: The HSREQ is practically always required, although may happen // in future that CONCLUSION can be sent multiple times for a separate // stream encryption support, and this way it won't enclose HSREQ. // Also, KMREQ may occur multiple times. // So, initially store the UDT legacy handshake. size_t hs_size = w_pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data w_hs.store_to((w_pkt.m_pcData), (hs_size)); // hs_size is updated size_t ra_size = hs_size / sizeof(int32_t); // Now attach the SRT handshake for HSREQ size_t offset = ra_size; uint32_t *p = reinterpret_cast(w_pkt.m_pcData); // NOTE: since this point, ra_size has a size in int32_t elements, NOT BYTES. // The first 4-byte item is the CMD/LENGTH spec. uint32_t *pcmdspec = p + offset; // Remember the location to be filled later, when we know the length ++offset; // Now use the original function to store the actual SRT_HS data // ra_size after that // NOTE: so far, ra_size is m_iMaxSRTPayloadSize expressed in number of elements. // WILL BE CHANGED HERE. ra_size = fillSrtHandshake((p + offset), total_ra_size - offset, srths_cmd, HS_VERSION_SRT1); *pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size << " space left: " << (total_ra_size - offset)); // Use only in REQ phase and only if stream name is set if (have_sid) { // Now prepare the string with 4-byte alignment. The string size is limited // to half the payload size. Just a sanity check to not pack too much into // the conclusion packet. size_t size_limit = m_iMaxSRTPayloadSize / 2; if (m_config.sStreamName.size() >= size_limit) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Warn, log << CONID() << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes"); return false; } offset += ra_size + 1; ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_SID, m_config.sStreamName.str()); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: after SID [" << m_config.sStreamName.c_str() << "] length=" << m_config.sStreamName.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " SID size=" << ra_size << " space left: " << (total_ra_size - offset)); } if (have_congctl) { // Pass the congctl to the other side as informational. // The other side should reject connection if it uses a different congctl. // The other side should also respond with the congctl it uses, if its non-default (for backward compatibility). offset += ra_size + 1; ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_CONGESTION, sm); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " CONGCTL size=" << ra_size << " space left: " << (total_ra_size - offset)); } if (have_filter) { offset += ra_size + 1; ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_FILTER, m_config.sPacketFilterConfig.str()); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: after filter [" << m_config.sPacketFilterConfig.c_str() << "] length=" << m_config.sPacketFilterConfig.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " filter size=" << ra_size << " space left: " << (total_ra_size - offset)); } #if SRT_ENABLE_BONDING // Note that this will fire in both cases: // - When the group has been set by the user on a socket (or socket was created as a part of the group), // and the handshake request is to be sent with informing the peer that this connection belongs to a group // - When the agent received a HS request with a group, has created its mirror group on its side, and // now sends the HS response to the peer, with ITS OWN group id (the mirror one). // // XXX Probably a condition should be checked here around the group type. // The time synchronization should be done only on any kind of parallel sending group. // Currently all groups are such groups (broadcast, backup, balancing), but it may // need to be changed for some other types. if (have_group) { // NOTE: See information about mutex ordering in api.h SharedLock gdrg (uglobal().m_GlobControlLock); if (!m_parent->m_GroupOf) { // This may only happen if since last check of m_GroupOf pointer the socket was removed // from the group in the meantime, which can only happen due to that the group was closed. // In such a case it simply means that the handshake process was requested to be interrupted. LOGC(cnlog.Fatal, log << CONID() << "GROUP DISAPPEARED. Socket not capable of continuing HS"); return false; } else { if (m_parent->m_GroupOf->closing()) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << CONID() << "createSrtHandshake: group is closing during the process, rejecting."); return false; } offset += ra_size + 1; ra_size = fillHsExtGroup(p + offset - 1); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: after GROUP [" << sm << "] length=" << sm.size() << ": offset=" << offset << " GROUP size=" << ra_size << " space left: " << (total_ra_size - offset)); } } #endif // When encryption turned on if (have_kmreq) { HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: " << (m_config.CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION")); if (!m_CryptoControl.initialized() && (srtkm_cmd == SRT_CMD_KMREQ || srtkm_cmd == SRT_CMD_KMRSP)) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << CONID() << "createSrtHandshake: IPE: need to send KM, but CryptoControl does not exist." << " Socket state: " << fmt_onoff(m_bConnected) << "connected, " << fmt_onoff(m_bConnecting) << "connecting, " << fmt_onoff(m_bBroken) << "broken, " << fmt_onoff(m_bClosing) << "closing."); return false; } if (srtkm_cmd == SRT_CMD_KMREQ) { bool have_any_keys = false; for (size_t ki = 0; ki < 2; ++ki) { // Skip those that have expired if (!m_CryptoControl.getKmMsg_needSend(ki, false)) continue; m_CryptoControl.getKmMsg_markSent(ki, false); offset += ra_size + 1; ra_size = fillHsExtKMREQ(p + offset - 1, ki); have_any_keys = true; } if (!have_any_keys) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << CONID() << "createSrtHandshake: IPE: all keys have expired, no KM to send."); return false; } } else if (srtkm_cmd == SRT_CMD_KMRSP) { offset += ra_size + 1; ra_size = fillHsExtKMRSP(p + offset - 1, kmdata, kmdata_wordsize); } else { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << CONID() << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd); return false; } } if (ra_size == 0) { // m_RejectReason is expected to be set by fillHsExtKMRSP(..) in this case. return false; } // ra_size + offset has a value in element unit. // Switch it again to byte unit. w_pkt.setLength((ra_size + offset) * sizeof(int32_t)); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(w_hs.m_iType) << " length: " << w_pkt.getLength() << " bytes"); return true; } template static inline int FindExtensionBlock(Integer* begin, size_t total_length, size_t& w_out_len, Integer*& w_next_block) { // Check if there's anything to process if (total_length == 0) { w_next_block = NULL; w_out_len = 0; return SRT_CMD_NONE; } // This function extracts the block command from the block and its length. // The command value is returned as a function result. // The size of that command block is stored into w_out_len. // The beginning of the prospective next block is stored in w_next_block. // The caller must be aware that: // - exactly one element holds the block header (cmd+size), so the actual data are after this one. // - the returned size is the number of uint32_t elements since that first data element // - the remaining size should be manually calculated as total_length - 1 - w_out_len, or // simply, as w_next_block - begin. // Note that if the total_length is too short to extract the whole block, it will return // SRT_CMD_NONE. Note that total_length includes this first CMDSPEC word. // // When SRT_CMD_NONE is returned, it means that nothing has been extracted and nothing else // can be further extracted from this block. int cmd = HS_CMDSPEC_CMD::unwrap(*begin); size_t size = HS_CMDSPEC_SIZE::unwrap(*begin); if (size + 1 > total_length) return SRT_CMD_NONE; w_out_len = size; if (total_length == size + 1) w_next_block = NULL; else w_next_block = begin + 1 + size; return cmd; } // NOTE: the rule of order of arguments is broken here because this order // serves better the logics and readability. template static inline bool NextExtensionBlock(Integer*& w_begin, Integer* next, size_t& w_length) { if (!next) return false; w_length = w_length - (next - w_begin); w_begin = next; return true; } void SrtExtractHandshakeExtensions(const char* bufbegin, size_t buflength, vector& w_output) { const uint32_t *begin = reinterpret_cast(bufbegin + CHandShake::m_iContentSize); size_t size = buflength - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 const uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // ONE SHOT, but continuable loop { const int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); if (cmd == SRT_CMD_NONE) { // End of blocks break; } w_output.push_back(SrtHandshakeExtension(cmd)); SrtHandshakeExtension& ext = w_output.back(); std::copy(begin+1, begin+blocklen+1, back_inserter(ext.contents)); // Any other kind of message extracted. Search on. if (!NextExtensionBlock((begin), next, (length))) break; } } #if SRT_DEBUG_RTT class RttTracer { public: RttTracer() { } ~RttTracer() { sync::ScopedLock lck(m_mtx); m_fout.close(); } void trace(const sync::steady_clock::time_point& currtime, const std::string& event, int rtt_sample, int rttvar_sample, bool is_smoothed_rtt_reset, int64_t recvTotal, int smoothed_rtt, int rttvar) { sync::ScopedLock lck(m_mtx); create_file(); m_fout << sync::FormatTimeSys(currtime) << ","; m_fout << sync::FormatTime(currtime) << ","; m_fout << event << ","; m_fout << rtt_sample << ","; m_fout << rttvar_sample << ","; m_fout << is_smoothed_rtt_reset << ","; m_fout << recvTotal << ","; m_fout << smoothed_rtt << ","; m_fout << rttvar << "\n"; m_fout.flush(); } private: void print_header() { m_fout << "Timepoint_SYST,Timepoint_STDY,Event,usRTTSample," "usRTTVarSample,IsSmoothedRTTReset,pktsRecvTotal," "usSmoothedRTT,usRTTVar\n"; } void create_file() { if (m_fout.is_open()) return; std::string str_tnow = sync::FormatTimeSys(sync::steady_clock::now()); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part while (str_tnow.find(':') != std::string::npos) { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "rtt_trace_" + str_tnow + "_" + SRT_SYNC_CLOCK_STR + ".csv"; m_fout.open(fname, std::ofstream::out); if (!m_fout) std::cerr << "IPE: Failed to open " << fname << "!!!\n"; print_header(); } private: sync::Mutex m_mtx; std::ofstream m_fout; }; RttTracer s_rtt_trace; #endif bool CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData; size_t len = ctrlpkt->getLength(); int etype = ctrlpkt->getExtendedType(); uint32_t ts = ctrlpkt->timestamp(); int res = SRT_CMD_NONE; HLOGC(cnlog.Debug, log << CONID() << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t))); switch (etype) { case SRT_CMD_HSREQ: { res = processSrtMsg_HSREQ(srtdata, len, ts, CUDT::HS_VERSION_UDT4); break; } case SRT_CMD_HSRSP: { res = processSrtMsg_HSRSP(srtdata, len, ts, CUDT::HS_VERSION_UDT4); break; } case SRT_CMD_KMREQ: // Special case when the data need to be processed here // and the appropriate message must be constructed for sending. // No further processing required { uint32_t srtdata_out[SRTDATA_MAXSIZE]; size_t len_out = 0; res = m_CryptoControl.processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, (srtdata_out), (len_out)); if (res == SRT_CMD_KMRSP) { if (len_out == 1) { if (m_config.bEnforcedEnc) { LOGC(cnlog.Warn, log << CONID() << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])) << " - rejecting per enforced encryption"); res = SRT_CMD_NONE; break; } HLOGC(cnlog.Debug, log << CONID() << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))); } else { HLOGC(cnlog.Debug, log << CONID() << "KMREQ -> requested to send KMRSP length=" << len_out); } sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); } // XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now. // Please review later. else { LOGC(cnlog.Warn, log << CONID() << "KMREQ failed to process the request - ignoring"); } return true; // already done what's necessary } case SRT_CMD_KMRSP: { // KMRSP doesn't expect any following action m_CryptoControl.processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion); return true; // nothing to do } default: return false; } if (res == SRT_CMD_NONE) return true; // Send the message that the message handler requested. sendSrtMsg(res); return true; } int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) { // Set this start time in the beginning, regardless as to whether TSBPD is being // used or not. This must be done in the Initiator as well as Responder. /* * Compute peer StartTime in our time reference * This takes time zone, time drift into account. * Also includes current packet transit time (rtt/2) */ m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts); // (in case of bonding group, this value will be OVERWRITTEN // later in CUDT::interpretGroup). // Prepare the initial runtime values of latency basing on the option values. // They are going to get the value fixed HERE. m_iTsbPdDelay_ms = m_config.iRcvLatency; m_iPeerTsbPdDelay_ms = m_config.iPeerLatency; if (bytelen < SRT_CMD_HSREQ_MINSZ) { m_RejectReason = SRT_REJ_ROGUE; /* Packet smaller than minimum compatible packet size */ LOGC(cnlog.Error, log << "HSREQ/rcv: cmd=" << SRT_CMD_HSREQ << "(HSREQ) len=" << bytelen << " invalid"); return SRT_CMD_NONE; } LOGC(cnlog.Debug, log << "HSREQ/rcv: cmd=" << SRT_CMD_HSREQ << "(HSREQ) len=" << bytelen << " vers=0x" << fmt(srtdata[SRT_HS_VERSION], hex) << " opts=0x" << fmt(srtdata[SRT_HS_FLAGS], hex) << " delay=" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; if (hsv == CUDT::HS_VERSION_UDT4) { if (m_uPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " is not acceptable."); return SRT_CMD_REJECT; } } else { if (m_uPeerSrtVersion < SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HSREQ/rcv: With HSv5 version must be >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " ."); return SRT_CMD_REJECT; } } // Check also if the version satisfies the minimum required version if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) { m_RejectReason = SRT_REJ_VERSION; LOGC(cnlog.Error, log << CONID() << "HSREQ/rcv: Peer version: " << SrtVersionString(m_uPeerSrtVersion) << " is too old for requested: " << SrtVersionString(m_config.uMinimumPeerSrtVersion) << " - REJECTING"); return SRT_CMD_REJECT; } HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_uPeerSrtVersion) << " Flags: " << m_uPeerSrtFlags << "(" << SrtFlagString(m_uPeerSrtFlags) << ") Min req version:" << SrtVersionString(m_config.uMinimumPeerSrtVersion)); m_bPeerRexmitFlag = IsSet(m_uPeerSrtFlags, SRT_OPT_REXMITFLG); HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: peer " << (m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND") << " REXMIT flag"); // Check if both use the same API type. Reject if not. bool peer_message_api = !IsSet(m_uPeerSrtFlags, SRT_OPT_STREAM); if (peer_message_api != m_config.bMessageAPI) { m_RejectReason = SRT_REJ_MESSAGEAPI; LOGC(cnlog.Error, log << CONID() << "HSREQ/rcv: Agent uses " << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, but the Peer declares " << (peer_message_api ? "MESSAGE" : "STREAM") << " API. Not compatible transmission type, rejecting."); return SRT_CMD_REJECT; } SRT_STATIC_ASSERT(SRT_HS_E_SIZE == SRT_HS_LATENCY + 1, "Assuming latency is the last field"); if (bytelen < (SRT_HS_E_SIZE * sizeof(uint32_t))) { // Handshake extension message includes VERSION, FLAGS and LATENCY // (3 x 32 bits). SRT v1.2.0 and earlier might supply shorter extension message, // without LATENCY fields. // It is acceptable, as long as the latency flags are not set on our side. // // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | SRT Version | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | SRT Flags | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Receiver TSBPD Delay | Sender TSBPD Delay | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting."); return SRT_CMD_REJECT; } LOGC(cnlog.Warn, log << CONID() << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, not getting any TSBPD settings."); // Don't process any further settings in this case. Turn off TSBPD, just for a case. m_bTsbPd = false; m_bPeerTsbPd = false; return SRT_CMD_HSRSP; } const uint32_t latencystr = srtdata[SRT_HS_LATENCY]; if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND)) { // TimeStamp-based Packet Delivery feature enabled if (!isOPT_TsbPd()) { LOGC(cnlog.Warn, log << CONID() << "HSREQ/rcv: Agent did not set rcv-TSBPD - ignoring proposed latency from peer"); // Note: also don't set the peer TSBPD flag HERE because // - in HSv4 it will be a sender, so it doesn't matter anyway // - in HSv5 if it's going to receive, the TSBPDRCV flag will define it. } else { int peer_decl_latency; if (hsv < CUDT::HS_VERSION_SRT1) { // In HSv4 there is only one value and this is the latency // that the sender peer proposes for the agent. peer_decl_latency = SRT_HS_LATENCY_LEG::unwrap(latencystr); } else { // In HSv5 there are latency declared for sending and receiving separately. // SRT_HS_LATENCY_SND is the value that the peer proposes to be the // value used by agent when receiving data. We take this as a local latency value. peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); } // Use the maximum latency out of latency from our settings and the latency // "proposed" by the peer. int maxdelay = std::max(m_iTsbPdDelay_ms, peer_decl_latency); HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay); m_iTsbPdDelay_ms = maxdelay; m_bTsbPd = true; } } else { std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent"; HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: Peer DOES NOT USE latency for sending - " << how_about_agent); } // This happens when the HSv5 RESPONDER receives the HSREQ message; it declares // that the peer INITIATOR will receive the data and informs about its predefined // latency. We need to maximize this with our setting of the peer's latency and // record as peer's latency, which will be then sent back with HSRSP. if (hsv > CUDT::HS_VERSION_UDT4 && IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // So, PEER uses TSBPD, set the flag. // NOTE: it doesn't matter, if AGENT uses TSBPD. m_bPeerTsbPd = true; // SRT_HS_LATENCY_RCV is the value that the peer declares as to be // used by it when receiving data. We take this as a peer's value, // and select the maximum of this one and our proposed latency for the peer. int peer_decl_latency = SRT_HS_LATENCY_RCV::unwrap(latencystr); int maxdelay = std::max(m_iPeerTsbPdDelay_ms, peer_decl_latency); HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay_ms << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay); m_iPeerTsbPdDelay_ms = maxdelay; } else { std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent"; HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: Peer DOES NOT USE latency for receiving - " << how_about_agent); } if (hsv > CUDT::HS_VERSION_UDT4) { // This is HSv5, do the same things as required for the sending party in HSv4, // as in HSv5 this can also be a sender. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TLPKTDROP)) { // Too late packets dropping feature supported m_bPeerTLPktDrop = true; } if (IsSet(m_uPeerSrtFlags, SRT_OPT_NAKREPORT)) { // Peer will send Periodic NAK Reports m_bPeerNakReport = true; } } return SRT_CMD_HSRSP; } int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) { // XXX Check for mis-version // With HSv4 we accept only version less than 1.3.0 if (hsv == CUDT::HS_VERSION_UDT4 && srtdata[SRT_HS_VERSION] >= SRT_VERSION_FEAT_HSv5) { LOGC(cnlog.Error, log << CONID() << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable."); return SRT_CMD_NONE; } if (bytelen < SRT_CMD_HSRSP_MINSZ) { /* Packet smaller than minimum compatible packet size */ LOGC(cnlog.Error, log << CONID() << "HSRSP/rcv: cmd=" << SRT_CMD_HSRSP << "(HSRSP) len=" << bytelen << " invalid"); return SRT_CMD_NONE; } // Set this start time in the beginning, regardless as to whether TSBPD is being // used or not. This must be done in the Initiator as well as Responder. In case when // agent is sender only (HSv4) this value simply won't be used. /* * Compute peer StartTime in our time reference * This takes time zone, time drift into account. * Also includes current packet transit time (rtt/2) */ if (is_zero(m_tsRcvPeerStartTime)) { // Do not set this time when it's already set, which may be the case // if the agent has this value already "borrowed" from a master socket // that was in the group at the time when it was added. m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts); HLOGC(cnlog.Debug, log << CONID() << "HSRSP/rcv: PEER START TIME not yet defined, setting: " << FormatTime(m_tsRcvPeerStartTime)); } else { HLOGC(cnlog.Debug, log << CONID() << "HSRSP/rcv: PEER START TIME already set (derived): " << FormatTime(m_tsRcvPeerStartTime)); } m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; HLOGC(cnlog.Debug, log << "HSRSP/rcv: Version: " << SrtVersionString(m_uPeerSrtVersion) << " Flags: SND:" << fmt(m_uPeerSrtFlags, fmtc().hex().fillzero().width(8)) << " (" << SrtFlagString(m_uPeerSrtFlags) << ")"); // Basic version check if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) { m_RejectReason = SRT_REJ_VERSION; LOGC(cnlog.Error, log << CONID() << "HSRSP/rcv: Peer version: " << SrtVersionString(m_uPeerSrtVersion) << " is too old for requested: " << SrtVersionString(m_config.uMinimumPeerSrtVersion) << " - REJECTING"); return SRT_CMD_REJECT; } if (hsv == CUDT::HS_VERSION_UDT4) { // The old HSv4 way: extract just one value and put it under peer. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // TsbPd feature enabled m_bPeerTsbPd = true; m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]); HLOGC(cnlog.Debug, log << CONID() << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << " (Agent: declared:" << m_iTsbPdDelay_ms << " rcv:" << m_iTsbPdDelay_ms << ")"); } // TSBPDSND isn't set in HSv4 by the RESPONDER, because HSv4 RESPONDER is always RECEIVER. } else { // HSv5 way: extract the receiver latency and sender latency, if used. // PEER WILL RECEIVE TSBPD == AGENT SHALL SEND TSBPD. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // TsbPd feature enabled m_bPeerTsbPd = true; m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]); HLOGC(cnlog.Debug, log << CONID() << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms"); } else { HLOGC(cnlog.Debug, log << CONID() << "HSRSP/rcv: Peer (responder) DOES NOT USE latency"); } // PEER WILL SEND TSBPD == AGENT SHALL RECEIVE TSBPD. if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND)) { if (!isOPT_TsbPd()) { LOGC(cnlog.Warn, log << CONID() << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD."); } else { m_bTsbPd = true; // NOTE: in case of Group TSBPD receiving, this field will be SWITCHED TO m_bGroupTsbPd. // Take this value as a good deal. In case when the Peer did not "correct" the latency // because it has TSBPD turned off, just stay with the present value defined in options. m_iTsbPdDelay_ms = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); HLOGC(cnlog.Debug, log << CONID() << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms"); } } } if ((m_config.uSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_uPeerSrtFlags, SRT_OPT_TLPKTDROP)) { // Too late packets dropping feature supported m_bPeerTLPktDrop = true; } if ((m_config.uSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_uPeerSrtFlags, SRT_OPT_NAKREPORT)) { // Peer will send Periodic NAK Reports m_bPeerNakReport = true; } if (m_config.uSrtVersion >= SrtVersion(1, 2, 0)) { if (IsSet(m_uPeerSrtFlags, SRT_OPT_REXMITFLG)) { // Peer will use REXMIT flag in packet retransmission. m_bPeerRexmitFlag = true; HLOGP(cnlog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer."); } else { HLOGP(cnlog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT"); } } else { HLOGP(cnlog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag"); } handshakeDone(); return SRT_CMD_NONE; } // This function is called only when the URQ_CONCLUSION handshake has been received from the peer. // The 'ls' parameter is marked unused to avoid warning when SRT_ENABLE_BONDING == 0. bool CUDT::interpretSrtHandshake(CUDTSocket* lsn SRT_ATR_UNUSED, const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data SRT_ATR_UNUSED, size_t* pw_len) { // Initialize pw_len to 0 to handle the unencrypted case if (pw_len) *pw_len = 0; // The version=0 statement as rejection is used only since HSv5. // The HSv4 sends the AGREEMENT handshake message with version=0, do not misinterpret it. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4 && hs.m_iVersion == 0) { m_RejectReason = SRT_REJ_PEER; LOGC(cnlog.Error, log << CONID() << "HS VERSION = 0, meaning the handshake has been rejected."); return false; } if (hs.m_iVersion < HS_VERSION_SRT1) { if (m_config.uMinimumPeerSrtVersion && m_config.uMinimumPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_VERSION; // This means that a version with minimum 1.3.0 that features HSv5 is required, // hence all HSv4 clients should be rejected. LOGP(cnlog.Error, "interpretSrtHandshake: minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client"); return false; } return true; // do nothing } // Anyway, check if the handshake contains any extra data. if (hspkt.getLength() <= CHandShake::m_iContentSize) { m_RejectReason = SRT_REJ_ROGUE; // This would mean that the handshake was at least HSv5, but somehow no extras were added. // Dismiss it then, however this has to be logged. LOGC(cnlog.Error, log << CONID() << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!"); return false; } // We still believe it should work, let's check the flags. const int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); if (ext_flags == 0) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!"); return false; } HLOGC(cnlog.Debug, log << CONID() << "HS VERSION=" << hs.m_iVersion << " EXTENSIONS: " << CHandShake::ExtensionFlagStr(ext_flags)); // Ok, now find the beginning of an int32_t array that follows the UDT handshake. uint32_t* p = reinterpret_cast(hspkt.m_pcData + CHandShake::m_iContentSize); size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 int hsreq_type_cmd SRT_ATR_UNUSED = SRT_CMD_NONE; if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ)) { HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting HSREQ/RSP type extension"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // this is ONE SHOT LOOP { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_HSREQ) { hsreq_type_cmd = cmd; // Set is the size as it should, then give it for interpretation for // the proper function. if (blocklen < SRT_HS_E_SIZE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.timestamp(), HS_VERSION_SRT1); // Interpreted? Then it should be responded with SRT_CMD_HSRSP. if (rescmd != SRT_CMD_HSRSP) { // m_RejectReason already set LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd); return false; } handshakeDone(); // updateAfterSrtHandshake -> moved to postConnect and processRendezvous } else if (cmd == SRT_CMD_HSRSP) { hsreq_type_cmd = cmd; // Set is the size as it should, then give it for interpretation for // the proper function. if (blocklen < SRT_HS_E_SIZE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.timestamp(), HS_VERSION_SRT1); // Interpreted? Then it should be responded with SRT_CMD_NONE. // (nothing to be responded for HSRSP, unless there was some kinda problem) if (rescmd != SRT_CMD_NONE) { // Just formally; the current code doesn't seem to return anything else // (unless it's already set) if (m_RejectReason == SRT_REJ_UNKNOWN) m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd); return false; } handshakeDone(); // updateAfterSrtHandshake -> moved to postConnect and processRendezvous } else if (cmd == SRT_CMD_NONE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Warn, log << CONID() << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!"); // This means that there can be no more processing done by FindExtensionBlock(). // And we haven't found what we need - otherwise one of the above cases would pass // and lead to exit this loop immediately. return false; } else { // Any other kind of message extracted. Search on. length -= (next - begin); begin = next; if (begin) continue; } break; } } HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: HSREQ done, checking KMREQ"); // Now check the encrypted bool encrypted = false; if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ)) { HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting KMREQ/RSP type extension"); #ifdef SRT_ENABLE_ENCRYPTION if (!m_CryptoControl.hasPassphrase()) { if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << CONID() << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per enforced " "encryption"); return false; } LOGC(cnlog.Warn, log << CONID() << "HS KMREQ: Peer declares encryption, but agent does not - still allowing connection."); // Still allow for connection, and allow Agent to send unencrypted stream to the peer. // Also normally allow the key to be processed; worst case it will send the failure response. } uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // This is one shot loop, unless REPEATED by 'continue'. { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_KMREQ) { if (!out_data || !pw_len) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << CONID() << "IPE: HS/KMREQ extracted without passing target buffer!"); return false; } int res = m_CryptoControl.processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, m_uPeerSrtVersion, (out_data), (*pw_len)); if (res != SRT_CMD_KMRSP) { m_RejectReason = SRT_REJ_IPE; // Something went wrong. HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res); return false; } if (*pw_len == 1) { #ifdef SRT_ENABLE_AEAD if (m_CryptoControl.kmState().rcv == SRT_KM_S_BADCRYPTOMODE) { // Cryptographic modes mismatch. Not acceptable at all. m_RejectReason = SRT_REJ_CRYPTO; LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: KMREQ result: Bad crypto mode - rejecting"); return false; } #endif // This means that there was an abnormal encryption situation occurred. // This is inacceptable in case of strict encryption. if (m_config.bEnforcedEnc) { if (m_CryptoControl.kmState().rcv == SRT_KM_S_BADSECRET) { m_RejectReason = SRT_REJ_BADSECRET; } else { m_RejectReason = SRT_REJ_UNSECURE; } LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption"); return false; } } encrypted = true; } else if (cmd == SRT_CMD_KMRSP) { int res = m_CryptoControl.processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion); if (m_config.bEnforcedEnc && res == -1) { if (m_CryptoControl.kmState().snd == SRT_KM_S_BADSECRET) m_RejectReason = SRT_REJ_BADSECRET; #ifdef SRT_ENABLE_AEAD else if (m_CryptoControl.kmState().snd == SRT_KM_S_BADCRYPTOMODE) m_RejectReason = SRT_REJ_CRYPTO; #endif else m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << CONID() << "KMRSP failed - rejecting connection as per enforced encryption."); return false; } encrypted = true; } else if (cmd == SRT_CMD_NONE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS KMREQ expected - none found!"); return false; } else { HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); if (NextExtensionBlock((begin), next, (length))) continue; } break; } #else // When encryption is not enabled at compile time, behave as if encryption wasn't set, // so accordingly to StrictEncryption flag. if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << CONID() << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting " "per enforced encryption"); return false; } LOGC(cnlog.Warn, log << CONID() << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing " "connection."); encrypted = true; #endif } bool have_congctl = false; bool have_filter = false; string agsm = m_config.sCongestion.str(); if (agsm == "") { agsm = "live"; m_config.sCongestion.set("live", 4); } bool have_group SRT_ATR_UNUSED = false; if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) { HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting various CONFIG extensions"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // This is one shot loop, unless REPEATED by 'continue'. { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); const size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_SID) { if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Copied through a cleared array. This is because the length is aligned to 4 // where the padding is filled by zero bytes. For the case when the string is // exactly of a 4-divisible length, we make a big array with maximum allowed size // filled with zeros. Copying to this array should then copy either only the valid // characters of the string (if the length is divisible by 4), or the string with // padding zeros. In all these cases in the resulting array we should have all // subsequent characters of the string plus at least one '\0' at the end. This will // make it a perfect NUL-terminated string, to be used to initialize a string. char target[CSrtConfig::MAX_SID_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_SID_LENGTH + 1); memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); // Determine the length by dropping all but one of terminal NUL characters. size_t targetlen = bytelen; for (size_t pos = bytelen - 1; pos != 0; --pos) { if (target[pos]) { // Found the first non-NUL character backwards // So the length is pos+1 targetlen = pos + 1; break; } } m_config.sStreamName.set(target, targetlen); HLOGC(cnlog.Debug, log << CONID() << "CONNECTOR'S REQUESTED SID [" << m_config.sStreamName.c_str() << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_CONGESTION) { if (have_congctl) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "CONGCTL BLOCK REPEATED!"); return false; } if (!bytelen || bytelen > CSrtConfig::MAX_CONG_LENGTH) { LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_CONG_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Declare that congctl has been received have_congctl = true; char target[CSrtConfig::MAX_CONG_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_CONG_LENGTH + 1); memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); string sm = target; // As the congctl has been declared by the peer, // check if your congctl is compatible. // sm cannot be empty, but the agent's sm can be empty meaning live. if (sm != agsm) { m_RejectReason = SRT_REJ_CONGESTION; LOGC(cnlog.Error, log << CONID() << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'"); return false; } HLOGC(cnlog.Debug, log << CONID() << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_FILTER) { if (have_filter) { m_RejectReason = SRT_REJ_FILTER; LOGC(cnlog.Error, log << CONID() << "FILTER BLOCK REPEATED!"); return false; } if (!bytelen || bytelen > CSrtConfig::MAX_PFILTER_LENGTH) { LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: packet-filter type length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_PFILTER_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Declare that filter has been received have_filter = true; char target[CSrtConfig::MAX_PFILTER_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_PFILTER_LENGTH + 1); memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); string fltcfg = target; HLOGC(cnlog.Debug, log << CONID() << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); if (!checkApplyFilterConfig(fltcfg)) { m_RejectReason = SRT_REJ_FILTER; LOGC(cnlog.Error, log << CONID() << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected"); return false; } } #if SRT_ENABLE_BONDING else if ( cmd == SRT_CMD_GROUP ) { // Note that this will fire in both cases: // - When receiving HS request from the Initiator, which belongs to a group, and agent must // create the mirror group on his side (or join the existing one, if there's already // a mirror group for that group ID). // - When receiving HS response from the Responder, with its mirror group ID, so the agent // must put the group into his peer group data int32_t groupdata[GRPD_E_SIZE] = {}; if (bytelen < GRPD_MIN_SIZE * GRPD_FIELD_SIZE || bytelen % GRPD_FIELD_SIZE) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "PEER'S GROUP wrong size: " << (bytelen/GRPD_FIELD_SIZE)); return false; } size_t used_bytelen = std::min(bytelen, sizeof(groupdata)); size_t groupdata_size = used_bytelen / GRPD_FIELD_SIZE; memcpy(groupdata, begin+1, used_bytelen); if (!interpretGroup(lsn, groupdata, groupdata_size, hsreq_type_cmd) ) { // m_RejectReason handled inside interpretGroup(). return false; } have_group = true; HLOGC(cnlog.Debug, log << CONID() << "CONNECTOR'S PEER GROUP [" << groupdata[0] << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } #endif else if (cmd == SRT_CMD_NONE) { break; } else { // Found some block that is not interesting here. Skip this and get the next one. HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); } if (!NextExtensionBlock((begin), next, (length))) break; } } // Post-checks // Check if peer declared encryption if (!encrypted && m_config.CryptoSecret.len > 0) { if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; LOGC(cnlog.Error, log << CONID() << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per " "enforced encryption."); return false; } LOGC(cnlog.Warn, log << CONID() << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets " "from Peer)."); // This is required so that the sender is still allowed to send data, when encryption is required, // just this will be for waste because the receiver won't decrypt them anyway. m_CryptoControl.createFakeSndContext(); // Because Peer did not send KMX, though Agent has pw ; Peer has no PW, as has sent no KMREQ. m_CryptoControl.setKMState(SRT_KM_S_NOSECRET, SRT_KM_S_UNSECURED); return true; } // If agent has set some nondefault congctl, then congctl is expected from the peer. if (agsm != "live" && !have_congctl) { m_RejectReason = SRT_REJ_CONGESTION; LOGC(cnlog.Error, log << CONID() << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live')."); return false; } // Check if the filter was configured on agent, // but the peer responded with no filter. if (!m_config.sPacketFilterConfig.empty() && !have_filter) { if (m_SrtHsSide == HSD_INITIATOR) { // IMPORTANT: // If agent didn't set the packet filter in the configuration, the peer // could have responded with its own configuration and enforce the packet filter // setting here locally. In this case the sPacketFilterConfig will not be empty // at the moment, even if it was empty initially. // The situation caught here is that the configuration for packet filter was set, // but the peer responded as if it didn't understand it, or simply didn't accept it. // In such a situation the configuration should be rejected because if the // peer doesn't support packet filter, the caller side should not have set it. m_RejectReason = SRT_REJ_FILTER; LOGC(cnlog.Error, log << CONID() << "HS EXT: Agent has configured packetfilter, but peer didn't respond."); return false; } // Responder: we are the listener that has configured packetfilter, // but the caller didn't request packetfilter. m_config.sPacketFilterConfig.set(""); LOGC(cnlog.Warn, log << CONID() << "HS EXT: Agent has configured packetfilter, but peer didn't request it"); } #if SRT_ENABLE_BONDING bool group_already = false; { SharedLock sgl (uglobal().m_GlobControlLock); group_already = m_parent->m_GroupOf; } // m_GroupOf and locking info: NULL check won't hurt here. If the group // was deleted in the meantime, it will be found out later anyway and result with error. if (m_SrtHsSide == HSD_INITIATOR && group_already) { // XXX Later probably needs to check if this group REQUIRES the group // response. Currently this implements the bonding-category group, and this // always requires that the listener respond with the group id, otherwise // it probably DID NOT UNDERSTAND THE GROUP, so the connection should be rejected. if (!have_group) { m_RejectReason = SRT_REJ_GROUP; LOGC(cnlog.Error, log << CONID() << "HS EXT: agent is a group member, but the listener did not respond with group ID. Rejecting."); return false; } } #endif // Ok, finished, for now. return true; } bool CUDT::checkApplyFilterConfig(const std::string &confstr) { SrtFilterConfig cfg; if (!ParseFilterConfig(confstr, (cfg))) return false; // Now extract the type, if present, and // check if you have this type of corrector available. if (!PacketFilter::correctConfig(cfg)) return false; string thisconf = m_config.sPacketFilterConfig.str(); // Now parse your own string, if you have it. if (thisconf != "") { // - for rendezvous, both must be exactly the same (it's unspecified, which will be the first one) if (m_config.bRendezvous && thisconf != confstr) { return false; } SrtFilterConfig mycfg; if (!ParseFilterConfig(thisconf, (mycfg))) return false; // Check only if both have set a filter of the same type. if (mycfg.type != cfg.type) return false; // If so, then: // - for caller-listener configuration, accept the listener version. if (m_SrtHsSide == HSD_INITIATOR) { // This is a caller, this should apply all parameters received // from the listener, forcefully. for (map::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x) { mycfg.parameters[x->first] = x->second; } } else { if (!CheckFilterCompat((mycfg), cfg)) return false; } HLOGC(cnlog.Debug, log << CONID() << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters) << " FORGN: " << Printable(cfg.parameters)); ostringstream myos; myos << mycfg.type; for (map::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x) { myos << "," << x->first << ":" << x->second; } m_config.sPacketFilterConfig.set(myos.str()); HLOGC(cnlog.Debug, log << CONID() << "checkApplyFilterConfig: Effective config: " << thisconf); } else { // Take the foreign configuration as a good deal. HLOGC(cnlog.Debug, log << CONID() << "checkApplyFilterConfig: Good deal config: " << thisconf); m_config.sPacketFilterConfig.set(confstr); } // XXX Using less maximum payload size of IPv4 and IPv6; this is only about the payload size // for live. size_t efc_max_payload_size = SRT_MAX_PLSIZE_AF_INET6 - cfg.extra_size; if (m_config.zExpPayloadSize > efc_max_payload_size) { LOGC(cnlog.Warn, log << CONID() << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " << efc_max_payload_size << " bytes"); m_config.zExpPayloadSize = efc_max_payload_size; } return true; } #if SRT_ENABLE_BONDING bool CUDT::interpretGroup(CUDTSocket* lsn, const int32_t groupdata[], size_t data_size SRT_ATR_UNUSED, int hsreq_type_cmd SRT_ATR_UNUSED) { // `data_size` isn't checked because we believe it's checked earlier. // Also this code doesn't predict to get any other format than the official one, // so there are only data in two fields. Passing this argument is only left // for consistency and possibly changes in future. // We are granted these two fields do exist SRTSOCKET grpid = SRTSOCKET(groupdata[GRPD_GROUPID]); uint32_t gd = groupdata[GRPD_GROUPDATA]; SRT_GROUP_TYPE gtp = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); int link_weight = SrtHSRequest::HS_GROUP_WEIGHT::unwrap(gd); uint32_t link_flags = SrtHSRequest::HS_GROUP_FLAGS::unwrap(gd); if (m_config.iGroupConnect == 0) { m_RejectReason = SRT_REJ_GROUP; LOGC(cnlog.Error, log << CONID() << "HS/GROUP: this socket is not allowed for group connect."); return false; } // This is called when the group type has come in the handshake is invalid. if (gtp >= SRT_GTYPE_E_END) { m_RejectReason = SRT_REJ_GROUP; LOGC(cnlog.Error, log << CONID() << "HS/GROUP: incorrect group type value " << gtp << " (max is " << SRT_GTYPE_E_END << ")"); return false; } if (!isgroup(grpid)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS/GROUP: socket ID passed as a group ID is not a group ID"); return false; } // We have the group, now take appropriate action. // The redundancy group requires to make a mirror group // on this side, and the newly created socket should // be made belong to it. #if HVU_ENABLE_HEAVY_LOGGING HLOGC(cnlog.Debug, log << CONID() << "interpretGroup: STATE: HsSide=" << s_hs_side[m_SrtHsSide] << " HS MSG: " << MessageTypeStr(UMSG_EXT, hsreq_type_cmd) << " $" << grpid << " type=" << gtp << " weight=" << link_weight << " flags=0x" << fmt(link_flags, hex)); #endif // XXX Here are two separate possibilities: // // 1. This is a HS request and this is a newly created socket not yet part of any group. // 2. This is a HS response and the group is the mirror group for the group to which the agent belongs; we need to pin the mirror group as peer group // // These two situations can be only distinguished by the HS side. if (m_SrtHsSide == HSD_DRAW) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << CONID() << "IPE: interpretGroup: The HS side should have been already decided; it's still DRAW. Grouping " "rejected."); return false; } if (m_SrtHsSide == HSD_INITIATOR) { // Here we'll be only using a pre-existing group, so shared is enough. SharedLock guard_group_existence (uglobal().m_GlobControlLock); // Recheck broken flags after acquisition if (m_bClosing || m_bBroken) { m_RejectReason = SRT_REJ_CLOSE; LOGC(cnlog.Error, log << CONID() << "interpretGroup: closure during handshake, interrupting"); return false; } // This is a connection initiator that has requested the peer to make a // mirror group and join it, then respond its mirror group id. The // `grpid` variable contains this group ID; map this as your peer // group. If your group already has a peer group set, check if this is // the same id, otherwise the connection should be rejected. // So, first check the group of the current socket and see if a peer is set. CUDTGroup* pg = m_parent->m_GroupOf; if (!pg) { // This means that the responder has responded with a group membership, // but the initiator did not request any group membership presence. // Currently impossible situation. m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << CONID() << "IPE: HS/RSP: group membership responded, while not requested."); return false; } { // XXX Consider moving this to another function to avoid // exposing group lock. // Group existence is guarded, so we can now lock the group as well. ScopedLock gl(*pg->exp_groupLock()); // Now we know the group exists, but it might still be closed if (pg->closing()) { LOGC(cnlog.Error, log << CONID() << "HS/RSP: group was closed in the process, can't continue connecting"); m_RejectReason = SRT_REJ_IPE; return false; } SRTSOCKET peer = pg->peerid(); if (peer == SRT_INVALID_SOCK) { // This is the first connection within this group, so this group // has just been informed about the peer membership. Accept it. pg->set_peerid(grpid); HLOGC(cnlog.Debug, log << CONID() << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() << ", copying characteristic data"); // The call to syncWithSocket is copying // some interesting data from the first connected // socket. This should be only done for the first successful connection. pg->syncWithSocket(*this, HSD_INITIATOR); } // Otherwise the peer id must be the same as existing, otherwise // this group is considered already bound to another peer group. // (Note that the peer group is peer-specific, and peer id numbers // may repeat among sockets connected to groups established on // different peers). else if (peer != grpid) { LOGC(cnlog.Error, log << CONID() << "IPE: HS/RSP: group membership responded for peer $" << grpid << " but the current socket's group $" << pg->id() << " has already a peer $" << peer); m_RejectReason = SRT_REJ_GROUP; return false; } else { HLOGC(cnlog.Debug, log << CONID() << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $" << pg->peerid()); } } m_parent->m_GroupOf->debugGroup(); } else { // Here we'll be potentially creating a new group, hence exclusive is needed. ExclusiveLock guard_group_existence (uglobal().m_GlobControlLock); // Recheck broken flags after acquisition if (m_bClosing || m_bBroken) { m_RejectReason = SRT_REJ_CLOSE; LOGC(cnlog.Error, log << CONID() << "interpretGroup: closure during handshake, interrupting"); return false; } // This is a connection responder that has been requested to make a // mirror group and join it. Later on, the HS response will be sent // and its group ID will be added to the HS extensions as mirror group // ID to the peer. SRTSOCKET listener = lsn ? lsn->id(): SRT_INVALID_SOCK; SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags, listener); if (lgid == SRT_SOCKID_CONNREQ) return true; // already done if (lgid == SRT_INVALID_SOCK) { // NOTE: This error currently isn't reported by makeMePeerOf, // so this is left to handle a possible error introduced in future. m_RejectReason = SRT_REJ_GROUP; return false; // error occurred } if (!m_parent->m_GroupOf) { // Strange, we just added it... m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << CONID() << "IPE: socket not in group after adding to it"); return false; } groups::SocketData* f = m_parent->m_GroupMemberData; f->weight = link_weight; f->agent = m_parent->m_SelfAddr; f->peer = m_PeerAddr; m_parent->m_GroupOf->debugGroup(); } // That's all. For specific things concerning group // types, this will be later. return true; } #endif #if SRT_ENABLE_BONDING // NOTE: This function is called only in one place and it's done // exclusively on the listener side (HSD_RESPONDER, HSv5+). // [[using locked(s_UDTUnited.m_GlobControlLock)]] // XXX // SRT_TSA_NEEDS_LOCKED(CUDTUnited::m_GlobControlLock) // Can't be set because clang-tsa doesn't honor friend declarations // Alternatively you can move it to CUDTSocket class. SRTSOCKET CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags, SRTSOCKET listener) { // Note: This function will lock pg->m_GroupLock! CUDTSocket* s = m_parent; // Note that the socket being worked out here is about to be returned // from `srt_accept` call, and until this moment it will be inaccessible // for any other thread. It is then assumed that no other thread is accessing // it right now so there's no need to lock s->m_ControlLock. // Check if there exists a group that this one is a peer of. CUDTGroup* gp = uglobal().findPeerGroup_LOCKED(peergroup); bool was_empty = true; if (gp) { if (gp->type() != gtp) { LOGC(gmlog.Error, log << CONID() << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp << " agent group=$" << gp->id() << " type" << gp->type()); return SRT_INVALID_SOCK; } HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id()); gp->updatePending(listener); if (!gp->groupEmpty()) was_empty = false; } else { try { gp = &uglobal().newGroup(gtp); } catch (...) { // Expected exceptions are only those referring to system resources return SRT_INVALID_SOCK; } if (!gp->applyFlags(link_flags, m_SrtHsSide)) { // Wrong settings. Must reject. Delete group. uglobal().deleteGroup_LOCKED(gp); return SRT_INVALID_SOCK; } gp->set_peerid(peergroup); gp->deriveSettings(this); // This can only happen on a listener (it's only called on a site that is // HSD_RESPONDER), so it was a response for a groupwise connection. // Therefore such a group shall always be considered opened. // It's also set pending and it stays this way until accepted. gp->setOpenPending(listener); HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: no group has peer=$" << peergroup << " - creating new mirror group $" << gp->id()); } { ScopedLock glock (*gp->exp_groupLock()); if (gp->closing()) { HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group $" << gp->id() << " is being closed, can't process"); } if (was_empty) { gp->syncWithSocket(s->core(), HSD_RESPONDER); } } // Setting non-blocking reading for group socket. s->core().m_config.bSynRecving = false; s->core().m_config.bSynSending = false; // Copy of addSocketToGroup. No idea how many parts could be common, not much. // Check if the socket already is in the group groups::SocketData* f; if (gp->contains(m_SocketID, (f))) { // XXX This is internal error. Report it, but continue // (A newly created socket from acceptAndRespond should not have any group membership yet) LOGC(gmlog.Error, log << CONID() << "IPE (non-fatal): the socket is in the group, but has no clue about it!"); s->m_GroupOf = gp; s->m_GroupMemberData = f; return SRT_SOCKID_CONNREQ; } s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); s->m_GroupOf = gp; m_HSGroupType = gtp; // Record the remote address in the group data. return gp->id(); } void CUDT::synchronizeWithGroup(CUDTGroup* gp) { ScopedLock gl (*gp->exp_groupLock()); if (gp->isClosing()) return; // We have blocked here the process of connecting a new // socket and adding anything new to the group, so no such // thing may happen in the meantime. steady_clock::time_point start_time, peer_start_time; start_time = m_stats.tsStartTime; peer_start_time = m_tsRcvPeerStartTime; if (!gp->applyGroupTime((start_time), (peer_start_time))) { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: ST=" << FormatTime(m_stats.tsStartTime) << " -> " << FormatTime(start_time) << " PST=" << FormatTime(m_tsRcvPeerStartTime) << " -> " << FormatTime(peer_start_time)); m_stats.tsStartTime = start_time; m_tsRcvPeerStartTime = peer_start_time; } else { // This was the first connected socket and it defined start time. HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: ST=" << FormatTime(m_stats.tsStartTime) << " PST=" << FormatTime(m_tsRcvPeerStartTime)); } steady_clock::time_point rcv_buffer_time_base; bool rcv_buffer_wrap_period = false; steady_clock::duration rcv_buffer_udrift(0); if (m_bTsbPd && gp->getBufferTimeBase(this, (rcv_buffer_time_base), (rcv_buffer_wrap_period), (rcv_buffer_udrift))) { // We have at least one socket in the group, each socket should have // the value of the timebase set exactly THE SAME. // In case when we have the following situation: // - the existing link is before [LAST30] (so wrap period is off) // - the new link gets the timestamp from [LAST30] range // --> this will be recognized as entering the wrap period, next // timebase will get added a segment to this value // // The only dangerous situations could be when one link gets // timestamps from the [FOLLOWING30] and the other in [FIRST30], // but between them there's a 30s distance, considered large enough // time to not fill a network window. enterCS(m_RecvLock); // NOTE: Here is theoretically also the buffer lock required, but this // function is called from acceptAndRespond when no modifications in the // buffer or reading from any thread are for the time being possible. m_pRcvBuffer->applyGroupTime(rcv_buffer_time_base, rcv_buffer_wrap_period, m_iTsbPdDelay_ms * 1000, rcv_buffer_udrift); m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); leaveCS(m_RecvLock); HLOGC(gmlog.Debug, log << "AFTER HS: Set Rcv TsbPd mode: delay=" << (m_iTsbPdDelay_ms/1000) << "." << (m_iTsbPdDelay_ms%1000) << "s GROUP TIME BASE: " << FormatTime(rcv_buffer_time_base) << " (" << (rcv_buffer_wrap_period ? "" : "NOT") << " WRAP PERIOD)"); } else { HLOGC(gmlog.Debug, log << CONID() << "AFTER HS: (GROUP, but " << (m_bTsbPd ? "FIRST SOCKET is initialized normally)" : "no TSBPD set)")); updateSrtRcvSettings(); } // This function currently does nothing, just left for consistency // with updateAfterSrtHandshake(). updateSrtSndSettings(); // These are the values that are normally set initially by setters. int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; if (!gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn))) { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) << ") SND=%" << m_iSndLastAck.load() << " -> %" << snd_isn << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); setInitialRcvSeq(rcv_isn); setInitialSndSeq(snd_isn); } else { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: DEFINED ISN: RCV=%" << m_iRcvLastAck << " SND=%" << m_iSndLastAck.load()); } } #endif void CUDT::registerConnector(const sockaddr_any& addr, const steady_clock::time_point& ttl) { HLOGC(cnlog.Debug, log << "registerConnector: adding @" << id() << " addr=" << addr.str() << " TTL=" << FormatTime(ttl)); CMultiplexer::CRL r; r.m_iID = id(); r.m_pUDT = this; r.m_PeerAddr = addr; r.m_tsTTL = ttl; m_pMuxer->registerCRL(r); } void CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) { HLOGC(aclog.Debug, log << CONID() << "startConnect: -> " << serv_addr.str() << (m_config.bSynRecving ? " (SYNCHRONOUS)" : " (ASYNCHRONOUS)") << "..."); if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); if (m_bListening) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); if (m_bConnecting || m_bConnected) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); m_PeerAddr = serv_addr; // register this socket in the rendezvous queue // RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this // function steady_clock::duration ttl = m_config.tdConnTimeOut; if (m_config.bRendezvous) ttl *= 10; const steady_clock::time_point ttl_time = steady_clock::now() + ttl; registerConnector(serv_addr, ttl_time); UniqueLock cg (m_ConnectionLock); // The m_iType is used in the INDUCTION for nothing. This value is only regarded // in CONCLUSION handshake, however this must be created after the handshake version // is already known. UDT_DGRAM is the value that was the only valid in the old SRT // with HSv4 (it supported only live transmission), for HSv5 it will be changed to // handle handshake extension flags. m_ConnReq.m_iType = UDT_DGRAM; // Auto mode for Caller and in Rendezvous is equivalent to CIPHER_MODE_AES_CTR. if (m_config.iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO) m_config.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; // This is my current configuration if (m_config.bRendezvous) { // For rendezvous, use version 5 in the waveahand and the cookie. // In case when you get the version 4 waveahand, simply switch to // the legacy HSv4 rendezvous and this time send version 4 CONCLUSION. // The HSv4 client simply won't check the version nor the cookie and it // will be sending its waveahands with version 4. Only when the party // has sent version 5 waveahand should the agent continue with HSv5 // rendezvous. m_ConnReq.m_iVersion = HS_VERSION_SRT1; // m_ConnReq.m_iVersion = HS_VERSION_UDT4; // <--- Change in order to do regression test. m_ConnReq.m_iReqType = URQ_WAVEAHAND; m_ConnReq.m_iCookie = bake(serv_addr); // This will be also passed to a HSv4 rendezvous, but fortunately the old // SRT didn't read this field from URQ_WAVEAHAND message, only URQ_CONCLUSION. m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_config.iSndCryptoKeyLen); IF_HEAVY_LOGGING(const bool whether = m_config.iSndCryptoKeyLen != 0); HLOGC(aclog.Debug, log << CONID() << "startConnect (rnd): " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); m_RdvState = CHandShake::RDV_WAVING; m_SrtHsSide = HSD_DRAW; // initially not resolved. } else { // For caller-listener configuration, set the version 4 for INDUCTION // due to a serious problem in UDT code being also in the older SRT versions: // the listener peer simply sents the EXACT COPY of the caller's induction // handshake, except the cookie, which means that when the caller sents version 5, // the listener will respond with version 5, which is a false information. Therefore // HSv5 clients MUST send HS_VERSION_UDT4 from the caller, regardless of currently // supported handshake version. // // The HSv5 listener should only respond with INDUCTION with m_iVersion == HS_VERSION_SRT1. m_ConnReq.m_iVersion = HS_VERSION_UDT4; m_ConnReq.m_iReqType = URQ_INDUCTION; m_ConnReq.m_iCookie = 0; m_RdvState = CHandShake::RDV_INVALID; } m_ConnReq.m_iMSS = m_config.iMSS; // Defined as the size of the receiver buffer in packets, unless // SRTO_FC has been set to a less value. m_ConnReq.m_iFlightFlagSize = m_config.flightCapacity(); m_ConnReq.m_iID = m_SocketID; CIPAddress::encode(serv_addr, (m_ConnReq.m_piPeerIP)); if (forced_isn == SRT_SEQNO_NONE) { forced_isn = generateISN(); HLOGC(aclog.Debug, log << CONID() << "startConnect: ISN generated = " << forced_isn); } else { HLOGC(aclog.Debug, log << CONID() << "startConnect: ISN forced = " << forced_isn); } m_iISN = m_ConnReq.m_iISN = forced_isn; setInitialSndSeq(m_iISN); // Inform the server my configurations. CPacket reqpkt; reqpkt.setControl(UMSG_HANDSHAKE); reqpkt.allocate(m_iMaxSRTPayloadSize); // XXX NOTE: Now the memory for the payload part is allocated automatically, // and such allocated memory is also automatically deallocated in the // destructor. If you use CPacket::allocate, remember that you must not: // - delete this memory // - assign to m_pcData. // If you use only manual assignment to m_pCData, this is then manual // allocation and so it won't be deallocated in the destructor. // // (Desired would be to disallow modification of m_pcData outside the // control of methods.) // ID = 0, connection request reqpkt.set_id(SRT_SOCKID_CONNREQ); size_t hs_size = m_iMaxSRTPayloadSize; m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); // Note that CPacket::allocate() sets also the size // to the size of the allocated buffer, which not // necessarily is to be the size of the data. reqpkt.setLength(hs_size); const steady_clock::time_point tnow = steady_clock::now(); m_SndLastAck2Time = tnow; setPacketTS(reqpkt, tnow); HLOGC(cnlog.Debug, log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (TimeStamp: " << reqpkt.timestamp() << "). SENDING HS: " << m_ConnReq.show()); /* * Race condition if non-block connect response thread scheduled before we set m_bConnecting to true? * Connect response will be ignored and connecting will wait until timeout. * Maybe m_ConnectionLock handling problem? Not used in CUDT::connect(const CPacket& response) */ m_tsLastReqTime = tnow; m_bConnecting = true; // At this point m_SourceAddr is probably default-any, but this function // now requires that the address be specified here because there will be // no possibility to do it at any next stage of sending. channel()->sendto(serv_addr, reqpkt, m_SourceAddr); // /// //// ---> CONTINUE TO: .CUDT::processConnectRequest() /// (Take the part under condition: hs.m_iReqType == URQ_INDUCTION) //// <--- RETURN WHEN: channel()->sendto() is called. //// .... SKIP UNTIL m_RcvQueue.recvfrom() HERE.... //// (the first "sendto" will not be called due to being too early) /// // ////////////////////////////////////////////////////// // SYNCHRO BAR ////////////////////////////////////////////////////// if (!m_config.bSynRecving) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Exiting srt_connect() now."); return; } // That's it; now we need to wait until the Receiver Worker thread reports readiness. cg.unlock(); HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE DETECTED. Entering wait until RcvQ:worker finishes"); // SYNCHRONOUS VERSION: wait until the background process reports connection. CUniqueSync sendblock_cc (m_SendBlockLock, m_SendBlockCond); // Check every 1 second until either connected or broken. sync::steady_clock::time_point waiting_since = sync::steady_clock::now(); for (;;) { SRT_ATR_UNUSED bool signaled = sendblock_cc.wait_for(seconds_from(1)); // We don't care by what reasons it was interrupted. // Act according to the flags. HLOGC(cnlog.Debug, log << CONID() << "startConnect: sync wait interrupted on " << (signaled ? "SIGNAL" : "TIMEOUT")); try { if (m_RejectReason > SRT_REJ_UNKNOWN) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. Rejection detected - exiting"); // Rejection code is also internally used to designate timeout. // This is only needed to report correct error code. if (m_RejectReason == SRT_REJ_TIMEOUT) throw CUDTException(MJ_SETUP, MN_TIMEOUT); throw CUDTException(MJ_SETUP, MN_REJECTED); } if (m_bBroken) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. BROKEN detected - exiting"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST); } if (m_bClosing || m_bBreaking) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. CLOSED detected - exiting"); throw CUDTException(MJ_SETUP, MN_CLOSED); } if (m_bConnected) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. CONNECTED detected - exit with success"); break; } // Wait only up until connection timeout if (sync::steady_clock::now() - waiting_since > m_config.tdConnTimeOut) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. TIMEOUT detected - exiting"); throw CUDTException(MJ_SETUP, MN_TIMEOUT); } } catch (...) { m_bConnecting = false; m_pMuxer->removeConnector(m_SocketID); throw; } } // Parameters at the end. HLOGC(cnlog.Debug, log << CONID() << "startConnect: success. Parameters: sourceIP=" << m_SourceAddr.str() << " mss=" << m_config.iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize() << " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iSRTT.load() << " bw=" << m_iBandwidth.load()); } // Asynchronous connection EConnectStatus CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT { EConnectStatus cst = CONN_CONTINUE; CUDTException e; ScopedLock cg(m_ConnectionLock); HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing"); cst = processConnectResponse(pkt, &e); HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: response processing result: " << ConnectStatusStr(cst) << "; REQ-TIME LOW to enforce immediate response"); m_tsLastReqTime = steady_clock::time_point(); return cst; } bool CUDT::processAsyncConnectRequest(EReadStatus rst, EConnectStatus cst, const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr) { // IMPORTANT! // This function is called, still asynchronously, but in the order // of call just after the call to the above processAsyncConnectResponse. // This should have got the original value returned from // processConnectResponse through processAsyncConnectResponse. CPacket reqpkt; reqpkt.setControl(UMSG_HANDSHAKE); reqpkt.allocate(m_iMaxSRTPayloadSize); const steady_clock::time_point now = steady_clock::now(); setPacketTS(reqpkt, now); m_tsLastReqTime = now; // ID = 0, connection request reqpkt.set_id(!m_config.bRendezvous ? SRT_SOCKID_CONNREQ : m_ConnRes.m_iID); HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: REQ-TIME: HIGH. Address to @" << reqpkt.id() << " peer=" << m_ConnRes.m_iID); bool status = true; ScopedLock cg(m_ConnectionLock); if (!m_bOpened) // Check the socket has not been closed before already. return false; if (cst == CONN_RENDEZVOUS) { HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: passing to processRendezvous"); cst = processRendezvous(pResponse, serv_addr, rst, (reqpkt)); if (cst == CONN_ACCEPT) { HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. " "Done."); notifyBlockingConnect(); return true; } if (cst != CONN_CONTINUE) { // processRendezvous already set the reject reason LOGC(cnlog.Warn, log << CONID() << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further."); if (m_RejectReason == SRT_REJ_UNKNOWN) m_RejectReason = SRT_REJ_ROGUE; sendRendezvousRejection(serv_addr, (reqpkt)); status = false; } notifyBlockingConnect(); } else if (cst == CONN_REJECT) { notifyBlockingConnect(); // m_RejectReason already set at worker_ProcessAddressedPacket. LOGC(cnlog.Warn, log << CONID() << "processAsyncConnectRequest: REJECT reported from HS processing: " << srt_rejectreason_str(m_RejectReason) << " - not processing further"); // m_tsLastReqTime = steady_clock::time_point(); XXX ? return false; } else { // (this procedure will be also run for HSv4 rendezvous) HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: serializing HS: buffer size=" << reqpkt.getLength()); if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (reqpkt), (m_ConnReq))) { // All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired". LOGC(cnlog.Error, log << CONID() << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing."); status = false; } else { HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) << " to socket " << reqpkt.id() << " size=" << reqpkt.getLength()); } } if (!status) { notifyBlockingConnect(); return false; /* XXX Shouldn't it send a single response packet for the rejection? // Set the version to 0 as "handshake rejection" status and serialize it CHandShake zhs; size_t size = reqpkt.getLength(); zhs.store_to((reqpkt.m_pcData), (size)); reqpkt.setLength(size); */ } HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: setting REQ-TIME HIGH, SENDING HS:" << m_ConnReq.show()); m_tsLastReqTime = steady_clock::now(); channel()->sendto(serv_addr, reqpkt, m_SourceAddr); return status; } void CUDT::sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& r_rsppkt) { // We can reuse m_ConnReq because we are about to abandon the connection process. m_ConnReq.m_iReqType = URQFailure(m_RejectReason); // Assumed that r_rsppkt refers to a packet object that was already prepared // to be used for storing the handshake there. size_t size = r_rsppkt.getLength(); m_ConnReq.store_to((r_rsppkt.m_pcData), (size)); r_rsppkt.setLength(size); HLOGC(cnlog.Debug, log << CONID() << "sendRendezvousRejection: using code=" << m_ConnReq.m_iReqType << " for reject reason code " << m_RejectReason.load() << " (" << srt_rejectreason_str(m_RejectReason) << ")"); setPacketTS(r_rsppkt, steady_clock::now()); channel()->sendto(serv_addr, r_rsppkt, m_SourceAddr); } HandshakeSide CUDT::compareCookies(int32_t req, int32_t res) { IF_HEAVY_LOGGING(fmtc hex08 = fmtc().uhex().fillzero().width(8)); // XXX ROUND VERSION on 32-bit if (req < res) { HLOGC(cnlog.Debug, log << "compareCookies: " << "REQ: " << fmt(req, hex08) << " RES: " << fmt(res, hex08) << " - result: RESPONDER"); return HSD_RESPONDER; } else if (req > res) { HLOGC(cnlog.Debug, log << "compareCookies: " << "REQ: " << fmt(req, hex08) << " RES: " << fmt(res, hex08) << " - result: INITIATOR"); return HSD_INITIATOR; } else { return HSD_DRAW; } } // NOTE: This function remains here for historical reasons only. This is how this // was last done in the version 1.5.5. For reference only. HandshakeSide CUDT::backwardCompatibleCookieContest(int32_t req, int32_t res) { const int64_t xreq = int64_t(req); const int64_t xres = int64_t(res); const int64_t contest = xreq - xres; IF_HEAVY_LOGGING(fmtc hex64 = fmtc().uhex().fillzero().width(16)); HLOGC(cnlog.Debug, log << "cookieContest: agent=" << req << " peer=" << res << " X64: " << fmt(xreq, hex64) << " vs. " << fmt(xres, hex64) << " DIFF: " << fmt(contest, hex64)); if ((contest & 0xFFFFFFFF) == 0) { return HSD_DRAW; } if (contest & 0x80000000) { const int64_t revert = xres - xreq; if (revert & 0x80000000 && req > res) return HSD_INITIATOR; return HSD_RESPONDER; } return HSD_INITIATOR; } void CUDT::cookieContest() { if (m_SrtHsSide != HSD_DRAW) return; // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0) { HLOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie << " - ERROR: zero not allowed!"); return; } m_SrtHsSide = compareCookies(m_ConnReq.m_iCookie, m_ConnRes.m_iCookie); } // This function should complete the data for KMX needed for an out-of-band // handshake response. Possibilities are: // - There's no KMX (including first responder's handshake in rendezvous). This writes 0 to w_kmdatasize. // - The encryption status is failure. Respond with fail code and w_kmdatasize = 1. // - The last KMX was successful. Respond with the original kmdata and their size in w_kmdatasize. EConnectStatus CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatasize) { // If the last CONCLUSION message didn't contain the KMX extension, there's // no key recorded yet, so it can't be extracted. Mark this w_kmdatasize empty though. int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ)) { // m_pCryptoControl can be NULL if the socket has been closed already. See issue #2231. if (!m_CryptoControl.initialized()) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Error, log << CONID() << "IPE: craftKmResponse needs to send KM, but CryptoControl does not exist." << " Socket state: " << fmt_onoff(m_bConnected) << "connected, " << fmt_onoff(m_bConnecting) << "connecting, " << fmt_onoff(m_bBroken) << "broken, " << fmt_onoff(m_bOpened) << "opened, " << fmt_onoff(m_bClosing) << "closing."); return CONN_REJECT; } // This is a periodic handshake update, so you need to extract the KM data from the // first message, provided that it is there. size_t msgsize = m_CryptoControl.getKmMsg_size(0); if (msgsize == 0) { switch (m_CryptoControl.kmState().rcv) { // If the KMX process ended up with a failure, the KMX is not recorded. // In this case as the KMRSP answer the "failure status" should be crafted. case SRT_KM_S_NOSECRET: case SRT_KM_S_BADSECRET: { HLOGC(cnlog.Debug, log << CONID() << "craftKmResponse: No KMX recorded, status = " << KmStateStr(m_CryptoControl.kmState().rcv) << ". Respond it."); // Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case, // that is, copy the NOSECRET code into KMX message. SRT_KM_STATE rstate = m_CryptoControl.kmState().rcv; memcpy((aw_kmdata), &rstate, sizeof(int32_t)); w_kmdatasize = 1; } break; // Treat as ACCEPT in general; might change to REJECT on enforced-encryption default: // Remaining values: // UNSECURED: should not fall here at all // SECURING: should not happen in HSv5 // SECURED: should have received the recorded KMX correctly (getKmMsg_size(0) > 0) { m_RejectReason = SRT_REJ_IPE; // Remaining situations: // - password only on this site: shouldn't be considered to be sent to a no-password site LOGC(cnlog.Error, log << CONID() << "craftKmResponse: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV=" << KmStateStr(m_CryptoControl.kmState().rcv) << " SND=" << KmStateStr(m_CryptoControl.kmState().snd)); return CONN_REJECT; } break; } } else { w_kmdatasize = msgsize / 4; if (msgsize > w_kmdatasize * 4) { // Sanity check LOGC(cnlog.Error, log << CONID() << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize); memset((aw_kmdata + (w_kmdatasize * 4)), 0, msgsize - (w_kmdatasize * 4)); ++w_kmdatasize; } HLOGC(cnlog.Debug, log << CONID() << "craftKmResponse: getting KM DATA from the fore-recorded KMX from KMREQ, size=" << w_kmdatasize); memcpy((aw_kmdata), m_CryptoControl.getKmMsg_data(0), msgsize); } } else { HLOGC(cnlog.Debug, log << CONID() << "craftKmResponse: no KMX flag - not extracting KM data for KMRSP"); w_kmdatasize = 0; } return CONN_ACCEPT; } #if HVU_ENABLE_HEAVY_LOGGING // NOTE VALUES: // none = 0 // SRT_CMD_HSREQ = 1 // SRT_CMD_HSRSP = 2 static std::string s_hs_ext_side[3] = {"no", "HSREQ", "HSRSP"}; #endif EConnectStatus CUDT::processRendezvous( const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr, EReadStatus rst, CPacket& w_reqpkt) { if (m_RdvState == CHandShake::RDV_CONNECTED) { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: already in CONNECTED state."); return CONN_ACCEPT; } uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; cookieContest(); // We know that the other side was contacted and the other side has sent // the handshake message - we know then both cookies. If it's a draw, it's // a very rare case of creating identical cookies. if (m_SrtHsSide == HSD_DRAW) { m_RejectReason = SRT_REJ_RDVCOOKIE; LOGC(cnlog.Error, log << CONID() << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute."); return CONN_REJECT; } UDTRequestType rsp_type = URQ_FAILURE_TYPES; // just to track uninitialized errors // We can assume that the Handshake packet received here as 'response' // is already serialized in m_ConnRes. Check extra flags that are meaningful // for further processing here. int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (m_ConnRes.m_iReqType == URQ_CONCLUSION) { // ext_flags are interpreted here as extension markers. // Expected is that: // if m_SrtHsSide == HSD_RESPONDER, expected is ext_flags != 0 (WITH extensions AND of HSREQ type). // if m_SrtHsSide == HSD_INITIATOR, expected is: // - ext_flags == 0, if this is the very first response (at least it hasn´t received any HSREQ yet) // - ext_flags != 0, HSREQ flag is set, and extensions contain HSRSP. HandshakeSide expected_side = m_SrtHsSide; HLOGC(cnlog.Debug, log << "processRendezvous: {" << s_hs_side[m_SrtHsSide] << "}[" << CHandShake::RdvStateStr(m_RdvState) << "] " << " receives CONCLUSION/" << SrtCmdName(m_ConnRes.m_extensionType) << " flags:" << fmt(ext_flags, fmtc().uhex().fillzero().width(4)) << " expects " << (expected_side == HSD_INITIATOR ? "HSRSP/noext" : "HSREQ")); // XXX While checking for flags, you might also check for the SRT Version. // This however requires breaking up the process of handshake parsing so that // all is parsed completely into temporary containers and only then interpreted so that // details, including those available in the extensions, are available at any time of processing. // m_SrtHsSide is now either HSD_INITIATOR or HSD_RESPONDER; all others were already handled above. if (m_SrtHsSide == HSD_INITIATOR) { if (ext_flags != 0) { // We have received hs/conclusion/ext from a side that should have // designated itself as RESPONDER. Check if this hs HSRSP. if (m_ConnRes.m_extensionType == SRT_CMD_HSREQ) { HLOGC(cnlog.Debug, log << "processRendezvous: HS SIDE:INITIATOR, received HS with extension:HSREQ - COLLISION!"); // !!! COLLISION !!! expected_side = HSD_RESPONDER; } } } else { // HSD_RESPONDER - expected is that ext_flags is set and type is HSREQ if (m_ConnRes.m_extensionType != SRT_CMD_HSREQ) { HLOGC(cnlog.Debug, log << "processRendezvous: HS SIDE:RESPONDER, received HS with extension:" << SrtCmdName(m_ConnRes.m_extensionType) << " - COLLISION!"); // !!! COLLISION !!! expected_side = HSD_INITIATOR; } } // In all other cases we have an error and a "side collision" case is in order. // We deem that: // - if both sides are >= 1.6.0, this isn't possible, unless it's a DRAW, which was handled already. // - if this WAS the case, we state this is an old version with a bug on the cookie resolution; in that // case, you should simply ADAPT YOURSELF to the resolution of the other side, as this doesn't matter, // the other side will not understand anyway, while this will at least allow connection and proper // side resolution. if (expected_side != m_SrtHsSide) { LOGC(cnlog.Error, log << CONID() << "COOKIE COLLISION: {" << s_hs_side[m_SrtHsSide] << "} gets " << SrtCmdName(m_ConnRes.m_extensionType) << " - ADAPTING to the peer, switching to {" << s_hs_side[expected_side] << "}"); m_SrtHsSide = expected_side; } } int tosend_ext_type = 0; if (ext_flags) { if (m_SrtHsSide == HSD_INITIATOR) tosend_ext_type = SRT_CMD_HSREQ; else tosend_ext_type = SRT_CMD_HSRSP; } rendezvousSwitchState((rsp_type), (tosend_ext_type)); if (rsp_type > URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(rsp_type); HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type)); return CONN_REJECT; } checkUpdateCryptoKeyLen("processRendezvous", m_ConnRes.m_iType); // We have three possibilities here as it comes to HSREQ extensions: // 1. The agent is loser in attention state, it sends EMPTY conclusion (without extensions) // 2. The agent is loser in initiated state, it interprets incoming HSREQ and creates HSRSP // 3. The agent is winner in attention or fine state, it sends HSREQ extension m_ConnReq.m_iReqType = rsp_type; m_ConnReq.m_extensionType = tosend_ext_type; // This must be done before prepareConnectionObjects(), because it sets ISN and m_iMaxSRTPayloadSize needed to create buffers. if (!applyResponseSettings(pResponse)) { LOGC(cnlog.Error, log << CONID() << "processRendezvous: peer settings rejected"); return CONN_REJECT; } // The CryptoControl must be created by the prepareConnectionObjects() before interpreting and creating HSv5 extensions // because the it will be used there. if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, NULL) || !prepareBuffers(NULL)) { // m_RejectReason already handled HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); return CONN_REJECT; } // Case 2. if (tosend_ext_type == SRT_CMD_HSRSP) { // This means that we have received HSREQ extension with the handshake, so we need to interpret // it and craft the response. if (rst == RST_OK) { // We have JUST RECEIVED packet in this session (not that this is called as periodic update). // Sanity check m_tsLastReqTime = steady_clock::time_point(); if (!pResponse || pResponse->getLength() == size_t(-1)) { m_RejectReason = SRT_REJ_IPE; LOGC(cnlog.Fatal, log << CONID() << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)"); return CONN_REJECT; } if (!interpretSrtHandshake(NULL, m_ConnRes, *pResponse, kmdata, &kmdatasize)) { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW."); return CONN_REJECT; } updateAfterSrtHandshake(HS_VERSION_SRT1); // Pass on, inform about the shortened response-waiting period. HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately."); } else { // This is a repeated handshake, so you can't use the incoming data to // prepare data for createSrtHandshake. They have to be extracted from inside. EConnectStatus conn = craftKmResponse((kmdata), (kmdatasize)); if (conn != CONN_ACCEPT) return conn; } // No matter the value of needs_extension, the extension is always needed // when HSREQ was interpreted (to store HSRSP extension). m_ConnReq.m_extensionType = tosend_ext_type; HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize); w_reqpkt.setLength(m_iMaxSRTPayloadSize); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (w_reqpkt), (m_ConnReq))) { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW"); m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } // This means that it has received URQ_CONCLUSION with HSREQ, agent is then in RDV_FINE // state, it sends here URQ_CONCLUSION with HSREQ/KMREQ extensions and it awaits URQ_AGREEMENT. return CONN_CONTINUE; } // Special case: if URQ_AGREEMENT is to be sent, when this side is INITIATOR, // then it must have received HSRSP, so it must interpret it. Otherwise it would // end up with URQ_DONE, which means that it is the other side to interpret HSRSP. if (m_SrtHsSide == HSD_INITIATOR && m_ConnReq.m_iReqType == URQ_AGREEMENT) { // The same is done in CUDT::postConnect(), however this section will // not be done in case of rendezvous. The section in postConnect() is // predicted to run only in regular CALLER handling. if (rst != RST_OK || !pResponse || pResponse->getLength() == size_t(-1)) { // Actually the -1 length would be an IPE, but it's likely that this was reported already. HLOGC( cnlog.Debug, log << CONID() << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on existing data)"); } else { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"); if (!interpretSrtHandshake(NULL, m_ConnRes, *pResponse, 0, 0)) { // m_RejectReason is already set, so set the reqtype accordingly m_ConnReq.m_iReqType = URQFailure(m_RejectReason); return CONN_REJECT; } } // This should be false, make a kinda assert here. if (tosend_ext_type) { LOGC(cnlog.Fatal, log << CONID() << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS"); m_ConnReq.m_extensionType = 0; } updateAfterSrtHandshake(HS_VERSION_SRT1); } HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: COOKIES Agent/Peer: " << m_ConnReq.m_iCookie << "/" << m_ConnRes.m_iCookie << " HSD:" << (m_SrtHsSide == HSD_INITIATOR ? "initiator" : "responder") << " STATE:" << CHandShake::RdvStateStr(m_RdvState) << " ..."); if (rsp_type == URQ_DONE) { HLOGC(cnlog.Debug, log << CONID() << "... WON'T SEND any response, both sides considered connected"); } else { HLOGC(cnlog.Debug, log << CONID() << "... WILL SEND " << RequestTypeStr(rsp_type) << " with" << s_hs_ext_side[m_ConnReq.m_extensionType] << " SRT HS extensions"); } // This marks the information for the serializer that // the SRT handshake extension is required. // Rest of the data will be filled together with // serialization. m_ConnReq.m_extensionType = tosend_ext_type; w_reqpkt.setLength(m_iMaxSRTPayloadSize); if (m_RdvState == CHandShake::RDV_CONNECTED) { int cst = postConnect(pResponse, true, 0); if (cst == CONN_REJECT) { // m_RejectReason already set HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rejecting due to problems in postConnect."); return CONN_REJECT; } } // URQ_DONE or URQ_AGREEMENT can be the result if the state is RDV_CONNECTED. // If URQ_DONE, then there's nothing to be done, when URQ_AGREEMENT then return // CONN_CONTINUE to make the caller send again the contents if the packet buffer, // this time with URQ_AGREEMENT message, but still consider yourself connected. if (rsp_type == URQ_DONE) { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)"); return CONN_ACCEPT; } // createSrtHandshake moved here because if the above conditions are satisfied, // no response is going to be send, so nothing needs to be "created". // needs_extension here distinguishes between cases 1 and 3. // NOTE: in case when interpretSrtHandshake was run under the conditions above (to interpret HSRSP), // then createSrtHandshake below will create only empty AGREEMENT message. if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (w_reqpkt), (m_ConnReq))) { // m_RejectReason already set LOGC(cnlog.Warn, log << CONID() << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW"); m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } if (rsp_type == URQ_AGREEMENT && m_RdvState == CHandShake::RDV_CONNECTED) { // We are using our own serialization method (not the one called after // processConnectResponse, this is skipped in case when this function // is called), so we can also send this immediately. Agreement must be // sent just once and the party must switch into CONNECTED state - in // contrast to CONCLUSION messages, which should be sent in loop repeatedly. // // Even though in theory the AGREEMENT message sent just once may miss // the target (as normal thing in UDP), this is little probable to happen, // and this doesn't matter much because even if the other party doesn't // get AGREEMENT, but will get payload or KEEPALIVE messages, it will // turn into connected state as well. The AGREEMENT is rather kinda // catalyzer here and may turn the entity on the right track faster. When // AGREEMENT is missed, it may have kinda initial tearing. const steady_clock::time_point now = steady_clock::now(); m_tsLastReqTime = now; setPacketTS(w_reqpkt, now); HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH."); channel()->sendto(serv_addr, w_reqpkt, m_SourceAddr); return CONN_ACCEPT; } if (rst == RST_OK) { // the request time must be updated so that the next handshake can be sent out immediately HLOGC(cnlog.Debug, log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType) << " REQ-TIME: LOW to send immediately, consider yourself connected"); m_tsLastReqTime = steady_clock::time_point(); } else { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected"); } return CONN_CONTINUE; } // [[using locked(m_ConnectionLock)]]; EConnectStatus CUDT::processConnectResponse(const CPacket& response, CUDTException* eout) ATR_NOEXCEPT { // NOTE: ASSUMED LOCK ON: m_ConnectionLock. // this is the 2nd half of a connection request. If the connection is setup successfully this returns 0. // Returned values: // - CONN_REJECT: there was some error when processing the response, connection should be rejected // - CONN_ACCEPT: the handshake is done and finished correctly // - CONN_CONTINUE: the induction handshake has been processed correctly, and expects CONCLUSION handshake if (!m_bConnecting) return CONN_REJECT; // This is required in HSv5 rendezvous, in which it should send the URQ_AGREEMENT message to // the peer, however switch to connected state. HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: TYPE:" << (response.isControl() ? MessageTypeStr(response.getType(), response.getExtendedType()) : string("DATA"))); // ConnectStatus res = CONN_REJECT; // used later for status - must be declared here due to goto POST_CONNECT. // For HSv4, the data sender is INITIATOR, and the data receiver is RESPONDER, // regardless of the connecting side affiliation. This will be changed for HSv5. HandshakeSide hsd = m_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; // (defined here due to 'goto' below). // SRT peer may send the SRT handshake private message (type 0x7fff) before a keep-alive. // This condition is checked when the current agent is trying to do connect() in rendezvous mode, // but the peer was faster to send a handshake packet earlier. This makes it continue with connecting // process if the peer is already behaving as if the connection was already established. // This value will check either the initial value, which is less than SRT1, or // the value previously loaded to m_ConnReq during the previous handshake response. // For the initial form this value should not be checked. bool hsv5 = m_ConnRes.m_iVersion >= HS_VERSION_SRT1; if (m_config.bRendezvous && (m_RdvState == CHandShake::RDV_CONNECTED // somehow Rendezvous-v5 switched it to CONNECTED. || !response.isControl() // WAS A PAYLOAD PACKET. || (response.getType() == UMSG_KEEPALIVE) // OR WAS A UMSG_KEEPALIVE message. || (response.getType() == UMSG_EXT) // OR WAS a CONTROL packet of some extended type (i.e. any SRT specific) ) // This may happen if this is an initial state in which the socket type was not yet set. // If this is a field that holds the response handshake record from the peer, this means that it wasn't received // yet. HSv5: added version check because in HSv5 the m_iType field has different meaning and it may be 0 in // case when the handshake does not carry SRT extensions. && (hsv5 || m_ConnRes.m_iType != UDT_UNDEFINED)) { // a data packet or a keep-alive packet comes, which means the peer side is already connected // in this situation, the previously recorded response will be used // In HSv5 this situation is theoretically possible if this party has missed the URQ_AGREEMENT message. HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in"); if (hsv5) { m_RdvState = CHandShake::RDV_CONNECTED; } return postConnect(&response, hsv5, eout); } if (!response.isControl(UMSG_HANDSHAKE)) { m_RejectReason = SRT_REJ_ROGUE; if (!response.isControl()) { LOGC(cnlog.Warn, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected"); } else { LOGC(cnlog.Error, log << CONID() << "processConnectResponse: CONFUSED: expected UMSG_HANDSHAKE as connection not yet established, " "got: " << MessageTypeStr(response.getType(), response.getExtendedType())); if (response.getType() == UMSG_SHUTDOWN) { LOGC(cnlog.Error, log << CONID() << "processConnectResponse: UMSG_SHUTDOWN received, rejecting connection."); return CONN_REJECT; } } if (m_config.bRendezvous) { // In rendezvous mode we expect that both sides are known // to the service operator (unlike a listener, which may // operate connections from unknown sources). This means that // the connection process should be terminated anyway, on // whichever side it would happen. return CONN_REJECT; } return CONN_CONFUSED; } if (m_config.bRendezvous) { m_SourceAddr = response.udpDestAddr(); } if (m_ConnRes.load_from(response.m_pcData, response.getLength()) == -1) { m_RejectReason = SRT_REJ_ROGUE; // Handshake data were too small to reach the Handshake structure. Reject. LOGC(cnlog.Error, log << CONID() << "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting."); return CONN_REJECT; } HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show()); if (m_ConnRes.m_iReqType >= URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); LOGC(cnlog.Warn, log << CONID() << "processConnectResponse: rejecting per reception of a rejection HS response: " << RequestTypeStr(m_ConnRes.m_iReqType)); return CONN_REJECT; } if (size_t(m_ConnRes.m_iMSS) > CPacket::ETH_MAX_MTU_SIZE) { // Yes, we do abort to prevent buffer overrun. Set your MSS correctly // and you'll avoid problems. m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Fatal, log << CONID() << "MSS size " << m_config.iMSS << "exceeds MTU size!"); return CONN_REJECT; } // (see createCrypter() call below) // // The CCryptoControl attached object must be created early // because it will be required to create a conclusion handshake in HSv5 // if (m_config.bRendezvous) { // SANITY CHECK: A rendezvous socket should reject any caller requests (it's not a listener) if (m_ConnRes.m_iReqType == URQ_INDUCTION) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "processConnectResponse: Rendezvous-point received INDUCTION handshake (expected WAVEAHAND). " "Rejecting."); return CONN_REJECT; } // The procedure for version 5 is completely different and changes the states // differently, so the old code will still maintain HSv4 the old way. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4) { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED."); return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous(). } // XXX BELOW CODE is for handling HSv4, should return error instead. HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendsezvous HSv4 DETECTED."); // So, here it has either received URQ_WAVEAHAND handshake message (while it should be in URQ_WAVEAHAND itself) // or it has received URQ_CONCLUSION/URQ_AGREEMENT message while this box has already sent URQ_WAVEAHAND to the // peer, and DID NOT send the URQ_CONCLUSION yet. if (m_ConnReq.m_iReqType == URQ_WAVEAHAND || m_ConnRes.m_iReqType == URQ_WAVEAHAND) { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW. got HS RDV. Agent state:" << RequestTypeStr(m_ConnReq.m_iReqType) << " Peer HS:" << m_ConnRes.show()); // Here we could have received WAVEAHAND or CONCLUSION. // For HSv4 simply switch to CONCLUSION for the sake of further handshake rolling. // For HSv5, make the cookie contest and basing on this decide, which party // should provide the HSREQ/KMREQ attachment. if (!createCrypter(hsd)) { m_RejectReason = SRT_REJ_RESOURCE; m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE); // the request time must be updated so that the next handshake can be sent out immediately. m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } m_ConnReq.m_iReqType = URQ_CONCLUSION; // the request time must be updated so that the next handshake can be sent out immediately. m_tsLastReqTime = steady_clock::time_point(); return CONN_CONTINUE; } else { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand"); } } else { // set cookie if (m_ConnRes.m_iReqType == URQ_INDUCTION) { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" << fmt(m_ConnRes.m_iCookie, hex) << " version:" << m_ConnRes.m_iVersion << "), sending CONCLUSION HS with this cookie"); m_ConnReq.m_iCookie = m_ConnRes.m_iCookie; m_ConnReq.m_iReqType = URQ_CONCLUSION; // Here test if the LISTENER has responded with version HS_VERSION_SRT1, // it means that it is HSv5 capable. It can still accept the HSv4 handshake. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4) { const int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (hs_flags != SrtHSRequest::SRT_MAGIC_CODE) { LOGC(cnlog.Warn, log << CONID() << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE."); m_RejectReason = SRT_REJ_ROGUE; return CONN_REJECT; } checkUpdateCryptoKeyLen("processConnectResponse", m_ConnRes.m_iType); // This will catch HS_VERSION_SRT1 and any newer. // Set your highest version. m_ConnReq.m_iVersion = HS_VERSION_SRT1; // CONTROVERSIAL: use 0 as m_iType according to the meaning in HSv5. // The HSv4 client might not understand it, which means that agent // must switch itself to HSv4 rendezvous, and this time iType should // be set to UDT_DGRAM value. m_ConnReq.m_iType = 0; // This marks the information for the serializer that // the SRT handshake extension is required. // Rest of the data will be filled together with // serialization. m_ConnReq.m_extensionType = SRT_CMD_HSREQ; // For HSv5, the caller is INITIATOR and the listener is RESPONDER. // The m_config.bDataSender value should be completely ignored and the // connection is always bidirectional. hsd = HSD_INITIATOR; m_SrtHsSide = hsd; } m_tsLastReqTime = steady_clock::time_point(); if (!createCrypter(hsd)) { m_RejectReason = SRT_REJ_RESOURCE; return CONN_REJECT; } // NOTE: This setup sets URQ_CONCLUSION and appropriate data in the handshake structure. // The full handshake to be sent will be filled back in the caller function -- CUDT::startConnect(). return CONN_CONTINUE; } } EConnectStatus cst = postConnect(&response, false, eout); notifyBlockingConnect(); return cst; } bool CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) ATR_NOEXCEPT { if (!m_ConnRes.valid()) { LOGC(cnlog.Error, log << CONID() << "applyResponseSettings: ROGUE HANDSHAKE - rejecting"); m_RejectReason = SRT_REJ_ROGUE; return false; } m_TransferIPVersion = m_PeerAddr.family(); if (m_PeerAddr.family() == AF_INET6) { // Check if the m_PeerAddr's address is a mapped IPv4. If so, // define Transfer IP version as 4 because this one will be used. if (checkMappedIPv4(m_PeerAddr.sin6)) m_TransferIPVersion = AF_INET; } // Re-configure according to the negotiated values. m_config.iMSS = m_ConnRes.m_iMSS; const size_t full_hdr_size = CPacket::udpHeaderSize(m_TransferIPVersion) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; if (m_iMaxSRTPayloadSize < int(m_config.zExpPayloadSize)) { LOGC(cnlog.Error, log << CONID() << "applyResponseSettings: negotiated MSS=" << m_config.iMSS << " leaves too little payload space " << m_iMaxSRTPayloadSize << " for configured payload size " << m_config.zExpPayloadSize); m_RejectReason = SRT_REJ_CONFIG; return false; } HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); // NOTE: m_RcvAckLock required, but this is here allowed because not all threads run yet m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; const int udpsize = m_config.iMSS - CPacket::udpHeaderSize(m_TransferIPVersion); m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; m_iPeerISN = m_ConnRes.m_iISN; setInitialRcvSeq(m_iPeerISN); m_iRcvCurrPhySeqNo = CSeqNo::decseq(m_ConnRes.m_iISN); m_PeerID = m_ConnRes.m_iID; memcpy((m_piSelfIP), m_ConnRes.m_piPeerIP, sizeof m_piSelfIP); if (pHspkt) m_SourceAddr = pHspkt->udpDestAddr(); HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: HANDSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " peer-ISN=" << m_ConnRes.m_iISN << " local-ISN=" << m_iISN << " peerID=" << m_ConnRes.m_iID << " sourceIP=" << m_SourceAddr.str()); return true; } void CUDT::notifyBlockingConnect() { if (m_config.bSynRecving) { CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); } } EConnectStatus CUDT::postConnect(const CPacket* pResponse, bool rendezvous, CUDTException *eout) ATR_NOEXCEPT { if (m_ConnRes.m_iVersion < HS_VERSION_SRT1) m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly in SRT HS. // This part fictionally "loses" incoming conclusion HS given number of times. #if SRT_ENABLE_FAKE_LOSS_HS > 0 static int fail_count = SRT_ENABLE_FAKE_LOSS_HS; if (--fail_count) { LOGC(cnlog.Note, log << "postConnect: FAKE LOSS HS conclusion message"); return CONN_CONTINUE; } #endif // This procedure isn't being executed in rendezvous because // in rendezvous it's completed before calling this function. if (!rendezvous) { HLOGC(cnlog.Debug, log << CONID() << "postConnect: packet:" << fmt_yesno(pResponse) << " rendezvous:" << fmt_yesno(rendezvous)); // The "local storage depleted" case shouldn't happen here, but // this is a theoretical path that needs prevention. bool ok = pResponse; if (!ok) { m_RejectReason = SRT_REJ_IPE; if (eout) { *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); } return CONN_REJECT; } // [[assert (pResponse != NULL)]]; // NOTE: THIS function must be called before calling prepareConnectionObjects. // The reason why it's not part of prepareConnectionObjects is that the activities // done there are done SIMILAR way in acceptAndRespond, which also calls this // function. In fact, prepareConnectionObjects() represents the code that was // done separately in processConnectResponse() and acceptAndRespond(), so this way // this code is now common. Now acceptAndRespond() does "manually" something similar // to applyResponseSettings(), just a little bit differently. This SHOULD be made // common as a part of refactoring job, just needs a bit more time. // // Currently just this function must be called always BEFORE prepareConnectionObjects // everywhere except acceptAndRespond(). ok = applyResponseSettings(pResponse); // This will actually be done also in rendezvous HSv4, // however in this case the HSREQ extension will not be attached, // so it will simply go the "old way". // (&&: skip if failed already) // Must be called before interpretSrtHandshake() to create the CryptoControl. ok = ok && prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout); // May happen that 'response' contains a data packet that was sent in rendezvous mode. // In this situation the interpretation of handshake was already done earlier. ok = ok && pResponse->isControl(); ok = ok && interpretSrtHandshake(NULL, m_ConnRes, *pResponse, 0, 0); ok = ok && prepareBuffers(eout); if (!ok) { if (eout) { *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); } // m_RejectReason already set return CONN_REJECT; } } bool have_group = false; { #if SRT_ENABLE_BONDING SharedLock cl (uglobal().m_GlobControlLock); // Mutex-protection is likely not needed here because // the group will not be deleted without having the socket // present, and the socket closure, should it happen in another // thread, will have to wait for released m_ConnectionLock. CUDTGroup* g = m_parent->m_GroupOf; if (g) { // This is the last moment when this can be done. // The updateAfterSrtHandshake call will copy the receiver // start time to the receiver buffer data, so the correct // value must be set before this happens. synchronizeWithGroup(g); have_group = true; } #endif } if (!have_group) { // This function will be called internally inside // synchronizeWithGroup(). This is just more complicated. updateAfterSrtHandshake(m_ConnRes.m_iVersion); } CInfoBlock ib; ib.m_iIPversion = m_PeerAddr.family(); CInfoBlock::convert(m_PeerAddr, ib.m_piIP); if (m_pCache->lookup(&ib) >= 0) { m_iSRTT = ib.m_iSRTT; m_iRTTVar = ib.m_iSRTT / 2; m_iBandwidth = ib.m_iBandwidth; } #if SRT_DEBUG_RTT s_rtt_trace.trace(steady_clock::now(), "Connect", -1, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); #endif SRT_REJECT_REASON rr = setupCC(); if (rr != SRT_REJ_UNKNOWN) { m_RejectReason = rr; return CONN_REJECT; } // And, I am connected too. m_bConnecting = false; // The lock on m_ConnectionLock should still be applied, but // the socket could have been started removal before this function // has started. Do a sanity check before you continue with the // connection process. CUDTSocket* s = uglobal().locateSocket(m_SocketID); if (s) { // The socket could be closed at this very moment. // Continue with removing the socket from the pending structures, // but prevent it from setting it as connected. m_bConnected = true; HLOGC(cnlog.Debug, log << CONID() << "postConnect: setReceiver"); // register this socket for receiving data packets m_pMuxer->setReceiver(this); } // XXX Problem around CONN_CONFUSED! // If some too-eager packets were received from a listener // that thinks it's connected, but his last handshake was missed, // they are collected by CRcvQueue::storePktClone. The removeConnector // function will want to delete them all, so it would be nice // if these packets can be re-delivered. Of course the listener // should be prepared to resend them (as every packet can be lost // on UDP), but it's kinda overkill when we have them already and // can dispatch them. // Remove from rendezvous queue (in this particular case it's // actually removing the socket that undergoes asynchronous HS processing). // Removing at THIS point because since when setReceiver is called, // the next iteration in the CRcvQueue::worker loop will be dispatching // packets normally, as within-connection, so the "connector" won't // play any role since this time. // The connector, however, must stay alive until the setReceiver is called // because otherwise the packets that are coming for this socket before the // connection process is complete will be rejected as "attack", instead of // being enqueued for later pickup from the queue. m_pMuxer->removeConnector(m_SocketID); // Ok, no more things to be done as per "clear connecting state" if (!s) { LOGC(cnlog.Error, log << CONID() << "Connection broken in the process - socket closed"); m_RejectReason = SRT_REJ_CLOSE; if (eout) { *eout = CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } return CONN_REJECT; } // copy address information of local node // the local port must be correctly assigned BEFORE CUDT::startConnect(), // otherwise if startConnect() fails, the multiplexer cannot be located // by garbage collection and will cause leak s->m_SelfAddr = s->core().channel()->getSockAddr(); CIPAddress::decode(s->core().m_piSelfIP, m_PeerAddr, (s->m_SelfAddr)); //int token = -1; #if SRT_ENABLE_BONDING { SharedLock cl (uglobal().m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; if (g) { ScopedLock gl (*g->exp_groupLock()); // XXX this might require another check of group type. // For redundancy group, at least, update the status in the group. // LEAVING as comment for historical reasons. Locking is here most // likely not necessary because the socket cannot be removed from the // group until the socket isn't removed, and this requires locking of // m_GlobControlLock. This should ensure that when m_GroupOf is // not NULL, m_GroupMemberData is also valid. // ScopedLock glock(g->m_GroupLock); HLOGC(cnlog.Debug, log << "group: Socket @" << m_SocketID << " fresh connected, setting IDLE"); groups::SocketData* gi = m_parent->m_GroupMemberData; gi->sndstate = SRT_GST_IDLE; gi->rcvstate = SRT_GST_IDLE; gi->laststatus = SRTS_CONNECTED; //token = gi->token; g->setGroupConnected(); } } #endif s->m_Status = SRTS_CONNECTED; // acknowledde any waiting epolls to write // This must be done AFTER the group member status is upgraded to IDLE because // this state change will trigger the waiting function in blocking-mode groupConnect // and this may be immediately followed by exit from connect and start sending function, // which must see this very link already as IDLE, not PENDING, which will make this // link unable to be used and therefore the sending call would fail. uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_CONNECT, true); CGlobEvent::triggerEvent(); /* XXX Likely it should NOT be called here for two reasons: - likely lots of mutexes are locked here so any API call from here might cause a deadlock - if called from an asynchronous connection process, it was already called from inside updateConnStatus - if called from startConnect (synchronous mode), it is even wrong. if (m_cbConnectHook) { CALLBACK_CALL(m_cbConnectHook, m_SocketID, SRT_SUCCESS, m_PeerAddr.get(), token); } */ LOGC(cnlog.Note, log << CONID() << "Connection established from (" << m_SourceAddr.str() << ") to peer @" << m_PeerID << " (" << m_PeerAddr.str() << ")"); return CONN_ACCEPT; } void CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield) { int enc_flags = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(typefield); // potentially 0-7 values are possible. // When 0, don't change anything - it should rely on the value 0. // When 1, 5, 6, 7, this is kinda internal error - ignore. if (enc_flags >= 2 && enc_flags <= 4) // 2 = 128, 3 = 192, 4 = 256 { int rcv_pbkeylen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(enc_flags); if (m_config.iSndCryptoKeyLen == 0) { m_config.iSndCryptoKeyLen = rcv_pbkeylen; HLOGC(cnlog.Debug, log << CONID() << loghdr << ": PBKEYLEN adopted from advertised value: " << m_config.iSndCryptoKeyLen); } else if (m_config.iSndCryptoKeyLen != rcv_pbkeylen) { // Conflict. Use SRTO_SENDER flag to check if this side should accept // the enforcement, otherwise simply let it win. if (!m_config.bDataSender) { LOGC(cnlog.Warn, log << CONID() << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_config.iSndCryptoKeyLen << " by " << rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)"); m_config.iSndCryptoKeyLen = rcv_pbkeylen; } else { LOGC(cnlog.Warn, log << CONID() << loghdr << ": PBKEYLEN conflict - keep " << m_config.iSndCryptoKeyLen << "; peer-advertised PBKEYLEN " << rcv_pbkeylen << " rejected because Agent is SRTO_SENDER"); } } } else if (enc_flags != 0) { LOGC(cnlog.Error, log << CONID() << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags); } else { HLOGC(cnlog.Debug, log << CONID() << loghdr << ": No encryption flags found in type field: " << typefield); } } // Rendezvous void CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, int& w_tosend_ext_type) { UDTRequestType req = m_ConnRes.m_iReqType; int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); bool has_extension = !!hs_flags; // it holds flags, if no flags, there are no extensions. const HandshakeSide& hsd = m_SrtHsSide; // Note important possibilities that are considered here: // 1. The serial arrangement. This happens when one party has missed the // URQ_WAVEAHAND message, it sent its own URQ_WAVEAHAND message, and then the // firstmost message it received from the peer is URQ_CONCLUSION, as a response // for agent's URQ_WAVEAHAND. // // In this case, Agent switches to RDV_FINE state and Peer switches to RDV_ATTENTION state. // // 2. The parallel arrangement. This happens when the URQ_WAVEAHAND message sent // by both parties are almost in a perfect synch (a rare, but possible case). In this // case, both parties receive one another's URQ_WAVEAHAND message and both switch to // RDV_ATTENTION state. // // It's not possible to predict neither which arrangement will happen, or which // party will be RDV_FINE in case when the serial arrangement has happened. What // will actually happen will depend on random conditions. // // No matter this randomity, we have a limited number of possible conditions: // // Stating that "agent" is the party that has received the URQ_WAVEAHAND in whatever // arrangement, we are certain, that "agent" switched to RDV_ATTENTION, and peer: // // - switched to RDV_ATTENTION state (so, both are in the same state independently) // - switched to RDV_FINE state (so, the message interchange is actually more-less sequenced) // // In particular, there's no possibility of a situation that both are in RDV_FINE state // because the agent can switch to RDV_FINE state only if it received URQ_CONCLUSION from // the peer, while the peer could not send URQ_CONCLUSION without switching off RDV_WAVING // (actually to RDV_ATTENTION). There's also no exit to RDV_FINE from RDV_ATTENTION. // DEFAULT STATEMENT: don't attach extensions to URQ_CONCLUSION, neither HSREQ nor HSRSP. w_tosend_ext_type = 0; string reason; #if HVU_ENABLE_HEAVY_LOGGING HLOGC(cnlog.Debug, log << CONID() << "rendezvousSwitchState: HS: " << m_ConnRes.show()); struct LogAtTheEnd { CHandShake::RendezvousState ost; UDTRequestType orq; const CHandShake::RendezvousState& nst; const UDTRequestType& nrq; int& exttype; string& reason; ~LogAtTheEnd() { HLOGC(cnlog.Debug, log << "rendezvousSwitchState: STATE[" << CHandShake::RdvStateStr(ost) << "->" << CHandShake::RdvStateStr(nst) << "] REQTYPE[" << RequestTypeStr(orq) << "->" << RequestTypeStr(nrq) << "] " << "ext: " << s_hs_ext_side[exttype] << (reason == "" ? string() : " reason:" + reason)); } } l_logend = {m_RdvState, req, m_RdvState, w_rsptype, w_tosend_ext_type, reason}; #endif switch (m_RdvState) { case CHandShake::RDV_INVALID: return; case CHandShake::RDV_WAVING: { if (req == URQ_WAVEAHAND) { // Exception: send waveahand in response to force the other party // declare itself. NOTE: // - in 1.6.0 there is no possibility to resolve both sides with the same HSD // - such a possibility exists in an earlier version and this way this will be // detected by forcing it to send their conclusion first. if (hsd == HSD_RESPONDER) { w_rsptype = URQ_WAVEAHAND; w_tosend_ext_type = 0; return; } m_RdvState = CHandShake::RDV_ATTENTION; // NOTE: if this->isWinner(), attach HSREQ w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) w_tosend_ext_type = SRT_CMD_HSREQ; return; } if (req == URQ_CONCLUSION) { m_RdvState = CHandShake::RDV_FINE; w_rsptype = URQ_CONCLUSION; // (see below - this needs to craft either HSREQ or HSRSP) // if this->isWinner(), then craft HSREQ for that response. // if this->isLoser(), then this packet should bring HSREQ, so craft HSRSP for the response. w_tosend_ext_type = hsd == HSD_RESPONDER ? SRT_CMD_HSRSP : SRT_CMD_HSREQ; return; } } reason = "WAVING -> WAVEAHAND or CONCLUSION"; break; case CHandShake::RDV_ATTENTION: { if (req == URQ_WAVEAHAND) { // This is only possible if the URQ_CONCLUSION sent to the peer // was lost on track. The peer is then simply unaware that the // agent has switched to ATTENTION state and continues sending // waveahands. In this case, just remain in ATTENTION state and // retry with URQ_CONCLUSION, as normally. w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) w_tosend_ext_type = SRT_CMD_HSREQ; return; } if (req == URQ_CONCLUSION) { // We have two possibilities here: // // WINNER (HSD_INITIATOR): send URQ_AGREEMENT if (hsd == HSD_INITIATOR) { // WINNER should get a response with HSRSP, otherwise this is kinda empty conclusion. // If no HSRSP attached, stay in this state. if (hs_flags == 0) { HLOGC(cnlog.Debug, log << CONID() << "rendezvousSwitchState: {INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, " "got CONCLUSION, remain in [ATTENTION]"); w_rsptype = URQ_CONCLUSION; w_tosend_ext_type = SRT_CMD_HSREQ; return; } m_RdvState = CHandShake::RDV_CONNECTED; w_rsptype = URQ_AGREEMENT; return; } // LOSER (HSD_RESPONDER): send URQ_CONCLUSION and attach HSRSP extension, then expect URQ_AGREEMENT if (hsd == HSD_RESPONDER) { // If no HSREQ attached, stay in this state. // (Although this seems completely impossible). if (hs_flags == 0) { LOGC(cnlog.Warn, log << CONID() << "rendezvousSwitchState: (IPE!){RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, " "got CONCLUSION, remain in [ATTENTION]"); w_rsptype = URQ_CONCLUSION; w_tosend_ext_type = 0; // If you received WITHOUT extensions, respond WITHOUT extensions (wait // for the right message) return; } m_RdvState = CHandShake::RDV_INITIATED; w_rsptype = URQ_CONCLUSION; w_tosend_ext_type = SRT_CMD_HSRSP; return; } LOGC(cnlog.Error, log << CONID() << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state."); // Fallback for cookie draw m_RdvState = CHandShake::RDV_INVALID; w_rsptype = URQFailure(SRT_REJ_RDVCOOKIE); return; } if (req == URQ_AGREEMENT) { // This means that the peer has received our URQ_CONCLUSION, but // the agent missed the peer's URQ_CONCLUSION (received only initial // URQ_WAVEAHAND). if (hsd == HSD_INITIATOR) { // In this case the missed URQ_CONCLUSION was sent without extensions, // whereas the peer received our URQ_CONCLUSION with HSREQ, and therefore // it sent URQ_AGREEMENT already with HSRSP. This isn't a problem for // us, we can go on with it, especially that the peer is already switched // into CHandShake::RDV_CONNECTED state. m_RdvState = CHandShake::RDV_CONNECTED; // Both sides are connected, no need to send anything anymore. w_rsptype = URQ_DONE; return; } if (hsd == HSD_RESPONDER) { // In this case the missed URQ_CONCLUSION was sent with extensions, so // we have to request this once again. Send URQ_CONCLUSION in order to // inform the other party that we need the conclusion message once again. // The ATTENTION state should be maintained. w_rsptype = URQ_CONCLUSION; w_tosend_ext_type = SRT_CMD_HSRSP; return; } } } reason = "ATTENTION -> WAVEAHAND(conclusion), CONCLUSION(agreement/conclusion), AGREEMENT (done/conclusion)"; break; case CHandShake::RDV_FINE: { // In FINE state we can't receive URQ_WAVEAHAND because if the peer has already // sent URQ_CONCLUSION, it's already in CHandShake::RDV_ATTENTION, and in this state it can // only send URQ_CONCLUSION, whereas when it isn't in CHandShake::RDV_ATTENTION, it couldn't // have sent URQ_CONCLUSION, and if it didn't, the agent wouldn't be in CHandShake::RDV_FINE state. if (req == URQ_CONCLUSION) { // There's only one case when it should receive CONCLUSION in FINE state: // When it's the winner. If so, it should then contain HSREQ extension. // In case of loser, it shouldn't receive CONCLUSION at all - it should // receive AGREEMENT. // The winner case, received CONCLUSION + HSRSP - switch to CONNECTED and send AGREEMENT. // So, check first if HAS EXTENSION bool correct_switch = false; if (hsd == HSD_INITIATOR && !has_extension) { // Received REPEATED empty conclusion that has initially switched it into FINE state. // To exit FINE state we need the CONCLUSION message with HSRSP. HLOGC(cnlog.Debug, log << CONID() << "rendezvousSwitchState: {INITIATOR}[FINE] uglobal(), self->m_parent); #endif CUniqueSync recvdata_lcc (self->m_RecvLock, self->m_RecvDataCond); CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); self->m_bTsbPdNeedsWakeup = true; while (!self->m_bClosing) { steady_clock::time_point tsNextDelivery; // Next packet delivery time bool rxready = false; #if SRT_ENABLE_BONDING bool shall_update_group = false; #endif INCREMENT_THREAD_ITERATIONS(); enterCS(self->m_RcvBufferLock); const steady_clock::time_point tnow = steady_clock::now(); self->m_pRcvBuffer->updRcvAvgDataSize(tnow); const CRcvBuffer::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; #if HVU_ENABLE_HEAVY_LOGGING if (info.seqno == SRT_SEQNO_NONE) { HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: NO PACKETS"); } else { HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: %" << info.seqno << " T=" << FormatTime(tsNextDelivery) << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) << " ready=" << is_time_to_deliver << " ondrop=" << info.seq_gap); } #endif if (!self->m_bTLPktDrop) { rxready = !info.seq_gap && is_time_to_deliver; } else if (is_time_to_deliver) { rxready = true; if (info.seq_gap) { // XXX TSA: Requires lock on m_RcvBufferLock (locked already by enterCS) const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); #if SRT_ENABLE_BONDING shall_update_group = true; #endif #if HVU_ENABLE_LOGGING const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); #if HVU_ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" << iDropCnt << " packets) playable at " << FormatTime(info.tsbpd_time) << " delayed " << (timediff_us / 1000) << "." << fmt(timediff_us % 1000, fmtc().fixed().fillzero().width(3)) << " ms"); #endif string why; if (self->frequentLogAllowed(FREQLOGFA_RCV_DROPPED, tnow, (why))) { LOGC(brlog.Warn, log << self->CONID() << "RCV-DROPPED " << iDropCnt << " packet(s). Packet seqno %" << info.seqno << " delayed for " << (timediff_us / 1000) << "." << fmt(timediff_us % 1000, fmtc().fixed().fillzero().width(3)) << " ms " << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE else { LOGC(brlog.Warn, log << "SUPPRESSED: RCV-DROPPED LOG: " << why); } #endif #endif tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } } leaveCS(self->m_RcvBufferLock); if (rxready) { HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " << FormatDuration(steady_clock::now() - info.tsbpd_time) << ")"); /* * There are packets ready to be delivered * signal a waiting "recv" call if there is any data available */ if (self->m_config.bSynRecving) { recvdata_lcc.notify_one(); } /* * Set EPOLL_IN to wakeup any thread waiting on epoll */ self->uglobal().m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); #if SRT_ENABLE_BONDING // If this is NULL, it means: // - the socket never was a group member // - the socket was a group member, but: // - was just removed as a part of closure // - and will never be member of the group anymore // If this is not NULL, it means: // - This socket is currently member of the group // - This socket WAS a member of the group, though possibly removed from it already, BUT: // - the group that this socket IS OR WAS member of is in the GroupKeeper // - the GroupKeeper prevents the group from being deleted // - it is then completely safe to access the group here, // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. // It is ensured that the group object exists here because GroupKeeper // keeps it busy, even if you just closed the socket, remove it as a member // or even the group is empty and was explicitly closed. if (gkeeper.group) { // Functions called below will lock m_GroupLock, which in hierarchy // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock // m_GroupLock inside the calls. // XXX TSA will report this because it doesn't understand the // annotation applied to a constructor or destructor of CUniqueSync. InvertedLock unrecv(self->m_RecvLock); // The current "APP reader" needs to simply decide as to whether // the next CUDTGroup::recv() call should return with no blocking or not. // When the group is read-ready, it should update its pollers as it sees fit. // NOTE: this call will set lock to m_IncludedGroup->m_GroupLock HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: GROUP: checking if %" << info.seqno << " makes group readable"); gkeeper.group->updateReadState(self->m_SocketID, info.seqno); if (shall_update_group) { // A group may need to update the parallelly used idle links, // should it have any. Pass the current socket position in order // to skip it from the group loop. // NOTE: SELF LOCKING. gkeeper.group->updateLatestRcv(self->m_parent); } } // After re-acquisition of the m_RecvLock, re-check the closing flag if (self->m_bClosing) { break; } #endif CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } // We may just briefly unlocked the m_RecvLock, so we need to check m_bClosing again to avoid deadlock. if (self->m_bClosing) break; SRT_ATR_UNUSED bool bWokeUpOnSignal = true; if (!is_zero(tsNextDelivery)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); /* * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ self->m_bTsbPdNeedsWakeup = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno << " T=" << FormatTime(tsNextDelivery) << " - waiting " << FormatDuration(timediff)); THREAD_PAUSED(); bWokeUpOnSignal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP on " << (bWokeUpOnSignal? "SIGNAL" : "TIMEOUIT") << "!!!"); } else { /* * We have just signaled epoll; or * receive queue is empty; or * next buffer to deliver is not in receive queue (missing packet in sequence). * * Block until woken up by one of the following event: * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time * if any) * - New buffers ACKed * - Closing the connection */ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); self->m_bTsbPdNeedsWakeup = true; THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); } HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP [" << (bWokeUpOnSignal ? "signal" : "timeout") << "]!!! - " << "NOW=" << FormatTime(steady_clock::now())); } THREAD_EXIT(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); return NULL; } int CUDT::rcvDropTooLateUpTo(int seqno, DropReason reason) { // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. if (CSeqNo::seqcmp(seqno, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) seqno = CSeqNo::incseq(m_iRcvCurrSeqNo); dropFromLossLists(SRT_SEQNO_NONE, CSeqNo::decseq(seqno)); const std::pair iDropDiscardedPkts = m_pRcvBuffer->dropUpTo(seqno); const int iDropCnt = iDropDiscardedPkts.first; const int iDiscardedCnt = iDropDiscardedPkts.second; const int iDropCntTotal = iDropCnt + iDiscardedCnt; // In case of DROP_TOO_LATE discarded packets should also be counted because they are not read from another member socket. const int iDropStatCnt = (reason == DROP_DISCARD) ? iDropCnt : iDropCntTotal; if (iDropStatCnt > 0) { enterCS(m_StatsLock); // Estimate dropped bytes from average payload size. const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvr.dropped.count(stats::BytesPackets(iDropStatCnt * avgpayloadsz, (uint32_t)iDropStatCnt)); leaveCS(m_StatsLock); } return iDropCntTotal; } void CUDT::setInitialRcvSeq(int32_t isn) { m_iRcvLastAck = isn; #if HVU_ENABLE_LOGGING m_iDebugPrevLastAck = isn; #endif m_iRcvLastAckAck = isn; m_iRcvCurrSeqNo = CSeqNo::decseq(isn); sync::ScopedLock rb(m_RcvBufferLock); if (m_pRcvBuffer) { if (!m_pRcvBuffer->empty()) { LOGC(cnlog.Error, log << CONID() << "IPE: setInitialRcvSeq expected empty RCV buffer. Dropping all."); const int iDropCnt = m_pRcvBuffer->dropAll(); const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); sync::ScopedLock sl(m_StatsLock); m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); } m_pRcvBuffer->setStartSeqNo(isn); } } bool CUDT::prepareConnectionObjects(const CHandShake& hs SRT_ATR_UNUSED, HandshakeSide hsd, CUDTException *eout) { // This will be lazily created due to being the common // code with HSv5 rendezvous, in which this will be run // in a little bit "randomly selected" moment, but must // be run once in the whole connection process. if (m_CryptoControl.initialized()) { HLOGC(rslog.Debug, log << CONID() << "prepareConnectionObjects: (lazy) already created."); return true; } // HSD_DRAW is received only if this side is listener. // If this side is caller with HSv5, HSD_INITIATOR should be passed. // If this is a rendezvous connection with HSv5, the handshake role // is taken from m_SrtHsSide field. if (hsd == HSD_DRAW) { hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR. } if (!createCrypter(hsd)) // Make sure CC is created (lazy) { if (eout) *eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); m_RejectReason = SRT_REJ_RESOURCE; return false; } return true; } int CUDT::getAuthTagSize() const { if (m_CryptoControl.getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) return HAICRYPT_AUTHTAG_MAX; return 0; } bool CUDT::prepareBuffers(CUDTException* eout) { if (m_pSndBuffer) { HLOGC(rslog.Debug, log << CONID() << "prepareBuffers: (lazy) already created."); return true; } try { // XXX SND buffer may allocate more memory, but must set the size of a single // packet that fits the transmission for the overall connection. For any mixed 4-6 // connection it should be the less size, that is, for IPv6 // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. const int authtag = getAuthTagSize(); SRT_ASSERT(m_iMaxSRTPayloadSize != 0); SRT_ASSERT(m_TransferIPVersion != AF_UNSPEC); // IMPORTANT: // The m_iMaxSRTPayloadSize is the size of the payload in the "SRT packet" that can be sent // over the current connection - which means that if both parties are IPv6, then the maximum size // is the one for IPv6 (1444). If any party is IPv4, this maximum size is 1456. // The family as the first argument is something different - it's for the header size in order // to calculate rate and statistics. int snd_payload_size = m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(m_TransferIPVersion); SRT_ASSERT(m_iMaxSRTPayloadSize <= snd_payload_size); HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << snd_payload_size << " snd-bufsize=" << 32 << " TF-IPv" << (m_TransferIPVersion == AF_INET6 ? "6" : m_TransferIPVersion == AF_INET ? "4" : "???") << " authtag=" << authtag); m_pSndBuffer = new CSndBuffer(m_TransferIPVersion, 32, snd_payload_size, authtag); SRT_ASSERT(m_iPeerISN != -1); m_pRcvBuffer = new CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pMuxer->getBufferQueue(), m_config.bMessageAPI); // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space. m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) { // Simply reject. if (eout) *eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); m_RejectReason = SRT_REJ_RESOURCE; return false; } return true; } void CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) { // this is a response handshake w_hs.m_iReqType = URQ_CONCLUSION; w_hs.m_iMSS = m_config.iMSS; w_hs.m_iFlightFlagSize = m_config.flightCapacity(); w_hs.m_iID = m_SocketID; if (w_hs.m_iVersion > HS_VERSION_UDT4) { // The version is agreed; this code is executed only in case // when AGENT is listener. In this case, conclusion response // must always contain HSv5 handshake extensions. w_hs.m_extensionType = (m_SrtHsSide == HSD_INITIATOR) ? SRT_CMD_HSREQ : SRT_CMD_HSRSP; } CIPAddress::encode(peer, (w_hs.m_piPeerIP)); } void CUDT::acceptAndRespond(CUDTSocket* lsn, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: setting up data according to handshake"); const sockaddr_any& agent = lsn->m_SelfAddr; ScopedLock cg(m_ConnectionLock); m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS m_TransferIPVersion = peer.family(); if (peer.family() == AF_INET6) { // Check if the peer's address is a mapped IPv4. If so, // define Transfer IP version as 4 because this one will be used. if (checkMappedIPv4(peer.sin6)) m_TransferIPVersion = AF_INET; } // Uses the smaller MSS between the peers m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); const size_t full_hdr_size = CPacket::udpHeaderSize(m_TransferIPVersion) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; if (m_iMaxSRTPayloadSize < int(m_config.zExpPayloadSize)) { LOGC(cnlog.Error, log << CONID() << "acceptAndRespond: negotiated MSS=" << m_config.iMSS << " leaves too little payload space " << m_iMaxSRTPayloadSize << " for configured payload size " << m_config.zExpPayloadSize); m_RejectReason = SRT_REJ_CONFIG; throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); // exchange info for maximum flow window size m_iFlowWindowSize = w_hs.m_iFlightFlagSize; m_iPeerISN = w_hs.m_iISN; setInitialRcvSeq(m_iPeerISN); m_iRcvCurrPhySeqNo = CSeqNo::decseq(w_hs.m_iISN); m_PeerID = w_hs.m_iID; // use peer's ISN and send it back for security check m_iISN = w_hs.m_iISN; setInitialSndSeq(m_iISN); m_SndLastAck2Time = steady_clock::now(); // get local IP address and send the peer its IP address (because UDP cannot get local IP address) memcpy((m_piSelfIP), w_hs.m_piPeerIP, sizeof m_piSelfIP); m_parent->m_SelfAddr = agent; CIPAddress::decode(m_piSelfIP, peer, (m_parent->m_SelfAddr)); rewriteHandshakeData(peer, (w_hs)); m_TransferIPVersion = peer.family(); if (peer.family() == AF_INET6) { // Check if the peer's address is a mapped IPv4. If so, // define Transfer IP version as 4 because this one will be used. if (checkMappedIPv4(peer.sin6)) m_TransferIPVersion = AF_INET; } // Prepare all structures if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // // Respond with the rejection message and exit with exception // so that the caller will know that this new socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // Since now you can use m_pCryptoControl CInfoBlock ib; ib.m_iIPversion = peer.family(); CInfoBlock::convert(peer, ib.m_piIP); if (m_pCache->lookup(&ib) >= 0) { m_iSRTT = ib.m_iSRTT; m_iRTTVar = ib.m_iSRTT / 2; m_iBandwidth = ib.m_iBandwidth; } #if SRT_DEBUG_RTT s_rtt_trace.trace(steady_clock::now(), "Accept", -1, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); #endif m_PeerAddr = peer; // This should extract the HSREQ and KMREQ portion in the handshake packet. // This could still be a HSv4 packet and contain no such parts, which will leave // this entity as "non-SRT-handshaken", and await further HSREQ and KMREQ sent // as UMSG_EXT. uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; if (!interpretSrtHandshake(lsn, w_hs, hspkt, (kmdata), (&kmdatasize))) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // // Respond with the rejection message and return false from // this function so that the caller will know that this new // socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } #if SRT_ENABLE_BONDING m_ConnectionLock.unlock(); // The socket and the group are only linked to each other after interpretSrtHandshake(..) has been called. // Keep the group alive for the lifetime of this function, // and do it BEFORE acquiring m_ConnectionLock to avoid // lock inversion. // This will check if a socket belongs to a group and if so // it will remember this group and keep it alive here. CUDTUnited::GroupKeeper group_keeper(uglobal(), m_parent); m_ConnectionLock.lock(); #endif if (!prepareBuffers(NULL)) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); // If the SRT buffers failed to be allocated, // the connection must be rejected. // // Respond with the rejection message and exit with exception // so that the caller will know that this new socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // Synchronize the time NOW because the following function is about // to use the start time to pass it to the receiver buffer data. bool have_group = false; { #if SRT_ENABLE_BONDING CUDTGroup* g = group_keeper.group; if (g) { // This is the last moment when this can be done. // The updateAfterSrtHandshake call will copy the receiver // start time to the receiver buffer data, so the correct // value must be set before this happens. synchronizeWithGroup(g); have_group = true; } #endif } if (!have_group) { // This function will be called internally inside // synchronizeWithGroup(). This is just more complicated. updateAfterSrtHandshake(w_hs.m_iVersion); } SRT_REJECT_REASON rr = setupCC(); // UNKNOWN used as a "no error" value if (rr != SRT_REJ_UNKNOWN) { w_hs.m_iReqType = URQFailure(rr); m_RejectReason = rr; throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // And of course, it is connected. m_bConnected = true; HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: setReceiver"); // Save the handshake in m_ConnRes in case when needs repeating. m_ConnRes = w_hs; // Connection lock will be used with Muxer content locked when doing // checkTimers during connection. m_ConnectionLock.unlock(); // Register this socket for receiving data packets. m_pMuxer->setReceiver(this); m_ConnectionLock.lock(); // lock-back required because used here by ScopedLock // NOTE: UNBLOCK THIS instruction in order to cause the final // handshake to be missed and cause the problem solved in PR #417. // When missed this message, the caller should not accept packets // coming as connected, but continue repeated handshake until finally // received the listener's handshake. //return; if (!createSendHSResponse(kmdata, kmdatasize, hspkt.udpDestAddr(), (w_hs))) { throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } } bool CUDT::createSendHSResponse(uint32_t* kmdata, size_t kmdatasize, const CNetworkInterface& hsaddr, CHandShake& w_hs) ATR_NOTHROW { // Send the response to the peer, see listen() for more discussions // about this. // TODO: Here create CONCLUSION RESPONSE with: // - just the UDT handshake, if HS_VERSION_UDT4, // - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP. size_t size = m_iMaxSRTPayloadSize; // Allocate the maximum possible memory for an SRT payload. // This is a maximum you can send once. CPacket rsppkt; rsppkt.setControl(UMSG_HANDSHAKE); rsppkt.allocate(size); // This will serialize the handshake according to its current form. HLOGC(cnlog.Debug, log << CONID() << "createSendHSResponse: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (w_hs))) { LOGC(cnlog.Error, log << CONID() << "createSendHSResponse: error creating handshake response"); return false; } // We can safely assign it here stating that this has passed the cookie test. m_SourceAddr = hsaddr; #if HVU_ENABLE_HEAVY_LOGGING { // To make sure what REALLY is being sent, parse back the handshake // data that have been just written into the buffer. CHandShake debughs; debughs.load_from(rsppkt.m_pcData, rsppkt.getLength()); HLOGC(cnlog.Debug, log << CONID() << "createSendHSResponse: sending HS from agent @" << debughs.m_iID << " to peer @" << m_PeerID // <-- will be used by addressAndSend << "HS:" << debughs.show() << " sourceIP=" << m_SourceAddr.str()); } #endif // NOTE: BLOCK THIS instruction in order to cause the final // handshake to be missed and cause the problem solved in PR #417. // When missed this message, the caller should not accept packets // coming as connected, but continue repeated handshake until finally // received the listener's handshake. addressAndSend((rsppkt)); return true; } bool CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::string& w_why) { #ifndef SRT_LOG_SLOWDOWN_FREQ_MS #define SRT_LOG_SLOWDOWN_FREQ_MS 1000 #endif bool is_suppressed = IsSet(m_LogSlowDownExpired, BIT(logid)); const bool isnow = (m_tsLogSlowDown[logid].load() + milliseconds_from(SRT_LOG_SLOWDOWN_FREQ_MS)) <= tnow; if (isnow) { // Theoretically this should prevent other calls of this function to take // set their values simultaneously, but if it happened that the time is // also set, this section will not fire for the other log, if it didn't do // the check yet. m_LogSlowDownExpired.store(uint8_t(BIT(logid))); // Clear all other bits // Note: it may happen that two threads could intermix one another between // the check and setting up, but this will at worst case set the slightly // later time again. m_tsLogSlowDown[logid].store(tnow); is_suppressed = false; const int supr = m_aSuppressedMsg[logid]; if (supr > 0) w_why = fmtcat("++SUPPRESSED: ", supr); m_aSuppressedMsg[logid] = 0; } else { w_why = fmtcat("Too early - last one was ", FormatDuration(tnow - m_tsLogSlowDown[logid].load())); // Set YOUR OWN bit, atomically. m_LogSlowDownExpired |= uint8_t(BIT(logid)); ++m_aSuppressedMsg[logid]; } return !is_suppressed; } // This function is required to be called when a caller receives an INDUCTION // response from the listener and would like to create a CONCLUSION that includes // the SRT handshake extension. This extension requires that the crypter object // be created, but it's still too early for it to be completely configured. // This function then precreates the object so that the handshake extension can // be created, as this happens before the completion of the connection (and // therefore configuration of the crypter object), which can only take place upon // reception of CONCLUSION response from the listener. bool CUDT::createCrypter(HandshakeSide side) { // Lazy initialization if (m_CryptoControl.initialized()) return true; // Write back this value, when it was just determined. m_SrtHsSide = side; // XXX These below are a little bit controversial. // These data should probably be filled only upon // reception of the conclusion handshake - otherwise // they have outdated values. m_CryptoControl.setCryptoSecret(m_config.CryptoSecret); const bool useGcm153 = m_uPeerSrtVersion <= SrtVersion(1, 5, 3); HLOGC(rslog.Debug, log << CONID() << "createCrypter: setting RCV/SND KeyLen=" << m_config.iSndCryptoKeyLen); m_CryptoControl.setCryptoKeylen(m_config.iSndCryptoKeyLen); return m_CryptoControl.init(m_SocketID, side, m_config, useGcm153); } SRT_REJECT_REASON CUDT::setupCC() { // Prepare configuration object, // Create the CCC object and configure it. // UDT also sets back the congestion window: ??? // m_iCongestionWindow = m_pCC->m_dCWndSize; // XXX Not sure about that. May happen that AGENT wants // tsbpd mode, but PEER doesn't, even in bidirectional mode. // This way, the reception side should get precedence. // if (bidirectional || m_config.bDataSender || m_bTwoWayData) // m_bPeerTsbPd = m_bTSBPD; // SrtCongestion will retrieve whatever parameters it needs // from *this. bool res = m_CongCtl.select(m_config.sCongestion.str()); if (!res || !m_CongCtl.configure(this)) { return SRT_REJ_CONGESTION; } // Configure filter module if (!m_config.sPacketFilterConfig.empty()) { // This string, when nonempty, defines that the corrector shall be // configured. Otherwise it's left uninitialized. // At this point we state everything is checked and the appropriate // corrector type is already selected, so now create it. HLOGC(pflog.Debug, log << CONID() << "filter: Configuring: " << m_config.sPacketFilterConfig.c_str()); bool status = true; try { // The filter configurer is build the way that allows to quit immediately // exit by exception, but the exception is meant for the filter only. status = m_PacketFilter.configure(this, m_pMuxer->getBufferQueue(), m_config.sPacketFilterConfig.str()); } catch (CUDTException& ) { status = false; } if (!status) return SRT_REJ_FILTER; m_PktFilterRexmitLevel = m_PacketFilter.arqLevel(); } else { // When we have no filter, ARQ should work in ALWAYS mode. m_PktFilterRexmitLevel = SRT_ARQ_ALWAYS; } // Override the value of minimum NAK interval, per SrtCongestion's wish. // When default 0 value is returned, the current value set by CUDT // is preserved. const steady_clock::duration min_nak = microseconds_from(m_CongCtl->minNAKInterval()); if (min_nak != steady_clock::duration::zero()) m_tdMinNakInterval = min_nak; // Update timers const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime.store(currtime); m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); #ifdef SRT_ENABLE_RATE_MEASUREMENT // XXX NOTE: use IPv4 or IPv6 as applicable! HLOGC(bslog.Debug, log << CONID() << "RATE-MEASUREMENT: initializing time TS=" << FormatTime(currtime)); m_SndRegularMeasurement.init(currtime, CPacket::UDP_HDR_SIZE); m_SndRexmitMeasurement.init(currtime, CPacket::UDP_HDR_SIZE); #endif HLOGC(rslog.Debug, log << CONID() << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize.load() << " rcvrate=" << m_iDeliveryRate.load() << "p/s (" << m_iByteDeliveryRate.load() << "B/S)" << " rtt=" << m_iSRTT.load() << " bw=" << m_iBandwidth.load()); if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET))) { LOGC(rslog.Error, log << CONID() << "setupCC: IPE: resources not yet initialized!"); return SRT_REJ_IPE; } return SRT_REJ_UNKNOWN; } void CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timebase) { // Do a fast pre-check first - this simply declares that agent uses HSv5 // and the legacy SRT Handshake is not to be done. Second check is whether // agent is sender (=initiator in HSv4). if (!isOPT_TsbPd() || !m_config.bDataSender) return; if (m_iSndHsRetryCnt <= 0) { HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); return; } const steady_clock::time_point now = steady_clock::now(); if (!is_zero(timebase)) { // Then this should be done only if it's the right time, // the TSBPD mode is on, and when the counter is "still rolling". /* * SRT Handshake with peer: * If... * - we want TsbPd mode; and * - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and * - and did not get answer back from peer * - last sent handshake req should have been replied (RTT*1.5 elapsed); and * then (re-)send handshake request. */ if (timebase > now) // too early { HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times"); return; } } // If 0 timebase, it means that this is the initial sending with the very first // payload packet sent. Send only if this is still set to maximum+1 value. else if (m_iSndHsRetryCnt < SRT_MAX_HSRETRY + 1) { HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt << " times"); return; } HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response"); m_iSndHsRetryCnt--; m_tsSndHsLastTime = now; sendSrtMsg(SRT_CMD_HSREQ); } void CUDT::checkSndTimers() { if (m_SrtHsSide == HSD_INITIATOR) { HLOGC(cnlog.Debug, log << CONID() << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase"); // Legacy method for HSREQ, only if initiator. considerLegacySrtHandshake(m_tsSndHsLastTime + microseconds_from(m_iSRTT * 3 / 2)); } else { HLOGC(cnlog.Debug, log << CONID() << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)") << " - not considering legacy handshake"); } // Retransmit KM request after a timeout if there is no response (KM RSP). // Or send KM REQ in case of the HSv4. m_CryptoControl.sendKeysToPeer(this, avgRTT()); } void CUDT::checkSndKMRefresh() { // Do not apply the regenerated key to the to the receiver context. const bool bidir = false; m_CryptoControl.regenCryptoKm(this, bidir); } void CUDT::addressAndSend(CPacket& w_pkt) { w_pkt.set_id(m_PeerID); setPacketTS(w_pkt, steady_clock::now()); // NOTE: w_pkt isn't modified in this call, // just in CChannel::sendto it's modified in place // before sending for performance purposes, // and then modification is undone. Logically then // there's no modification here. SRT_ASSERT(channel()); channel()->sendto(m_PeerAddr, w_pkt, m_SourceAddr); } // [[using maybe_locked(m_GlobControlLock, if called from breakSocket_LOCKED, usually from GC)]] // [[using maybe_locked(m_parent->m_ControlLock, if called from srt_close())]] bool CUDT::closeEntity(int reason) ATR_NOEXCEPT { // NOTE: this function is called from within the garbage collector thread. if (!m_bOpened) { return false; } // IMPORTANT: // This function may block indefinitely, if called for a socket // that has m_bBroken == false or m_bConnected == true. // If it is intended to forcefully close the socket, make sure // that it's in response to a broken connection. HLOGC(smlog.Debug, log << CONID() << "closeEntity: closing socket"); if (m_config.Linger.l_onoff != 0) { const steady_clock::time_point entertime = steady_clock::now(); HLOGC(smlog.Debug, log << CONID() << "... (linger)"); while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) && (steady_clock::now() - entertime < seconds_from(m_config.Linger.l_linger))) { // linger has been checked by previous close() call and has expired if (m_tsLingerExpiration >= entertime) break; if (!m_config.bSynSending) { // if this socket enables asynchronous sending, return immediately and let GC to close it later if (is_zero(m_tsLingerExpiration)) m_tsLingerExpiration = entertime + seconds_from(m_config.Linger.l_linger); HLOGC(smlog.Debug, log << CONID() << "CUDT::closeEntity: linger-nonblocking, setting expire time T=" << FormatTime(m_tsLingerExpiration)); return false; } #ifndef _WIN32 timespec ts; ts.tv_sec = 0; ts.tv_nsec = 1000000; nanosleep(&ts, NULL); #else Sleep(1); #endif } } // Some calls of closeInternal pass UNKNOWN here, which means // that they don't want to change the code. It should have been // set already somewhere else, however. if (reason != SRT_CLS_UNKNOWN) { setAgentCloseReason(reason); } // remove this socket from the snd queue if (m_bConnected) m_pMuxer->removeSender(this); /* * update_events below useless * removing usock for EPolls right after (update_usocks) clears it (in other HAI patch). * * What is in EPoll shall be the responsibility of the application, if it want local close event, * it would remove the socket from the EPoll after close. */ uglobal().m_EPoll.wipe_usock(m_SocketID, m_sPollID); // XXX What's this, could any of the above actions make it !m_bOpened? if (!m_bOpened) { return true; } // Inform the threads handler to stop. m_bClosing = true; HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE (closing=true). Acquiring connection lock"); ScopedLock connectguard(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. releaseSynch(); HLOGC(smlog.Debug, log << CONID() << "CLOSING, removing from listener/connector"); if (m_bListening) { m_bListening = false; bool removed SRT_ATR_UNUSED = m_pMuxer->removeListener(this); // NOTE: removeListener removes this socket as listener in the multiplexer, // but DOES NOT remove the socket from the multiplerxer (YET). if (!removed) { LOGC(smlog.Error, log << CONID() << "CLOSING: IPE: listening=true but listener removal failed!"); } } else if (m_bConnecting) { m_pMuxer->removeConnector(m_SocketID); } if (m_bConnected) { if (!m_bShutdown) { HLOGC(smlog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer @" << m_PeerID); int32_t shdata[1] = { reason }; sendCtrl(UMSG_SHUTDOWN, NULL, shdata, sizeof shdata); } // Store current connection information. CInfoBlock ib; ib.m_iIPversion = m_PeerAddr.family(); CInfoBlock::convert(m_PeerAddr, ib.m_piIP); ib.m_iSRTT = m_iSRTT; ib.m_iBandwidth = m_iBandwidth; m_pCache->update(&ib); #if SRT_DEBUG_RTT s_rtt_trace.trace(steady_clock::now(), "Cache", -1, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, -1); #endif m_bConnected = false; } HLOGC(smlog.Debug, log << CONID() << "closeEntity: joining send/receive threads"); // waiting all send and recv calls to stop ScopedLock sendguard(m_SendLock); ScopedLock recvguard(m_RecvLock); // Locking m_RcvBufferLock to protect calling to m_CryptoControl.decrypt((packet)) // from the processData(...) function while resetting Crypto Control. ScopedLock rblock (m_RcvBufferLock); m_CryptoControl.close(); m_uPeerSrtVersion = SRT_VERSION_UNK; m_tsRcvPeerStartTime = steady_clock::time_point(); m_bOpened = false; m_bConnecting = false; m_bConnected = false; HLOGC(smlog.Debug, log << CONID() << "closeEntity: done."); return true; } bool CUDT::closeAtFork() ATR_NOEXCEPT { m_bShutdown = true; return closeEntity(SRT_CLS_CLEANUP); } int CUDT::receiveBuffer(char *data, int len) { if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (isOPT_TsbPd()) { LOGP(arlog.Error, "recv: This function is not intended to be used in Live mode with TSBPD."); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } UniqueLock recvguard(m_RecvLock); if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { if (m_bShutdown) { // For stream API, return 0 as a sign of EOF for transmission. // That's a bit controversial because theoretically the // UMSG_SHUTDOWN message may be lost as every UDP packet, although // another theory states that this will never happen because this // packet has a total size of 42 bytes and such packets are // declared as never dropped - but still, this is UDP so there's no // guarantee. // The most reliable way to inform the party that the transmission // has ended would be to send a single empty packet (that is, // a data packet that contains only an SRT header in the UDP // payload), which is a normal data packet that can undergo // normal sequence check and retransmission rules, so it's ensured // that this packet will be received. Receiving such a packet should // make this function return 0, potentially also without breaking // the connection and potentially also with losing no ability to // send some larger portion of data next time. HLOGC(arlog.Debug, log << CONID() << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } HLOGC(arlog.Debug, log << CONID() << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") << " SHUTDOWN. Reporting as BROKEN."); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } CSync rcond (m_RecvDataCond, recvguard); CSync tscond (m_RcvTsbPdCond, recvguard); if (!isRcvBufferReady()) { if (!m_config.bSynRecving) { throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } // Kick TsbPd thread to schedule the next wakeup (if running) if (m_config.iRcvTimeOut < 0) { THREAD_PAUSED(); while (stillConnected() && !isRcvBufferReady()) { // Do not block forever, check connection status each 1 sec. rcond.wait_for(seconds_from(1)); } THREAD_RESUMED(); } else { const steady_clock::time_point exptime = steady_clock::now() + milliseconds_from(m_config.iRcvTimeOut); THREAD_PAUSED(); while (stillConnected() && !isRcvBufferReady()) { if (!rcond.wait_until(exptime)) // NOT means "not received a signal" break; // timeout } THREAD_RESUMED(); } } // throw an exception if not connected if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { // See at the beginning if (!m_config.bMessageAPI && m_bShutdown) { HLOGC(arlog.Debug, log << CONID() << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } HLOGC(arlog.Debug, log << CONID() << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") << " SHUTDOWN. Reporting as BROKEN."); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } enterCS(m_RcvBufferLock); const int res = m_pRcvBuffer->readBuffer(data, len); leaveCS(m_RcvBufferLock); /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) { HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); tscond.notify_one_locked(recvguard); } else { HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); } if (!isRcvBufferReady()) { // read is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } if ((res <= 0) && (m_config.iRcvTimeOut >= 0)) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); return res; } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]]; // [[using locked(m_SendLock)]]; int CUDT::sndDropTooLate() { if (!m_bPeerTLPktDrop) return 0; if (!m_config.bMessageAPI) { LOGC(aslog.Error, log << CONID() << "The SRTO_TLPKTDROP flag can only be used with message API."); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } const time_point tnow = steady_clock::now(); const int buffdelay_ms = (int) count_milliseconds(m_pSndBuffer->getBufferingDelay(tnow)); // high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms) // Minimum value must accommodate an I-Frame (~8 x average frame size) // >>need picture rate or app to set min threshold // >>using 1 sec for worse case 1 frame using all bit budget. // picture rate would be useful in auto SRT setting for min latency // XXX Make SRT_TLPKTDROP_MINTHRESHOLD_MS option-configurable const int threshold_ms = (m_config.iSndDropDelay >= 0) ? std::max(m_iPeerTsbPdDelay_ms + m_config.iSndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) + (2 * COMM_SYN_INTERVAL_US / 1000) : 0; if (threshold_ms == 0 || buffdelay_ms <= threshold_ms) return 0; // protect packet retransmission ScopedLock rcvlck(m_RecvAckLock); int dbytes; int32_t first_msgno; const int dpkts = m_pSndBuffer->dropLateData((dbytes), (first_msgno), tnow - milliseconds_from(threshold_ms)); if (dpkts <= 0) return 0; m_iFlowWindowSize = m_iFlowWindowSize + dpkts; // If some packets were dropped update stats, socket state, loss list and the parent group if any. enterCS(m_StatsLock); m_stats.sndr.dropped.count(stats::BytesPackets((uint64_t) dbytes, (uint32_t) dpkts)); leaveCS(m_StatsLock); IF_HEAVY_LOGGING(const int32_t realack = m_iSndLastDataAck); const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); m_iSndLastAck = fakeack; m_iSndLastDataAck = fakeack; const int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); m_pSndLossList->removeUpTo(minlastack); /* If we dropped packets not yet sent, advance current position */ // THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1) if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0) { m_iSndCurrSeqNo = minlastack; } HLOGC(qslog.Debug, log << CONID() << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo.load() << ") n=" << dpkts << "pkt " << dbytes << "B, span=" << buffdelay_ms << " ms, FIRST #" << first_msgno); #if SRT_ENABLE_BONDING // This is done with a presumption that the group // exists and if this is not NULL, it means that this // function was called with locked m_GroupLock, as sendmsg2 // function was called from inside CUDTGroup::send, which // locks the whole function. // // XXX This is true only because all existing groups are managed // groups, that is, sockets cannot be added or removed from group // manually, nor can send/recv operation be done on a single socket // from the API call directly. This should be extra verified, if that // changes in the future. // if (m_parent->m_GroupOf) { // What's important is that the lock on GroupLock cannot be applied // here, both because it might be applied already, that is, according // to the condition defined at this function's header, it is applied // under this condition. Hence ackMessage can be defined as 100% locked. m_parent->m_GroupOf->ackMessage(first_msgno); } #endif return dpkts; } int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64_t srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; mctrl.msgttl = msttl; mctrl.inorder = inorder; mctrl.srctime = srctime; return this->sendmsg2(data, len, (mctrl)); } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] // GroupLock is applied when this function is called from inside CUDTGroup::send, // which is the only case when the m_parent->m_GroupOf is not NULL. int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) { // throw an exception if not connected if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (len <= 0) { LOGC(aslog.Error, log << CONID() << "INVALID: Data size for sending declared with length: " << len); return 0; } if (w_mctrl.msgno != -1) // most unlikely, unless you use balancing groups { if (w_mctrl.msgno < 1 || w_mctrl.msgno > MSGNO_SEQ_MAX) { LOGC(aslog.Error, log << CONID() << "INVALID forced msgno " << w_mctrl.msgno << ": can be -1 (trap) or <1..." << MSGNO_SEQ_MAX << ">"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } } int msttl = w_mctrl.msgttl; bool inorder = w_mctrl.inorder; // Sendmsg isn't restricted to the congctl type, however the congctl // may want to have something to say here. // NOTE: SrtCongestion is also allowed to throw CUDTException() by itself! { SrtCongestion::TransAPI api = SrtCongestion::STA_MESSAGE; CodeMinor mn = MN_INVALMSGAPI; if (!m_config.bMessageAPI) { api = SrtCongestion::STA_BUFFER; mn = MN_INVALBUFFERAPI; } if (!m_CongCtl->checkTransArgs(api, SrtCongestion::STAD_SEND, data, len, msttl, inorder)) throw CUDTException(MJ_NOTSUP, mn, 0); } // NOTE: the length restrictions differ in STREAM API and in MESSAGE API: // - STREAM API: // At least 1 byte free sending buffer space is needed // (in practice, one unit buffer of 1456 bytes). // This function will send as much as possible, and return // how much was actually sent. // - MESSAGE API: // At least so many bytes free in the sending buffer is needed, // as the length of the data, otherwise this function will block // or return MJ_AGAIN until this condition is satisfied. The EXACTLY // such number of data will be then written out, and this function // will effectively return either -1 (error) or the value of 'len'. // This call will be also rejected from upside when trying to send // out a message of a length that exceeds the total size of the sending // buffer (configurable by SRTO_SNDBUF). if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxSRTPayloadSize)) { LOGC(aslog.Error, log << CONID() << "Message length (" << len << ") exceeds the size of sending buffer: " << (m_config.iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } /* XXX This might be worth preserving for several occasions, but it must be at least conditional because it breaks backward compat. if (!m_pCryptoControl || !m_CryptoControl.isSndEncryptionOK()) { LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } */ UniqueLock sendguard(m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) { // delay the EXP timer to avoid mis-fired timeout ScopedLock ack_lock(m_RecvAckLock); m_tsLastRspAckTime = steady_clock::now(); m_iReXmitCount = 1; } // sndDropTooLate(...) may lock m_RecvAckLock // to modify m_pSndBuffer and m_pSndLossList const int iPktsTLDropped SRT_ATR_UNUSED = sndDropTooLate(); // For MESSAGE API the minimum outgoing buffer space required is // the size that can carry over the whole message as passed here. // Otherwise it is allowed to send less bytes. const int iNumPktsRequired = m_config.bMessageAPI ? m_pSndBuffer->countNumPacketsRequired(len) : 1; if (m_bTsbPd && iNumPktsRequired > 1) { LOGC(aslog.Error, log << CONID() << "Message length (" << len << ") can't fit into a single data packet (" << m_pSndBuffer->getMaxPacketLen() << " bytes max)."); throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } if (sndBuffersLeft() < iNumPktsRequired) { //>>We should not get here if SRT_ENABLE_TLPKTDROP // XXX Check if this needs to be removed, or put to an 'else' condition for m_bTLPktDrop. if (!m_config.bSynSending) throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); { // wait here during a blocking sending CUniqueSync sendblock_cc (m_SendBlockLock, m_SendBlockCond); if (m_config.iSndTimeOut < 0) { while (stillConnected() && sndBuffersLeft() < iNumPktsRequired && m_bPeerHealth) sendblock_cc.wait(); } else { const steady_clock::time_point exptime = steady_clock::now() + milliseconds_from(m_config.iSndTimeOut); THREAD_PAUSED(); while (stillConnected() && sndBuffersLeft() < iNumPktsRequired && m_bPeerHealth) { if (!sendblock_cc.wait_until(exptime)) break; } THREAD_RESUMED(); } } // check the connection status if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if (!m_bPeerHealth) { m_bPeerHealth = true; throw CUDTException(MJ_PEERERROR); } /* * The code below is to return ETIMEOUT when blocking mode could not get free buffer in time. * If no free buffer available in non-blocking mode, we already returned. If buffer available, * we test twice if this code is outside the else section. * This fix move it in the else (blocking-mode) section */ if (sndBuffersLeft() < iNumPktsRequired) { if (m_config.iSndTimeOut >= 0) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); // XXX This looks very weird here, however most likely // this will happen only in the following case, when // the above loop has been interrupted, which happens when: // 1. The buffers left gets enough for minlen - but this is excluded // in the first condition here. // 2. In the case of sending timeout, the above loop was interrupted // due to reaching timeout, but this is excluded by the second // condition here // 3. The 'stillConnected()' or m_bPeerHealth condition is false, of which: // - broken/closing status is checked and responded with CONNECTION/CONNLOST // - not connected status is checked and responded with CONNECTION/NOCONN // - m_bPeerHealth condition is checked and responded with PEERERROR // // ERGO: never happens? LOGC(aslog.Fatal, log << CONID() << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. " "Impossible."); return 0; } } // If the sender's buffer is empty, // record total time used for sending if (m_pSndBuffer->getCurrBufSize() == 0) { ScopedLock lock(m_StatsLock); m_stats.sndDurationCounter = steady_clock::now(); } int size = len; if (!m_config.bMessageAPI) { // For STREAM API it's allowed to send less bytes than the given buffer. // Just return how many bytes were actually scheduled for writing. // XXX May be reasonable to add a flag that requires that the function // not return until the buffer is sent completely. size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize); } { ScopedLock recvAckLock(m_RecvAckLock); // insert the user buffer into the sending list int32_t seqno = m_iSndNextSeqNo; IF_HEAVY_LOGGING(int32_t orig_seqno = seqno); IF_HEAVY_LOGGING(steady_clock::time_point ts_srctime = steady_clock::time_point() + microseconds_from(w_mctrl.srctime)); #if SRT_ENABLE_BONDING // Check if seqno has been set, in case when this is a group sender. // If the sequence is from the past towards the "next sequence", // simply return the size, pretending that it has been sent. // NOTE: it's assumed that if this is a group member, then // an attempt to call srt_sendmsg2 has been rejected, and so // the pktseq field has been set by the internal group sender function. if (m_parent->m_GroupOf && w_mctrl.pktseq != SRT_SEQNO_NONE && m_iSndNextSeqNo != SRT_SEQNO_NONE) { if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0) { HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq << " OLDER THAN next expected %" << seqno << " - FAKE-SENDING."); return size; } } #endif // Set this predicted next sequence to the control information. // It's the sequence of the FIRST (!) packet from all packets used to send // this buffer. Values from this field will be monotonic only if you always // have one packet per buffer (as it's in live mode). w_mctrl.pktseq = seqno; // Now seqno is the sequence to which it was scheduled // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurate. HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:" << (w_mctrl.srctime ? FormatTime(ts_srctime) : "none") << " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno << " STAMP: " << BufferStamp(data, size)); time_point start_time = m_stats.tsStartTime; if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(start_time.time_since_epoch())) { LOGC(aslog.Error, log << CONID() << "Wrong source time was provided. Sending is rejected."); throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); } if (w_mctrl.srctime && (!m_config.bMessageAPI || !m_bTsbPd)) { HLOGC( aslog.Warn, log << CONID() << "Source time can only be used with TSBPD and Message API enabled. Using default time instead."); w_mctrl.srctime = 0; } // w_mctrl.seqno is INPUT-OUTPUT value: // - INPUT: the current sequence number to be placed for the next scheduled packet // - OUTPUT: value of the sequence number to be put on the first packet at the next sendmsg2 call. // We need to supply to the output the value that was STAMPED ON THE PACKET, // which is seqno. In the output we'll get the next sequence number. m_pSndBuffer->addBuffer(data, size, (w_mctrl)); m_iSndNextSeqNo = w_mctrl.pktseq; w_mctrl.pktseq = seqno; HLOGC(aslog.Debug, log << CONID() << "buf:SENDING srctime:" << FormatTime(ts_srctime) << " size=" << size << " #" << w_mctrl.msgno << " SCHED %" << orig_seqno << "(>> %" << seqno << ") !" << BufferStamp(data, size)); if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as required. { // write is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); } } // Insert this socket to the snd list if it is not on the list already. // m_pMuxer->sndUList()->pop may lock CSndUList::m_ListLock and then m_RecvAckLock m_pMuxer->updateSendNormal(m_parent); #ifdef SRT_ENABLE_ECN // IF there was a packet drop on the sender side, report congestion to the app. if (iPktsTLDropped > 0) { LOGC(aslog.Error, log << CONID() << "sendmsg2: CONGESTION; reporting error"); throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0); } #endif /* SRT_ENABLE_ECN */ HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (END): success, size=" << size); return size; } int CUDT::recv(char* data, int len) { SRT_MSGCTRL mctrl = srt_msgctrl_default; return recvmsg2(data, len, (mctrl)); } int CUDT::recvmsg(char* data, int len, int64_t& srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int res = recvmsg2(data, len, (mctrl)); srctime = mctrl.srctime; return res; } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] // GroupLock is applied when this function is called from inside CUDTGroup::recv, // which is the only case when the m_parent->m_GroupOf is not NULL. int CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl) { // Check if the socket is a member of a receiver group. // If so, then reading by receiveMessage is disallowed. #if SRT_ENABLE_BONDING if (m_parent->m_GroupOf && m_parent->m_GroupOf->isGroupReceiver()) { LOGP(arlog.Error, "recv*: This socket is a receiver group member. Use group ID, NOT socket ID."); throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); } #endif if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (len <= 0) { LOGC(arlog.Error, log << CONID() << "Length of '" << len << "' supplied to srt_recvmsg."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } if (m_config.bMessageAPI) return receiveMessage(data, len, (w_mctrl)); return receiveBuffer(data, len); } // [[using locked(m_RcvBufferLock)]] size_t CUDT::getAvailRcvBufferSizeNoLock() const { return m_pRcvBuffer->getAvailSize(m_iRcvLastAck); } bool CUDT::isRcvBufferReady() const { ScopedLock lck(m_RcvBufferLock); return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } bool CUDT::isRcvBufferReadyNoLock() const { return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } bool CUDT::isRcvBufferFull() const { ScopedLock lck(m_RcvBufferLock); return m_pRcvBuffer->full(); } // int by_exception: accepts values of CUDTUnited::ErrorHandling: // - 0 - by return value // - 1 - by exception // - 2 - by abort (unused) int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_exception) { // Recvmsg isn't restricted to the congctl type, it's the most // basic method of passing the data. You can retrieve data as // they come in, however you need to match the size of the buffer. // Note: if by_exception = ERH_RETURN, this would still break it // by exception. The intention of by_exception isn't to prevent // exceptions here, but to intercept the erroneous situation should // it be handled by the caller in a less than general way. As this // is only used internally, we state that the problem that would be // handled by exception here should not happen, and in case if it does, // it's a bug to fix, so the exception is nothing wrong. if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); UniqueLock recvguard (m_RecvLock); CSync tscond (m_RcvTsbPdCond, recvguard); /* XXX DEBUG STUFF - enable when required char charbool[2] = {'0', '1'}; char ptrn [] = "RECVMSG/BEGIN BROKEN 1 CONN 1 CLOSING 1 SYNCR 1 NMSG "; int pos [] = {21, 28, 38, 46, 53}; ptrn[pos[0]] = charbool[m_bBroken]; ptrn[pos[1]] = charbool[m_bConnected]; ptrn[pos[2]] = charbool[m_bClosing]; ptrn[pos[3]] = charbool[m_config.m_bSynRecving]; int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum()); strcpy(ptrn + pos[4] + wrtlen, "\n"); fputs(ptrn, stderr); // */ if (m_bBroken || m_bClosing) { HLOGC(arlog.Debug, log << CONID() << "receiveMessage: CONNECTION BROKEN - reading from recv buffer just for formality"); enterCS(m_RcvBufferLock); const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) : 0; leaveCS(m_RcvBufferLock); // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); tscond.notify_one_locked(recvguard); } else { HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); } if (!isRcvBufferReady()) { // read is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } if (res == 0) { if (!m_config.bMessageAPI && m_bShutdown) return 0; // Forced to return error instead of throwing exception. if (!by_exception) return APIError(MJ_CONNECTION, MN_CONNLOST, 0), int(SRT_ERROR); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else return res; } if (!m_config.bSynRecving) { HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN ASYNC MODE. Going to extract payload size=" << len); enterCS(m_RcvBufferLock); const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) : 0; leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); if (res == 0) { // read is not available any more // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(arlog.Debug, "receiveMessage: nothing to read, kicking TSBPD, return AGAIN"); tscond.notify_one_locked(recvguard); } else { HLOGP(arlog.Debug, "receiveMessage: nothing to read, return AGAIN"); } // Shut up EPoll if no more messages in non-blocking mode uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); // Forced to return 0 instead of throwing exception, in case of AGAIN/READ if (!by_exception) return 0; throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } if (!isRcvBufferReady()) { // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more - kicking TSBPD."); tscond.notify_one_locked(recvguard); } else { HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more"); } // Shut up EPoll if no more messages in non-blocking mode uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); // After signaling the tsbpd for ready data, report the bandwidth. #if HVU_ENABLE_HEAVY_LOGGING double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxSRTPayloadSize ); HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth.load() << " buffers per second)"); #endif } return res; } HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN SYNC MODE. Going to extract payload size max=" << len); int res = 0; bool timeout = false; // Do not block forever, check connection status each 1 sec. const steady_clock::duration recv_timeout = m_config.iRcvTimeOut < 0 ? seconds_from(1) : milliseconds_from(m_config.iRcvTimeOut); CSync recv_cond (m_RecvDataCond, recvguard); do { if (stillConnected() && !timeout && !isRcvBufferReady()) { /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) { // XXX Experimental, so just inform: // Check if the last check of isRcvDataReady has returned any "next time for a packet". // If so, then it means that TSBPD has fallen asleep only up to this time, so waking it up // would be "spurious". If a new packet comes ahead of the packet which's time is returned // in tstime (as TSBPD sleeps up to then), the procedure that receives it is responsible // of kicking TSBPD. // bool spurious = (tstime != 0); HLOGC(tslog.Debug, log << CONID() << "receiveMessage: KICK tsbpd"); tscond.notify_one_locked(recvguard); } THREAD_PAUSED(); do { // `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be // only until the time that is now + timeout since the first moment // when this started, or sliced-waiting for 1 second, if timeout is // higher than this. const steady_clock::time_point exptime = steady_clock::now() + recv_timeout; HLOGC(tslog.Debug, log << CONID() << "receiveMessage: fall asleep up to TS=" << FormatTime(exptime) << " lock=" << (&m_RecvLock) << " cond=" << (&m_RecvDataCond)); if (!recv_cond.wait_until(exptime)) { if (m_config.iRcvTimeOut >= 0) // otherwise it's "no timeout set" timeout = true; HLOGP(tslog.Debug, "receiveMessage: DATA COND: EXPIRED -- checking connection conditions and rolling again"); } else { HLOGP(tslog.Debug, "receiveMessage: DATA COND: KICKED."); } } while (stillConnected() && !timeout && (!isRcvBufferReady())); THREAD_RESUMED(); HLOGC(tslog.Debug, log << CONID() << "receiveMessage: lock-waiting loop exited: stillConntected=" << stillConnected() << " timeout=" << timeout << " data-ready=" << isRcvBufferReady()); } /* XXX DEBUG STUFF - enable when required LOGC(arlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected << " CLOSING " << m_bClosing << " TMOUT " << timeout << " NMSG " << m_pRcvBuffer->getRcvMsgNum()); */ enterCS(m_RcvBufferLock); res = m_pRcvBuffer->readMessage((data), len, &w_mctrl); leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); if (m_bBroken || m_bClosing) { // Forced to return 0 instead of throwing exception. if (!by_exception) return APIError(MJ_CONNECTION, MN_CONNLOST, 0).as(); if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else if (!m_bConnected) { // Forced to return -1 instead of throwing exception. if (!by_exception) return APIError(MJ_CONNECTION, MN_NOCONN, 0).as(); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } } while ((res == 0) && !timeout); if (!isRcvBufferReady()) { // Falling here means usually that res == 0 && timeout == true. // res == 0 would repeat the above loop, unless there was also a timeout. // timeout has interrupted the above loop, but with res > 0 this condition // wouldn't be satisfied. // read is not available any more // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)"); tscond.notify_one_locked(recvguard); } // Shut up EPoll if no more messages in non-blocking mode uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } // Unblock when required // LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT"); if ((res <= 0) && (m_config.iRcvTimeOut >= 0)) { // Forced to return -1 instead of throwing exception. if (!by_exception) return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0).as(); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } return res; } int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (size <= 0 && size != -1) return 0; if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_SEND, 0, size, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (!m_CryptoControl.isSndEncryptionOK()) { LOGC(aslog.Error, log << CONID() << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } ScopedLock sendguard (m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) { // delay the EXP timer to avoid mis-fired timeout ScopedLock ack_lock(m_RecvAckLock); m_tsLastRspAckTime = steady_clock::now(); m_iReXmitCount = 1; } // positioning... try { if (size == -1) { ifs.seekg(0, std::ios::end); size = ifs.tellg(); if (offset > size) throw 0; // let it be caught below } // This will also set the position back to the beginning // in case when it was moved to the end for measuring the size. // This will also fail if the offset exceeds size, so measuring // the size can be skipped if not needed. ifs.seekg((streamoff)offset); if (!ifs.good()) throw 0; } catch (...) { // XXX It would be nice to note that this is reported // by exception only if explicitly requested by setting // the exception flags in the stream. Here it's fixed so // that when this isn't set, the exception is "thrown manually". throw CUDTException(MJ_FILESYSTEM, MN_SEEKGFAIL); } int64_t tosend = size; int unitsize; // sending block by block while (tosend > 0) { if (ifs.fail()) throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); if (ifs.eof()) break; unitsize = int((tosend >= block) ? block : tosend); { UniqueLock lock(m_SendBlockLock); THREAD_PAUSED(); while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth) m_SendBlockCond.wait(lock); THREAD_RESUMED(); } if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); else if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if (!m_bPeerHealth) { // reset peer health status, once this error returns, the app should handle the situation at the peer side m_bPeerHealth = true; throw CUDTException(MJ_PEERERROR); } // record total time used for sending if (m_pSndBuffer->getCurrBufSize() == 0) { ScopedLock lock(m_StatsLock); m_stats.sndDurationCounter = steady_clock::now(); } { ScopedLock recvAckLock(m_RecvAckLock); const int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize); if (sentsize > 0) { tosend -= sentsize; offset += sentsize; } if (sndBuffersLeft() <= 0) { // write is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); } } // insert this socket to snd list if it is not on the list yet m_pMuxer->updateSendNormal(m_parent); } return size - tosend; } int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) { if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } if (size <= 0) return 0; if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_RECV, 0, size, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (isOPT_TsbPd()) { LOGC(arlog.Error, log << CONID() << "Reading from file is incompatible with TSBPD mode and would cause a deadlock"); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } UniqueLock recvguard(m_RecvLock); // Well, actually as this works over a FILE (fstream), not just a stream, // the size can be measured anyway and predicted if setting the offset might // have a chance to work or not. // positioning... try { if (offset > 0) { // Don't do anything around here if the offset == 0, as this // is the default offset after opening. Whether this operation // is performed correctly, it highly depends on how the file // has been open. For example, if you want to overwrite parts // of an existing file, the file must exist, and the ios::trunc // flag must not be set. If the file is open for only ios::out, // then the file will be truncated since the offset position on // at the time when first written; if ios::in|ios::out, then // it won't be truncated, just overwritten. // What is required here is that if offset is 0, don't try to // change the offset because this might be impossible with // the current flag set anyway. // Also check the status and CAUSE exception manually because // you don't know, as well, whether the user has set exception // flags. ofs.seekp((streamoff)offset); if (!ofs.good()) throw 0; // just to get caught :) } } catch (...) { // XXX It would be nice to note that this is reported // by exception only if explicitly requested by setting // the exception flags in the stream. For a case, when it's not, // an additional explicit throwing happens when failbit is set. throw CUDTException(MJ_FILESYSTEM, MN_SEEKPFAIL); } int64_t torecv = size; int unitsize = block; int recvsize; // receiving... "recvfile" is always blocking while (torecv > 0) { if (ofs.fail()) { // send the sender a signal so it will not be blocked forever int32_t err_code = CUDTException::EFILE; sendCtrl(UMSG_PEERERROR, &err_code); throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); } { CSync rcond (m_RecvDataCond, recvguard); THREAD_PAUSED(); while (stillConnected() && !isRcvBufferReady()) rcond.wait(); THREAD_RESUMED(); } if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); else if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } unitsize = int((torecv > block) ? block : torecv); enterCS(m_RcvBufferLock); recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize); leaveCS(m_RcvBufferLock); if (recvsize > 0) { torecv -= recvsize; offset += recvsize; } } if (!isRcvBufferReady()) { // read is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } return size - torecv; } void CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) { if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); const int pktHdrSize = CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion == AF_UNSPEC ? AF_INET : m_TransferIPVersion); { m_RecvAckLock.lock(); int32_t flight_span = getFlightSpan(); m_RecvAckLock.unlock(); ScopedLock statsguard(m_StatsLock); const time_point currtime = steady_clock::now(), start_time = m_stats.tsStartTime; perf->msTimeStamp = count_milliseconds(currtime - start_time); perf->pktSent = m_stats.sndr.sent.trace.count(); perf->pktSentUnique = m_stats.sndr.sentUnique.trace.count(); perf->pktRecv = m_stats.rcvr.recvd.trace.count(); perf->pktRecvUnique = m_stats.rcvr.recvdUnique.trace.count(); perf->pktSndLoss = m_stats.sndr.lost.trace.count(); perf->pktRcvLoss = m_stats.rcvr.lost.trace.count(); perf->pktRetrans = m_stats.sndr.sentRetrans.trace.count(); perf->pktRcvRetrans = m_stats.rcvr.recvdRetrans.trace.count(); perf->pktSentACK = m_stats.rcvr.sentAck.trace.count(); perf->pktRecvACK = m_stats.sndr.recvdAck.trace.count(); perf->pktSentNAK = m_stats.rcvr.sentNak.trace.count(); perf->pktRecvNAK = m_stats.sndr.recvdNak.trace.count(); perf->usSndDuration = m_stats.sndDuration; perf->pktReorderDistance = m_stats.traceReorderDistance; perf->pktReorderTolerance = m_iReorderTolerance; perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime; perf->pktRcvBelated = m_stats.rcvr.recvdBelated.trace.count(); perf->pktSndFilterExtra = m_stats.sndr.sentFilterExtra.trace.count(); perf->pktRcvFilterExtra = m_stats.rcvr.recvdFilterExtra.trace.count(); perf->pktRcvFilterSupply = m_stats.rcvr.suppliedByFilter.trace.count(); perf->pktRcvFilterLoss = m_stats.rcvr.lossFilter.trace.count(); /* perf byte counters include all headers (SRT+UDP+IP) */ perf->byteSent = m_stats.sndr.sent.trace.bytesWithHdr(pktHdrSize); perf->byteSentUnique = m_stats.sndr.sentUnique.trace.bytesWithHdr(pktHdrSize); perf->byteRecv = m_stats.rcvr.recvd.trace.bytesWithHdr(pktHdrSize); perf->byteRecvUnique = m_stats.rcvr.recvdUnique.trace.bytesWithHdr(pktHdrSize); perf->byteRetrans = m_stats.sndr.sentRetrans.trace.bytesWithHdr(pktHdrSize); perf->byteRcvLoss = m_stats.rcvr.lost.trace.bytesWithHdr(pktHdrSize); perf->pktSndDrop = m_stats.sndr.dropped.trace.count(); perf->pktRcvDrop = m_stats.rcvr.dropped.trace.count(); perf->byteSndDrop = m_stats.sndr.dropped.trace.bytesWithHdr(pktHdrSize); perf->byteRcvDrop = m_stats.rcvr.dropped.trace.bytesWithHdr(pktHdrSize); perf->pktRcvUndecrypt = m_stats.rcvr.undecrypted.trace.count(); perf->byteRcvUndecrypt = m_stats.rcvr.undecrypted.trace.bytes(); perf->pktSentTotal = m_stats.sndr.sent.total.count(); perf->pktSentUniqueTotal = m_stats.sndr.sentUnique.total.count(); perf->pktRecvTotal = m_stats.rcvr.recvd.total.count(); perf->pktRecvUniqueTotal = m_stats.rcvr.recvdUnique.total.count(); perf->pktSndLossTotal = m_stats.sndr.lost.total.count(); perf->pktRcvLossTotal = m_stats.rcvr.lost.total.count(); perf->pktRetransTotal = m_stats.sndr.sentRetrans.total.count(); perf->pktSentACKTotal = m_stats.rcvr.sentAck.total.count(); perf->pktRecvACKTotal = m_stats.sndr.recvdAck.total.count(); perf->pktSentNAKTotal = m_stats.rcvr.sentNak.total.count(); perf->pktRecvNAKTotal = m_stats.sndr.recvdNak.total.count(); perf->usSndDurationTotal = m_stats.m_sndDurationTotal; perf->byteSentTotal = m_stats.sndr.sent.total.bytesWithHdr(pktHdrSize); perf->byteSentUniqueTotal = m_stats.sndr.sentUnique.total.bytesWithHdr(pktHdrSize); perf->byteRecvTotal = m_stats.rcvr.recvd.total.bytesWithHdr(pktHdrSize); perf->byteRecvUniqueTotal = m_stats.rcvr.recvdUnique.total.bytesWithHdr(pktHdrSize); perf->byteRetransTotal = m_stats.sndr.sentRetrans.total.bytesWithHdr(pktHdrSize); perf->pktSndFilterExtraTotal = m_stats.sndr.sentFilterExtra.total.count(); perf->pktRcvFilterExtraTotal = m_stats.rcvr.recvdFilterExtra.total.count(); perf->pktRcvFilterSupplyTotal = m_stats.rcvr.suppliedByFilter.total.count(); perf->pktRcvFilterLossTotal = m_stats.rcvr.lossFilter.total.count(); perf->byteRcvLossTotal = m_stats.rcvr.lost.total.bytesWithHdr(pktHdrSize); perf->pktSndDropTotal = m_stats.sndr.dropped.total.count(); perf->pktRcvDropTotal = m_stats.rcvr.dropped.total.count(); // TODO: The payload is dropped. Probably header sizes should not be counted? perf->byteSndDropTotal = m_stats.sndr.dropped.total.bytesWithHdr(pktHdrSize); perf->byteRcvDropTotal = m_stats.rcvr.dropped.total.bytesWithHdr(pktHdrSize); perf->pktRcvUndecryptTotal = m_stats.rcvr.undecrypted.total.count(); perf->byteRcvUndecryptTotal = m_stats.rcvr.undecrypted.total.bytes(); // TODO: The following class members must be protected with a different mutex, not the m_StatsLock. const double interval = (double) count_microseconds(currtime - m_stats.tsLastSampleTime); perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; perf->usPktSndPeriod = (double) count_microseconds(m_tdSendInterval.load()); perf->pktFlowWindow = m_iFlowWindowSize.load(); perf->pktCongestionWindow = m_iCongestionWindow; perf->pktFlightSize = flight_span; perf->msRTT = (double)m_iSRTT / 1000.0; perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0; perf->msRcvTsbPdDelay = isOPT_TsbPd() ? m_iTsbPdDelay_ms : 0; perf->byteMSS = m_config.iMSS; perf->mbpsMaxBW = m_config.llMaxBW > 0 ? Bps2Mbps(m_config.llMaxBW) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0; if (clear) { m_stats.sndr.resetTrace(); m_stats.rcvr.resetTrace(); m_stats.sndDuration = 0; m_stats.tsLastSampleTime = currtime; } } const int64_t availbw = m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth.load(); perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize)); if (tryEnterCS(m_ConnectionLock)) { if (m_pSndBuffer) { if (instantaneous) { /* Get instant SndBuf instead of moving average for application-based Algorithm (such as NAE) in need of fast reaction to network condition changes. */ perf->pktSndBuf = m_pSndBuffer->getCurrBufSize((perf->byteSndBuf), (perf->msSndBuf)); } else { perf->pktSndBuf = m_pSndBuffer->getAvgBufSize((perf->byteSndBuf), (perf->msSndBuf)); } perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize); perf->byteAvailSndBuf = (m_config.iSndBufSize - perf->pktSndBuf) * m_config.iMSS; } else { perf->byteAvailSndBuf = 0; perf->pktSndBuf = 0; perf->byteSndBuf = 0; perf->msSndBuf = 0; } if (m_pRcvBuffer) { ScopedLock lck(m_RcvBufferLock); perf->byteAvailRcvBuf = (int) getAvailRcvBufferSizeNoLock() * m_config.iMSS; if (instantaneous) // no need for historical API for Rcv side { perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf); } else { perf->pktRcvBuf = m_pRcvBuffer->getRcvAvgDataSize(perf->byteRcvBuf, perf->msRcvBuf); } } else { perf->byteAvailRcvBuf = 0; perf->pktRcvBuf = 0; perf->byteRcvBuf = 0; perf->msRcvBuf = 0; } leaveCS(m_ConnectionLock); } else { perf->byteAvailSndBuf = 0; perf->byteAvailRcvBuf = 0; perf->pktSndBuf = 0; perf->byteSndBuf = 0; perf->msSndBuf = 0; perf->byteRcvBuf = 0; perf->msRcvBuf = 0; } } bool CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) { // Special things that must be done HERE, not in SrtCongestion, // because it involves the input buffer in CUDT. It would be // slightly dangerous to give SrtCongestion access to it. // According to the rules, the congctl should be ready at the same // time when the sending buffer. For sanity check, check both first. if (!m_CongCtl.ready() || !m_pSndBuffer) { LOGC(rslog.Error, log << CONID() << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY") << "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created")); return false; } HLOGC(rslog.Debug, log << CONID() << "updateCC: EVENT:" << TransmissionEventStr(evt)); if (evt == TEV_INIT) { // only_input uses: // 0: in the beginning and when SRTO_MAXBW was changed // 1: SRTO_INPUTBW was changed // 2: SRTO_OHEADBW was changed EInitEvent only_input = arg.get(); // false = TEV_INIT_RESET: in the beginning, or when MAXBW was changed. if (only_input != TEV_INIT_RESET && m_config.llMaxBW) { HLOGC(rslog.Debug, log << CONID() << "updateCC/TEV_INIT: non-RESET stage and m_config.llMaxBW already set to " << m_config.llMaxBW); // Don't change } else // either m_config.llMaxBW == 0 or only_input == TEV_INIT_RESET { // Use the values: // - if SRTO_MAXBW is >0, use it. // - if SRTO_MAXBW == 0, use SRTO_INPUTBW + SRTO_OHEADBW // - if SRTO_INPUTBW == 0, pass 0 to request in-buffer sampling // Bytes/s const int64_t bw = m_config.llMaxBW != 0 ? m_config.llMaxBW : // When used SRTO_MAXBW m_config.llInputBW != 0 ? withOverhead(m_config.llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW 0; // When both MAXBW and INPUTBW are 0, request in-buffer sampling // Note: setting bw == 0 uses BW_INFINITE value in LiveCC m_CongCtl->updateBandwidth(m_config.llMaxBW, bw); if (only_input == TEV_INIT_OHEADBW) { // On updated SRTO_OHEADBW don't change input rate. // This only influences the call to withOverhead(). } else { // No need to calculate input rate if the bandwidth is set const bool disable_in_rate_calc = (bw != 0); m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc); } HLOGC(rslog.Debug, log << CONID() << "updateCC/TEV_INIT: updating BW=" << m_config.llMaxBW << (only_input == TEV_INIT_RESET ? " (UNCHANGED)" : only_input == TEV_INIT_OHEADBW ? " (only Overhead)" : " (updated sampling rate)")); } } // This part is also required only by LiveCC, however not // moved there due to that it needs access to CSndBuffer. if (evt == TEV_ACK || evt == TEV_LOSSREPORT || evt == TEV_CHECKTIMER || evt == TEV_SYNC) { // Specific part done when MaxBW is set to 0 (auto) and InputBW is 0. // This requests internal input rate sampling. // XXX NOTE: This is called with affinity to receiver worker thread, // but these values can be altered by setOpt() from the main thread. if (m_config.llMaxBW == 0 && m_config.llInputBW == 0) { // Get auto-calculated input rate, Bytes per second const int64_t inputbw = m_pSndBuffer->getInputRate(); /* * On blocked transmitter (tx full) and until connection closes, * auto input rate falls to 0 but there may be still lot of packet to retransmit * Calling updateBandwidth with 0 sets maxBW to default BW_INFINITE (1 Gbps) * and sendrate skyrockets for retransmission. * Keep previously set maximum in that case (inputbw == 0). */ if (inputbw >= 0) m_CongCtl->updateBandwidth(0, withOverhead(std::max(m_config.llMinInputBW, inputbw))); // Bytes/sec } } HLOGC(rslog.Debug, log << CONID() << "updateCC: emitting signal for EVENT:" << TransmissionEventStr(evt)); // Now execute a congctl-defined action for that event. EmitSignal(evt, arg); // This should be done with every event except ACKACK and SEND/RECEIVE // After any action was done by the congctl, update the congestion window and sending interval. if (evt != TEV_ACKACK && evt != TEV_SEND && evt != TEV_RECEIVE) { // This part comes from original UDT. // NOTE: THESE things come from CCC class: // - m_dPktSndPeriod // - m_dCWndSize m_tdSendInterval = microseconds_from((int64_t)m_CongCtl->pktSndPeriod_us()); const double cgwindow = m_CongCtl->cgWindowSize(); m_iCongestionWindow = (int) cgwindow; #if HVU_ENABLE_HEAVY_LOGGING HLOGC(rslog.Debug, log << CONID() << "updateCC: updated values from congctl: interval=" << FormatDuration(m_tdSendInterval) << " (cfg:" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << fmt(cgwindow, fmtc().precision(7))); #endif } HLOGC(rslog.Debug, log << CONID() << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt)); return true; } void CUDT::initSynch() { setupMutex(m_SendBlockLock, "SendBlock"); setupCond(m_SendBlockCond, "SendBlock"); setupCond(m_RecvDataCond, "RecvData"); setupMutex(m_SendLock, "Send"); setupMutex(m_RecvLock, "Recv"); setupMutex(m_RcvLossLock, "RcvLoss"); setupMutex(m_RecvAckLock, "RecvAck"); setupMutex(m_RcvBufferLock, "RcvBuffer"); setupMutex(m_ConnectionLock, "Connection"); setupMutex(m_StatsLock, "Stats"); setupCond(m_RcvTsbPdCond, "RcvTsbPd"); } void CUDT::destroySynch() { releaseMutex(m_SendBlockLock); // Just in case, signal the CV, on which some // other thread is possibly waiting, because a // process hanging on a pthread_cond_wait would // cause the call to destroy a CV hang up. m_SendBlockCond.notify_all(); releaseCond(m_SendBlockCond); m_RecvDataCond.notify_all(); releaseCond(m_RecvDataCond); releaseMutex(m_SendLock); releaseMutex(m_RecvLock); releaseMutex(m_RcvLossLock); releaseMutex(m_RecvAckLock); releaseMutex(m_RcvBufferLock); releaseMutex(m_ConnectionLock); releaseMutex(m_StatsLock); m_RcvTsbPdCond.notify_all(); releaseCond(m_RcvTsbPdCond); } void srt::CUDT::resetAtFork() { resetCond(m_SendBlockCond); resetCond(m_RecvDataCond); resetCond(m_RcvTsbPdCond); } void CUDT::releaseSynch() { SRT_ASSERT(m_bClosing); if (!m_bClosing) { LOGC(smlog.Error, log << "releaseSynch: IPE: m_bClosing not set to false, TSBPD might hangup!"); m_bClosing = true; } // wake up user calls CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); enterCS(m_SendLock); leaveCS(m_SendLock); // Awake tsbpd() and srt_recv*(..) threads for them to check m_bClosing. CSync::lock_notify_all(m_RecvDataCond, m_RecvLock); CSync::lock_notify_all(m_RcvTsbPdCond, m_RecvLock); // Azquiring m_RcvTsbPdStartupLock protects race in starting // the tsbpd() thread in CUDT::processData(). // Wait for tsbpd() thread to finish. enterCS(m_RcvTsbPdStartupLock); if (m_RcvTsbPdThread.joinable()) { m_RcvTsbPdThread.join(); } leaveCS(m_RcvTsbPdStartupLock); // Acquiring the m_RecvLock it is assumed that both tsbpd() // and srt_recv*(..) threads will be aware about the state of m_bClosing. enterCS(m_RecvLock); leaveCS(m_RecvLock); } #if HVU_ENABLE_HEAVY_LOGGING static void DebugAck(string hdr, int prev, int ack) { if (!prev) { HLOGC(xtlog.Debug, log << hdr << "ACK " << ack); return; } int diff = CSeqNo::seqoff(prev, ack); if (diff < 0) { HLOGC(xtlog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")"); return; } bool shorted = diff > 100; // sanity if (shorted) ack = CSeqNo::incseq(prev, 100); ostringstream ackv; for (; prev != ack; prev = CSeqNo::incseq(prev)) ackv << prev << " "; if (shorted) ackv << "..."; HLOGC(xtlog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack); } #else static inline void DebugAck(string, int, int) {} #endif void CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size) { CPacket ctrlpkt; setPacketTS(ctrlpkt, steady_clock::now()); int nbsent = 0; switch (pkttype) { case UMSG_ACK: // 010 - Acknowledgement { nbsent = sendCtrlAck(ctrlpkt, size); break; } case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement ctrlpkt.pack(pkttype, lparam); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_LOSSREPORT: // 011 - Loss Report { ScopedLock lock(m_RcvLossLock); // Explicitly defined lost sequences if (rparam) { int32_t *lossdata = (int32_t *)rparam; size_t bytes = sizeof(*lossdata) * size; ctrlpkt.pack(pkttype, NULL, lossdata, bytes); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); enterCS(m_StatsLock); m_stats.rcvr.sentNak.count(1); leaveCS(m_StatsLock); } // Call with no arguments - get loss list from internal data. else if (m_pRcvLossList->getLossLength() > 0) { // this is periodically NAK report; make sure NAK cannot be sent back too often // read loss list from the local receiver loss list int32_t *data = new int32_t[m_iMaxSRTPayloadSize / 4]; int losslen; m_pRcvLossList->getLossArray(data, losslen, m_iMaxSRTPayloadSize / 4); if (0 < losslen) { ctrlpkt.pack(pkttype, NULL, data, losslen * 4); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); enterCS(m_StatsLock); m_stats.rcvr.sentNak.count(1); leaveCS(m_StatsLock); } delete[] data; } // update next NAK time, which should wait enough time for the retansmission, but not too long m_tdNAKInterval = microseconds_from(m_iSRTT + 4 * m_iRTTVar); // Fix the NAKreport period according to the congctl m_tdNAKInterval = microseconds_from(m_CongCtl->updateNAKInterval(count_microseconds(m_tdNAKInterval), m_RcvTimeWindow.getPktRcvSpeed(), m_pRcvLossList->getLossLength())); // This is necessary because a congctl need not wish to define // its own minimum interval, in which case the default one is used. if (m_tdNAKInterval < m_tdMinNakInterval) m_tdNAKInterval = m_tdMinNakInterval; break; } case UMSG_CGWARNING: // 100 - Congestion Warning ctrlpkt.pack(pkttype); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); m_tsLastWarningTime = steady_clock::now(); break; case UMSG_KEEPALIVE: // 001 - Keep-alive ctrlpkt.pack(pkttype); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_HANDSHAKE: // 000 - Handshake ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake)); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_SHUTDOWN: // 101 - Shutdown if (m_PeerID == SRT_SOCKID_CONNREQ) // Don't send SHUTDOWN if we don't know peer ID. break; ctrlpkt.pack(pkttype, NULL, rparam, size); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_DROPREQ: // 111 - Msg drop request ctrlpkt.pack(pkttype, lparam, rparam, 8); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error ctrlpkt.pack(pkttype, lparam); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_EXT: // 0x7FFF - Reserved for future use break; default: break; } // Fix keepalive if (nbsent) m_tsLastSndTime.store(steady_clock::now()); } bool CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) { if (!m_pRcvBuffer) { LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); return false; } if (m_config.bTSBPD || !m_config.bMessageAPI) { // NOTE: it's not only about protecting the buffer itself, it's also protecting // the section where the m_iRcvCurrSeqNo is updated. ScopedLock buflock (m_RcvBufferLock); // The getFirstNonreadSeqNo() function returns the sequence number of the first packet // that cannot be read. In cases when a message can consist of several data packets, // an existing packet of partially available message also cannot be read. // If TSBPD mode is enabled, a message must consist of a single data packet only. w_seq = m_pRcvBuffer->getFirstNonreadSeqNo(); const int32_t iNextSeqNo = CSeqNo::incseq(m_iRcvCurrSeqNo); SRT_ASSERT(CSeqNo::seqcmp(w_seq, iNextSeqNo) <= 0); w_log_reason = iNextSeqNo != w_seq ? "first lost" : "expected next"; if (CSeqNo::seqcmp(w_seq, iNextSeqNo) > 0) { LOGC(xtlog.Error, log << "IPE: NONCONT-SEQUENCE: RCV buffer first non-read %" << w_seq << ", RCV latest seqno %" << m_iRcvCurrSeqNo.load()); w_seq = iNextSeqNo; } return true; } ScopedLock buflock (m_RcvBufferLock); bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); if (has_followers) w_log_reason = "first lost"; else w_log_reason = "expected next"; return true; } int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { int nbsent = 0; int local_prevack = 0; #if HVU_ENABLE_HEAVY_LOGGING struct SaveBack { int& target; const int& source; ~SaveBack() { target = source; } } l_saveback = { m_iDebugPrevLastAck, m_iRcvLastAck }; (void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable] local_prevack = m_iDebugPrevLastAck; #endif // NOTE: the below calls do locking on m_RcvBufferLock. // Hence up to the handling of lite ACK, the scoped lock is not applied. // The full ACK should be sent to indicate there is now available space in the RCV buffer // since the last full ACK. It should unblock the sender to proceed further. const bool bNeedFullAck = (m_bBufferWasFull && !isRcvBufferFull()); int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). string reason; // just for "a reason" of giving particular % for ACK if (!getFirstNoncontSequence((ack), (reason))) return nbsent; if (m_iRcvLastAckAck == ack && !bNeedFullAck) { HLOGC(xtlog.Debug, log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); return nbsent; } // send out a lite ACK // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number if (size == SEND_LITE_ACK && !bNeedFullAck) { ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); DebugAck(CONID() + "sendCtrl(lite): ", local_prevack, ack); return nbsent; } // Lock the group existence until this function ends. This will be useful // also on other places. #if SRT_ENABLE_BONDING CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); #endif // There are new received packets to acknowledge, update related information. /* tsbpd thread may also call ackData when skipping packet so protect code */ UniqueLock bufflock(m_RcvBufferLock); // IF ack %> m_iRcvLastAck // There are new received packets to acknowledge, update related information. if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) { // Sanity check if the "selected ACK" points to a sequence // in the past for the buffer. This SHOULD NEVER HAPPEN because // on drop the loss records should have been removed, and the last received // sequence also can't be in the past towards the buffer. // NOTE: This problem has been observed when the packet sequence // was incorrectly removed from the receiver loss list. This should // then stay here as a condition in order to detect this problem, // should it happen in the future. if (CSeqNo::seqcmp(ack, m_pRcvBuffer->getStartSeqNo()) < 0) { LOGC(xtlog.Error, log << CONID() << "sendCtrlAck: IPE: invalid ACK from %" << m_iRcvLastAck << " to %" << ack << " (" << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets) buffer=%" << m_pRcvBuffer->getStartSeqNo()); } else { HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: %" << m_iRcvLastAck << " -> %" << ack << " (" << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets)"); } m_iRcvLastAck = ack; #if SRT_ENABLE_BONDING const int32_t group_read_seq = m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()).seqno; #endif InvertedLock un_bufflock (m_RcvBufferLock); #if SRT_ENABLE_BONDING // This actually should be done immediately after the ACK pointers were // updated in this socket, but it can't be done inside this function due // to being run under a lock. // At this moment no locks are applied. The only lock used so far // was m_RcvBufferLock, but this was lifed above. At this moment // it is safe to apply any locks here. This function is affined // to CRcvQueue::worker thread, so it is free to apply locks as // required in the defined order. At present we only need the lock // on m_GlobControlLock to prevent the group from being deleted // in the meantime if (m_parent->m_GroupOf) { // Check is first done before locking to avoid unnecessary // mutex locking. The condition for this field is that it // can be either never set, already reset, or ever set // and possibly dangling. The re-check after lock eliminates // the dangling case. SharedLock glock (uglobal().m_GlobControlLock); // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, // but this is an intended order. if (m_parent->m_GroupOf) { // A group may need to update the parallelly used idle links, // should it have any. Pass the current socket position in order // to skip it from the group loop. m_parent->m_GroupOf->updateLatestRcv(m_parent); } } #endif // Signalling m_RecvDataCond is not done when TSBPD is on. // This signalling is done in file mode in order to keep the // API reader thread sleeping until there is a "bigger portion" // of data to read. In TSBPD mode this isn't done because every // packet has its individual delivery time and its readiness is signed // off by the TSBPD thread. HLOGC(xtlog.Debug, log << CONID() << "ACK: clip %" << m_iRcvLastAck << "-%" << ack << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); // There's no need to update TSBPD in the wake-on-recv state // from ACK because it is being done already in the receiver thread // when a newly inserted packet caused provision of a new candidate // that could be delivered soon. Also, this flag is only used in TSBPD // mode and can be only set to true in the TSBPD thread. if (!m_bTsbPd) { { CUniqueSync rdcc (m_RecvLock, m_RecvDataCond); // Locks m_RcvBufferLock, which is unlocked above by InvertedLock un_bufflock. // Must check read-readiness under m_RecvLock to protect the epoll from concurrent changes in readBuffer() if (isRcvBufferReady()) { if (m_config.bSynRecving) { // signal a waiting "recv" call if there is any data available rdcc.notify_one(); } // acknowledge any waiting epolls to read // fix SRT_EPOLL_IN event loss but rcvbuffer still have data: // 1. user call receive/receivemessage(about line number:6482) // 2. after read/receive, if rcvbuffer is empty, will set SRT_EPOLL_IN event to false // 3. but if we do not do some lock work here, will cause some sync problems between threads: // (1) user thread: call receive/receivemessage // (2) user thread: read data // (3) user thread: no data in rcvbuffer, set SRT_EPOLL_IN event to false // (4) receive thread: receive data and set SRT_EPOLL_IN to true // (5) user thread: set SRT_EPOLL_IN to false // 4. so , m_RecvLock must be used here to protect epoll event uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } } #if SRT_ENABLE_BONDING if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) { // See above explanation for double-checking SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { // The current "APP reader" needs to simply decide as to whether // the next CUDTGroup::recv() call should return with no blocking or not. // When the group is read-ready, it should update its pollers as it sees fit. m_parent->m_GroupOf->updateReadState(m_SocketID, group_read_seq); } } #endif CGlobEvent::triggerEvent(); } } else if (ack == m_iRcvLastAck && !bNeedFullAck) { // If the ACK was just sent already AND elapsed time did not exceed RTT, if ((steady_clock::now() - m_tsLastAckTime) < (microseconds_from(m_iSRTT + 4 * m_iRTTVar))) { HLOGC(xtlog.Debug, log << CONID() << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat"); return nbsent; } } else if (!bNeedFullAck) { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) LOGC(xtlog.Error, log << CONID() << "sendCtrl(UMSG_ACK): IPE: curr(" << reason << ") %" << ack << " <% last %" << m_iRcvLastAck); return nbsent; } // [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]]; // [[using locked(m_RcvBufferLock)]]; // Send out the ACK only if has not been received by the sender before if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0 || bNeedFullAck) { // NOTE: The BSTATS feature turns on extra fields above size 6 // also known as ACKD_TOTAL_SIZE_VER100. int32_t data[ACKD_TOTAL_SIZE]; // Case you care, CAckNo::incack does exactly the same thing as // CSeqNo::incseq. Logically the ACK number is a different thing // than sequence number (it's a "journal" for ACK request-response, // and starts from 0, unlike sequence, which starts from a random // number), but still the numbers are from exactly the same domain. m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo); data[ACKD_RCVLASTACK] = m_iRcvLastAck; data[ACKD_RTT] = m_iSRTT; data[ACKD_RTTVAR] = m_iRTTVar; data[ACKD_BUFFERLEFT] = (int) getAvailRcvBufferSizeNoLock(); m_bBufferWasFull = data[ACKD_BUFFERLEFT] == 0; if (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval) { int rcvRate; int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed((rcvRate)); data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); //>>Patch while incompatible (1.0.2) receiver floating around if (m_uPeerSrtVersion == SrtVersion(1, 0, 2)) { data[ACKD_RCVRATE] = rcvRate; // bytes/sec data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102_ONLY; } else if (m_uPeerSrtVersion >= SrtVersion(1, 0, 3)) { // Normal, currently expected version. data[ACKD_RCVRATE] = rcvRate; // bytes/sec ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101; } // ELSE: leave the buffer with ...UDTBASE size. ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ctrlsz); m_tsLastAckTime = steady_clock::now(); } else { ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); } bufflock.unlock(); ctrlpkt.set_id(m_PeerID); setPacketTS(ctrlpkt, steady_clock::now()); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); DebugAck(CONID() + "sendCtrl(UMSG_ACK): ", local_prevack, ack); m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); enterCS(m_StatsLock); m_stats.rcvr.sentAck.count(1); leaveCS(m_StatsLock); } else { HLOGC(xtlog.Debug, log << CONID() << "sendCtrl(UMSG_ACK): " << "ACK %" << m_iRcvLastAck << " <=% ACKACK %" << m_iRcvLastAckAck << " - NOT SENDING ACK"); } return nbsent; } void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) { #if SRT_ENABLE_BONDING // This is for the call of CSndBuffer::getMsgNoAt that returns // this value as a notfound-trap. int32_t msgno_at_last_acked_seq = SRT_MSGNO_CONTROL; bool is_group = m_parent->m_GroupOf; #endif // Update sender's loss list and acknowledge packets in the sender's buffer { // m_RecvAckLock protects sender's loss list and epoll ScopedLock ack_lock(m_RecvAckLock); const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno); // IF distance between m_iSndLastDataAck and ack is nonempty... if (offset <= 0) return; // update sending variables m_iSndLastDataAck = ackdata_seqno; #if SRT_ENABLE_BONDING if (is_group) { // Get offset-1 because 'offset' points actually to past-the-end // of the sender buffer. We have already checked that offset is // at least 1. msgno_at_last_acked_seq = m_pSndBuffer->getMsgNoAt(offset-1); // Just keep this value prepared; it can't be updated exactly right // now because accessing the group needs some locks to be applied // with preserved the right locking order. } #endif // remove any loss that predates 'ack' (not to be considered loss anymore) m_pSndLossList->removeUpTo(CSeqNo::decseq(m_iSndLastDataAck)); // acknowledge the sending buffer (remove data that predate 'ack') m_pSndBuffer->ackData(offset); // acknowledde any waiting epolls to write uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); CGlobEvent::triggerEvent(); } #if SRT_ENABLE_BONDING if (is_group) { // m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only // be done now that m_RecvAckLock is unlocked. SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { HLOGC(inlog.Debug, log << CONID() << "ACK: acking group sender buffer for #" << msgno_at_last_acked_seq); // Guard access to m_iSndAckedMsgNo field // Note: This can't be done inside CUDTGroup::ackMessage // because this function is also called from CUDT::sndDropTooLate // called from CUDT::sendmsg2 called from CUDTGroup::send, which // applies the lock on m_GroupLock already. ScopedLock glk (*m_parent->m_GroupOf->exp_groupLock()); // NOTE: ackMessage also accepts and ignores the trap representation // which is SRT_MSGNO_CONTROL. m_parent->m_GroupOf->ackMessage(msgno_at_last_acked_seq); } } #endif // insert this socket to snd list if it is not on the list yet const steady_clock::time_point currtime = m_pMuxer->updateSendNormal(m_parent); if (m_config.bSynSending) { CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); } // record total time used for sending enterCS(m_StatsLock); m_stats.sndDuration += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.sndDurationCounter = currtime; leaveCS(m_StatsLock); } void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) { const int32_t* ackdata = (const int32_t*)ctrlpkt.m_pcData; const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK]; // Check the value of ACK in case when it was some rogue peer if (ackdata_seqno < 0) { // This embraces all cases when the most significant bit is set, // as the variable is of a signed type. So, SRT_SEQNO_NONE is // included, but it also triggers for any other kind of invalid value. // This check MUST BE DONE before making any operation on this number. LOGC(inlog.Error, log << CONID() << "ACK: IPE/EPE: received invalid ACK value: " << ackdata_seqno << " " << fmt(ackdata_seqno, hex) << " (IGNORED)"); return; } const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, log << CONID() << "ACK covers: " << m_iSndLastDataAck.load() << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); updateSndLossListOnACK(ackdata_seqno); // Process a lite ACK if (isLiteAck) { ScopedLock ack_lock(m_RecvAckLock); if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } return; } // Decide to send ACKACK or not { // Sequence number of the ACK packet const int32_t ack_seqno = ctrlpkt.getAckSeqNo(); // Send ACK acknowledgement (UMSG_ACKACK). // There can be less ACKACK packets in the stream, than the number of ACK packets. // Only send ACKACK every syn interval or if ACK packet with the sequence number // already acknowledged (with ACKACK) has come again, which probably means ACKACK was lost. if ((currtime - m_SndLastAck2Time > microseconds_from(COMM_SYN_INTERVAL_US)) || (ack_seqno == m_iSndLastAck2)) { sendCtrl(UMSG_ACKACK, &ack_seqno); m_iSndLastAck2 = ack_seqno; m_SndLastAck2Time = currtime; } } // // Begin of the new code with TLPKTDROP. // // Protect packet retransmission { UniqueLock ack_lock(m_RecvAckLock); // Check the validation of the ack if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) { ack_lock.unlock(); // this should not happen: attack or bug LOGC(gglog.Error, log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current " << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); m_bBroken = true; m_iBrokenCounter = 0; setAgentCloseReason(SRT_CLS_IPE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! return; } if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow); const bool bWasStuck = cwnd1 <= getFlightSpan(); // Update Flow Window Size, must update before and together with m_iSndLastAck m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; m_iSndLastAck = ackdata_seqno; m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); if (bWasStuck && cwnd > getFlightSpan()) { m_pMuxer->updateSendNormal(m_parent); HLOGC(gglog.Debug, log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); } } /* * We must not ignore full ack received by peer * if data has been artificially acked by late packet drop. * Therefore, a distinct ack state is used for received Ack (iSndLastFullAck) * and ack position in send buffer (m_iSndLastDataAck). * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) * occurs, we drop received acks (as duplicates) and do not update stats like RTT, * which may go crazy and stay there, preventing proper stream recovery. */ if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) { // discard it if it is a repeated ACK return; } m_iSndLastFullAck = ackdata_seqno; } // // END of the new code with TLPKTDROP // #if SRT_ENABLE_BONDING if (!m_bClosing && m_parent->m_GroupOf) { SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { // Will apply m_GroupLock, ordered after m_GlobControlLock. // m_GlobControlLock is necessary for group existence. m_parent->m_GroupOf->updateWriteState(); } } #endif size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING bool wrongsize = 0 != (acksize % ACKD_FIELD_SIZE); acksize = acksize / ACKD_FIELD_SIZE; // ACTUAL VALUE if (wrongsize) { // Issue a log, but don't do anything but skipping the "odd" bytes from the payload. LOGC(inlog.Warn, log << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to " << acksize << " fields"); } // Start with checking the base size. if (acksize < ACKD_TOTAL_SIZE_SMALL) { LOGC(inlog.Warn, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!"); // Ack is already interpreted, just skip further parts. return; } // This check covers fields up to ACKD_BUFFERLEFT. // Extract RTT estimate and RTTVar from the ACK packet. const int rtt = ackdata[ACKD_RTT]; const int rttvar = ackdata[ACKD_RTTVAR]; // Update the values of smoothed RTT and the variation in RTT samples // on subsequent RTT estimates extracted from the ACK packets // (during transmission). if (m_bIsFirstRTTReceived) { // Suppose transmission is bidirectional if sender is also receiving // data packets. enterCS(m_StatsLock); const bool bPktsReceived = m_stats.rcvr.recvd.total.count() != 0; leaveCS(m_StatsLock); if (bPktsReceived) // Transmission is bidirectional. { // RTT value extracted from the ACK packet (rtt) is already smoothed // RTT obtained at the receiver side. Apply EWMA anyway for the second // time on the sender side. Ignore initial values which might arrive // after the smoothed RTT on the sender side has been // reset to the very first RTT sample received from the receiver. // TODO: The case of bidirectional transmission requires further // improvements and testing. Double smoothing is applied here to be // consistent with the previous behavior. if (rtt != INITIAL_RTT || rttvar != INITIAL_RTTVAR) { int iSRTT = m_iSRTT.load(), iRTTVar = m_iRTTVar.load(); iRTTVar = avg_iir<4>(iRTTVar, abs(rtt - iSRTT)); iSRTT = avg_iir<8>(iSRTT, rtt); m_iSRTT = iSRTT; m_iRTTVar = iRTTVar; } } else // Transmission is unidirectional. { // Simply take the values of smoothed RTT and RTT variance from // the ACK packet. m_iSRTT = rtt; m_iRTTVar = rttvar; } } // Reset the value of smoothed RTT to the first real RTT estimate extracted // from an ACK after initialization (at the beginning of transmission). // In case of resumed connection over the same network, the very first RTT // value sent within an ACK will be taken from cache and equal to previous // connection's final smoothed RTT value. The reception of such a value // will also trigger the smoothed RTT reset at the sender side. else if (rtt != INITIAL_RTT && rttvar != INITIAL_RTTVAR) { m_iSRTT = rtt; m_iRTTVar = rttvar; m_bIsFirstRTTReceived = true; } #if SRT_DEBUG_RTT s_rtt_trace.trace(currtime, "ACK", rtt, rttvar, m_bIsFirstRTTReceived, m_stats.recvTotal, m_iSRTT, m_iRTTVar); #endif /* Version-dependent fields: * Original UDT (total size: ACKD_TOTAL_SIZE_SMALL): * ACKD_RCVLASTACK * ACKD_RTT * ACKD_RTTVAR * ACKD_BUFFERLEFT * Additional UDT fields, not always attached: * ACKD_RCVSPEED * ACKD_BANDWIDTH * SRT extension since v1.0.1: * ACKD_RCVRATE * SRT extension in v1.0.2 only: * ACKD_XMRATE_VER102_ONLY */ if (acksize > ACKD_TOTAL_SIZE_SMALL) { // This means that ACKD_RCVSPEED and ACKD_BANDWIDTH fields are available. int pktps = ackdata[ACKD_RCVSPEED]; int bandwidth = ackdata[ACKD_BANDWIDTH]; int bytesps; /* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE_VER102_ONLY]) and delivery rate (pcData[ACKD_RCVRATE]) in * bytes/sec instead of pkts/sec */ /* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */ if (acksize > ACKD_TOTAL_SIZE_UDTBASE) bytesps = ackdata[ACKD_RCVRATE]; else bytesps = pktps * m_iMaxSRTPayloadSize; m_iBandwidth = avg_iir<8>(m_iBandwidth.load(), bandwidth); m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate.load(), pktps); m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate.load(), bytesps); // Update Estimated Bandwidth and packet delivery rate // m_iRcvRate = m_iDeliveryRate; // ^^ This has been removed because with the SrtCongestion class // instead of reading the m_iRcvRate local field this will read // cudt->deliveryRate() instead. } updateCC(TEV_ACK, EventVariant(ackdata_seqno)); enterCS(m_StatsLock); m_stats.sndr.recvdAck.count(1); leaveCS(m_StatsLock); } void CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) { int32_t ack = 0; // Calculate RTT estimate on the receiver side based on ACK/ACKACK pair. const int rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack, tsArrival); if (rtt == -1) { if (ctrlpkt.getAckSeqNo() > (m_iAckSeqNo - static_cast(ACK_WND_SIZE)) && ctrlpkt.getAckSeqNo() <= m_iAckSeqNo) { string why; if (frequentLogAllowed(FREQLOGFA_ACKACK_OUTOFORDER, tsArrival, (why))) { HLOGC(inlog.Debug, log << CONID() << "ACKACK out of order, skipping RTT calculation " << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo << ", RTT (EWMA): " << m_iSRTT << ")." << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE else { LOGC(qrlog.Note, log << "SUPPRESSED: ACKACK out of order LOG: " << why); } #endif return; } LOGC(inlog.Error, log << CONID() << "ACK record not found, can't estimate RTT " << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo << ", RTT (EWMA): " << m_iSRTT << ")"); return; } if (rtt <= 0) { LOGC(inlog.Error, log << CONID() << "IPE: invalid RTT estimate " << rtt << ", possible time shift. Clock: " << SRT_SYNC_CLOCK_STR); return; } // If increasing delay is detected. // sendCtrl(UMSG_CGWARNING); // Update the values of smoothed RTT and the variation in RTT samples // on subsequent RTT samples (during transmission). if (m_bIsFirstRTTReceived) { m_iRTTVar = avg_iir<4>(m_iRTTVar.load(), abs(rtt - m_iSRTT.load())); m_iSRTT = avg_iir<8>(m_iSRTT.load(), rtt); } // Reset the value of smoothed RTT on the first RTT sample after initialization // (at the beginning of transmission). // In case of resumed connection over the same network, the initial RTT // value will be taken from cache and equal to previous connection's // final smoothed RTT value. else { m_iSRTT = rtt; m_iRTTVar = rtt / 2; m_bIsFirstRTTReceived = true; } #if SRT_DEBUG_RTT s_rtt_trace.trace(tsArrival, "ACKACK", rtt, -1, m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); #endif updateCC(TEV_ACKACK, EventVariant(ack)); // This function will put a lock on m_RecvLock by itself, as needed. // It must be done inside because this function reads the current time // and if waiting for the lock has caused a delay, the time will be // inaccurate. Additionally it won't lock if TSBPD mode is off, and // won't update anything. Note that if you set TSBPD mode and use // srt_recvfile (which doesn't make any sense), you'll have a deadlock. if (m_config.bDriftTracer) { #if SRT_ENABLE_BONDING ExclusiveLock glock(uglobal().m_GlobControlLock); // XXX not too excessive? const bool drift_updated = #endif m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); #if SRT_ENABLE_BONDING if (drift_updated && m_parent->m_GroupOf) m_parent->m_GroupOf->synchronizeDrift(this); #endif } // Update last ACK that has been received by the sender if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) m_iRcvLastAckAck = ack; } void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData); const size_t losslist_len = ctrlpkt.getLength() / 4; bool secure = true; // This variable is used in "normal" logs, so it may cause a warning // when logging is forcefully off. int32_t wrong_loss SRT_ATR_UNUSED = SRT_SEQNO_NONE; // protect packet retransmission { ScopedLock ack_lock(m_RecvAckLock); // decode loss list message and insert loss into the sender loss list for (int i = 0, n = (int)losslist_len; i < n; ++i) { // IF the loss is a range if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) { // Then it's this is a specification with HI in a consecutive cell. const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]); const int32_t losslist_hi = losslist[i + 1]; // specification means that the consecutive cell has been already interpreted. ++i; HLOGC(inlog.Debug, log << CONID() << "received UMSG_LOSSREPORT: " << losslist_lo << "-" << losslist_hi << " (" << CSeqNo::seqlen(losslist_lo, losslist_hi) << " packets)..."); if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) || (CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0)) { // LO must not be greater than HI. // HI must not be greater than the most recent sent seq. LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT rng " << losslist_lo << " - " << losslist_hi << " with last sent " << m_iSndCurrSeqNo << " - DISCARDING"); secure = false; wrong_loss = losslist_hi; break; } int num = 0; // IF losslist_lo %>= m_iSndLastAck if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << losslist_lo << " - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(losslist_lo, losslist_hi); } // ELSE losslist_lo %< m_iSndLastAck else { // This should be theoretically impossible because this would mean that // the received packet loss report informs about the loss that predates // the ACK sequence. // However, this can happen in these situations: // - if the packet reordering has caused the earlier sent LOSSREPORT will be // delivered after later sent ACK. Whatever, ACK should be more important, // so simply drop the part that predates ACK. // - redundancy second link (ISN was screwed up initially, but late towards last sent) // - initial DROPREQ was lost // This just causes repeating DROPREQ, as when the receiver continues sending // LOSSREPORT, it's probably UNAWARE OF THE SITUATION. int32_t dropreq_hi = losslist_hi; IF_HEAVY_LOGGING(const char* drop_type = "completely"); // IF losslist_hi %>= m_iSndLastAck if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); } // In distinction to losslist, DROPREQ has always just one range, // and the data are , with no range bit. int32_t seqpair[2] = { losslist_lo, dropreq_hi }; const int32_t no_msgno = 0; // We don't know. // When this DROPREQ gets lost in UDP again, the receiver will do one of these: // - repeatedly send LOSSREPORT (as per NAKREPORT), so this will happen again // - finally give up rexmit request as per TLPKTDROP (DROPREQ should make // TSBPD wake up should it still wait for new packets to get ACK-ed) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: " << drop_type << " IGNORED with SndLastAck=%" << m_iSndLastAck << ": %" << losslist_lo << "-" << dropreq_hi << " - sending DROPREQ"); sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); } enterCS(m_StatsLock); m_stats.sndr.lost.count(num); leaveCS(m_StatsLock); } // ELSE the loss is a single seq else { // IF loss_seq %>= m_iSndLastAck if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0) { if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0) { LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT pkt %" << losslist[i] << " with last sent %" << m_iSndCurrSeqNo << " - DISCARDING"); // loss_seq must not be greater than the most recent sent seq secure = false; wrong_loss = losslist[i]; break; } HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); const int num = m_pSndLossList->insert(losslist[i], losslist[i]); enterCS(m_StatsLock); m_stats.sndr.lost.count(num); leaveCS(m_StatsLock); } // ELSE loss_seq %< m_iSndLastAck else { // In distinction to losslist, DROPREQ has always just one range, // and the data are , with no range bit. int32_t seqpair[2] = { losslist[i], losslist[i] }; const int32_t no_msgno = 0; // We don't know. HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: IGNORED with SndLastAck=%" << m_iSndLastAck << ": %" << losslist[i] << " - sending DROPREQ"); sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); } } } } updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); if (!secure) { LOGC(inlog.Warn, log << CONID() << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo << " vs loss %" << wrong_loss); // this should not happen: attack or bug m_bBroken = true; m_iBrokenCounter = 0; setAgentCloseReason(SRT_CLS_ROGUE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! return; } // the lost packet (retransmission) should be sent out immediately // NOTE: There was a change here towards UDT; update was done as "Fast" // because for a loss it should have been qualified as high priority. // But in the live mode with too many lost packets it could result in // holding head of the line for too long, while in file mode it doesn't // really improve the transmission efficiency (because it doesn't use // NAKREPORT, so the receiver won't repeat lossreport command), and the // blind rexmit mode is laterexmit (the sender will repeat sending // unacknowledged packets only when it has reached the limit with // nothing more to withdraw from the sender buffer). m_pMuxer->updateSendNormal(m_parent); enterCS(m_StatsLock); m_stats.sndr.recvdNak.count(1); leaveCS(m_StatsLock); } void CUDT::processCtrlHS(const CPacket& ctrlpkt) { CHandShake req; req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show()); if ((req.m_iReqType > URQ_INDUCTION_TYPES) // actually it catches URQ_INDUCTION and URQ_ERROR_* symbols...??? || (m_config.bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION { // The peer side has not received the handshake message, so it keeps querying // resend the handshake packet // This condition embraces cases when: // - this is normal accept() and URQ_INDUCTION was received // - this is rendezvous accept() and there's coming any kind of URQ except AGREEMENT (should be RENDEZVOUS // or CONCLUSION) // - this is any of URQ_ERROR_* - well... CHandShake tosend_hs; tosend_hs.m_iISN = m_iISN; tosend_hs.m_iMSS = m_config.iMSS; tosend_hs.m_iFlightFlagSize = m_config.iFlightFlagSize; // For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT. // For client-server we do URQ_INDUCTION --> URQ_CONCLUSION. tosend_hs.m_iReqType = (!m_config.bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT; tosend_hs.m_iID = m_SocketID; uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; bool have_hsreq = false; if (req.m_iVersion > HS_VERSION_UDT4) { tosend_hs.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener... const int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (hs_flags != 0) // has SRT extensions { HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType) << " WITH SRT ext"); have_hsreq = interpretSrtHandshake(NULL, req, ctrlpkt, (kmdata), (&kmdatasize)); if (!have_hsreq) { tosend_hs.m_iVersion = 0; m_RejectReason = SRT_REJ_ROGUE; tosend_hs.m_iReqType = URQFailure(m_RejectReason); } else { // Extensions are added only in case of CONCLUSION (not AGREEMENT). // Actually what is expected here is that this may either process the // belated-repeated handshake from a caller (and then it's CONCLUSION, // and should be added with HSRSP/KMRSP), or it's a belated handshake // of Rendezvous when it has already considered itself connected. // Sanity check - according to the rules, there should be no such situation if (m_config.bRendezvous && m_SrtHsSide == HSD_RESPONDER) { LOGC(inlog.Error, log << CONID() << "processCtrl/HS: IPE???: RESPONDER should receive all its handshakes in " "handshake phase."); } // The 'extension' flag will be set from this variable; set it to false // in case when the AGREEMENT response is to be sent. have_hsreq = tosend_hs.m_iReqType == URQ_CONCLUSION; HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(tosend_hs.m_iReqType) << " kmdatasize=" << kmdatasize); } } else { HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)); } } else { tosend_hs.m_iVersion = HS_VERSION_UDT4; kmdatasize = 0; // HSv4 doesn't add any extensions, no KMX } tosend_hs.m_extensionType = have_hsreq ? SRT_CMD_HSRSP : 0; HLOGC(inlog.Debug, log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(tosend_hs.m_iReqType) << (have_hsreq ? " WITH SRT HS response extensions" : "")); CPacket rsppkt; rsppkt.setControl(UMSG_HANDSHAKE); rsppkt.allocate(m_iMaxSRTPayloadSize); // If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE. // There is also no possible IPE condition in case of HSv4 - for this version it will always return true. enterCS(m_ConnectionLock); bool create_ok = createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (tosend_hs)); leaveCS(m_ConnectionLock); if (create_ok) { rsppkt.set_id(m_PeerID); setPacketTS(rsppkt, steady_clock::now()); const int nbsent = channel()->sendto(m_PeerAddr, rsppkt, m_SourceAddr); if (nbsent) { m_tsLastSndTime.store(steady_clock::now()); } } } else { HLOGC(inlog.Debug, log << CONID() << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."); } } void CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { typedef int32_t expected_t[2]; if (ctrlpkt.getLength() < sizeof (expected_t)) { // We ALLOW packets that are bigger than this to allow // future extensions, this just interprets the part that // is expected, and reject only those that don't carry // even the required data. LOGC(brlog.Error, log << CONID() << "EPE: Wrong size of the DROPREQ message: " << ctrlpkt.getLength() << " - expected >=" << sizeof(expected_t)); return; } int32_t msgno = ctrlpkt.getMsgSeq(m_bPeerRexmitFlag); // Check for rogue message if (msgno == SRT_MSGNO_NONE) { LOGC(brlog.Warn, log << CONID() << "ROGUE DROPREQ detected with #NONE - fallback: fixing to #CONTROL"); msgno = SRT_MSGNO_CONTROL; } const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; { CUniqueSync rcvtscc (m_RecvLock, m_RcvTsbPdCond); // With both TLPktDrop and TsbPd enabled, a message always consists only of one packet. // It will be dropped as too late anyway. Not dropping it from the receiver buffer // in advance reduces false drops if the packet somehow manages to arrive. // Still remove the record from the loss list to cease further retransmission requests. if (!m_bTLPktDrop || !m_bTsbPd) { ScopedLock rblock(m_RcvBufferLock); const int iDropCnt = m_pRcvBuffer->dropMessage(dropdata[0], dropdata[1], msgno, CRcvBuffer::KEEP_EXISTING); if (iDropCnt > 0) { ScopedLock lg (m_StatsLock); const steady_clock::time_point tnow = steady_clock::now(); string why; if (frequentLogAllowed(FREQLOGFA_RCV_DROPPED, tnow, (why))) { LOGC(brlog.Warn, log << CONID() << "RCV-DROPPED " << iDropCnt << " packet(s), seqno range %" << dropdata[0] << "-%" << dropdata[1] << ", #" << ctrlpkt.getMsgSeq(m_bPeerRexmitFlag) << " (SND DROP REQUEST). " << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE else { LOGC(brlog.Warn, log << "SUPPRESSED: RCV-DROPPED LOG: " << why); } #endif // Estimate dropped bytes from average payload size. const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); } } // NOTE: // PREVIOUSLY done: notify on rcvtscc if m_bTsbPd // OLD COMMENT: // // When the drop request was received, it means that there are // // packets for which there will never be ACK sent; if the TSBPD thread // // is currently in the ACK-waiting state, it may never exit. // // Likely this is no longer necessary because: // // 1. If there's a play-ready packet, either in cell 0 or // after a drop, TSBPD is sleeping timely, up to the play-time // of the next ready packet (and the drop region concerned here // is still a gap to be skipped for this). // 2. TSBPD sleeps forever when the buffer is empty, in which case // it will be always woken up on packet reception (see m_bTsbPdNeedsWakeup). // And dropping won't happen in that case anyway. Note that the drop // request will not drop packets that are already received. // 3. TSBPD sleeps forever when the API call didn't extract the // data that are ready to play. This isn't a problem if nothing // except the API call would wake it up. } dropFromLossLists(dropdata[0], dropdata[1]); // If dropping ahead of the current largest sequence number, // move the recv seq number forward. if ((CSeqNo::seqcmp(dropdata[0], CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) && (CSeqNo::seqcmp(dropdata[1], m_iRcvCurrSeqNo) > 0)) { HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" << dropdata[0] << "-" << dropdata[1] << " <-- set as current seq"); m_iRcvCurrSeqNo = dropdata[1]; } else { HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" << dropdata[0] << "-" << dropdata[1] << " current %" << m_iRcvCurrSeqNo); } } void CUDT::processCtrlShutdown(const CPacket& ctrlpkt) { const uint32_t* data = (const uint32_t*) ctrlpkt.m_pcData; const size_t data_len = ctrlpkt.getLength() / 4; int reason = 0; // This condition should be ALWAYS satisfied, it's only // a sanity check before reading the data. Versions that // do not support close reason will simply send 0 here because // it's the padding 0 that is provided in every command // that is not expected to carry any "body". It is acceptable // that the old versions simply send 0 here, but then you // can't have the UNKNOWN value in any of close reason // fields because it means that it wasn't set. if (data_len > 0) { reason = data[0]; } if (reason == 0) { setPeerCloseReason(SRT_CLS_FALLBACK); } m_bShutdown = true; m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 60; // This does the same as it would happen on connection timeout, // just we know about this state prematurely thanks to this message. updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! } void CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ", value=" << ctrlpkt.getExtendedType()); // This has currently two roles in SRT: // - HSv4 (legacy) handshake // - refreshed KMX (initial KMX is done still in the HS process in HSv5) const bool understood = processSrtMsg(&ctrlpkt); // CAREFUL HERE! This only means that this update comes from the UMSG_EXT // message received, REGARDLESS OF WHAT IT IS. This version doesn't mean // the handshake version, but the reason of calling this function. // // Fortunately, the only messages taken into account in this function // are HSREQ and HSRSP, which should *never* be interchanged when both // parties are HSv5. if (understood) { if (ctrlpkt.getExtendedType() == SRT_CMD_HSREQ || ctrlpkt.getExtendedType() == SRT_CMD_HSRSP) { updateAfterSrtHandshake(HS_VERSION_UDT4); } } else { updateCC(TEV_CUSTOM, EventVariant(&ctrlpkt)); } } void CUDT::processCtrl(const CPacket &ctrlpkt) { // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime = currtime; // XXX Requires lock m_RecvAckLock? HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=@" << ctrlpkt.id()); switch (ctrlpkt.getType()) { case UMSG_ACK: // 010 - Acknowledgement processCtrlAck(ctrlpkt, currtime); break; case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement processCtrlAckAck(ctrlpkt, currtime); break; case UMSG_LOSSREPORT: // 011 - Loss Report processCtrlLossReport(ctrlpkt); break; case UMSG_CGWARNING: // 100 - Delay Warning // One way packet delay is increasing, so decrease the sending rate m_tdSendInterval = (m_tdSendInterval.load() * 1125) / 1000; // XXX Note as interesting fact: this is only prepared for handling, // but nothing in the code is sending this message. Probably predicted // for a custom congctl. There's a predicted place to call it under // UMSG_ACKACK handling, but it's commented out. break; case UMSG_KEEPALIVE: // 001 - Keep-alive processKeepalive(ctrlpkt, currtime); break; case UMSG_HANDSHAKE: // 000 - Handshake processCtrlHS(ctrlpkt); break; case UMSG_SHUTDOWN: // 101 - Shutdown processCtrlShutdown(ctrlpkt); break; case UMSG_DROPREQ: // 111 - Msg drop request processCtrlDropReq(ctrlpkt); break; case UMSG_PEERERROR: // 1000 - An error has happened to the peer side // int err_type = packet.getAddInfo(); // currently only this error is signalled from the peer side // if recvfile() fails (e.g., due to disk fail), blocked sendfile/send should return immediately // giving the app a chance to fix the issue m_bPeerHealth = false; break; case UMSG_EXT: // 0x7FFF - reserved and user defined messages processCtrlUserDefined(ctrlpkt); break; default: break; } } void CUDT::updateSrtRcvSettings() { // CHANGED: we need to apply the tsbpd delay only for socket TSBPD. // For Group TSBPD the buffer will have to deliver packets always on request // by sequence number, although the buffer will have to solve all the TSBPD // things internally anyway. Extracting by sequence number means only that // the packet can be retrieved from the buffer before its time to play comes // (unlike in normal situation when reading directly from socket), however // its time to play shall be properly defined. ScopedLock lock(m_RecvLock); // XXX ALSO: m_RcvBufferLock ??? // NOTE: remember to also update synchronizeWithGroup() if more settings are updated here. m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); if (m_bTsbPd || m_bGroupTsbPd) { m_pRcvBuffer->setTsbPdMode(m_tsRcvPeerStartTime, false, milliseconds_from(m_iTsbPdDelay_ms)); HLOGC(cnlog.Debug, log << "AFTER HS: Set Rcv TsbPd mode" << (m_bGroupTsbPd ? " (AS GROUP MEMBER)" : "") << ": delay=" << (m_iTsbPdDelay_ms / 1000) << "." << (m_iTsbPdDelay_ms % 1000) << "s RCV START: " << FormatTime(m_tsRcvPeerStartTime).c_str()); } else { HLOGC(cnlog.Debug, log << CONID() << "AFTER HS: Rcv TsbPd mode not set"); } } void CUDT::updateSrtSndSettings() { if (m_bPeerTsbPd) { /* We are TsbPd sender */ // XXX Check what happened here. // m_iPeerTsbPdDelay_ms = m_CongCtl->getSndPeerTsbPdDelay();// + ((m_iSRTT + (4 * m_iRTTVar)) / 1000); /* * For sender to apply Too-Late Packet Drop * option (m_bTLPktDrop) must be enabled and receiving peer shall support it */ HLOGC(cnlog.Debug, log << "AFTER HS: Set Snd TsbPd mode " << (m_bPeerTLPktDrop ? "with" : "without") << " TLPktDrop: delay=" << (m_iPeerTsbPdDelay_ms/1000) << "." << (m_iPeerTsbPdDelay_ms%1000) << "s START TIME: " << FormatTime(m_stats.tsStartTime).c_str()); } else { HLOGC(cnlog.Debug, log << CONID() << "AFTER HS: Snd TsbPd mode not set"); } } void CUDT::updateAfterSrtHandshake(int hsv) { HLOGC(cnlog.Debug, log << CONID() << "updateAfterSrtHandshake: HS version " << hsv); // This is blocked from being run in the "app reader" version because here // every socket does its TsbPd independently, just the sequence screwup is // done and the application reader sorts out packets by sequence numbers, // but only when they are signed off by TsbPd. // The only possibility here is one of these two: // - Agent is RESPONDER and it receives HSREQ. // - Agent is INITIATOR and it receives HSRSP. // // In HSv4, INITIATOR is sender and RESPONDER is receiver. // In HSv5, both are sender AND receiver. // // This function will be called only ONCE in this // instance, through either HSREQ or HSRSP. #if HVU_ENABLE_HEAVY_LOGGING #if SRT_ENABLE_BONDING string grpspec; if (m_parent->m_GroupOf) { SharedLock glock (uglobal().m_GlobControlLock); grpspec = m_parent->m_GroupOf ? fmtcat(" group=$", m_parent->m_GroupOf->id()) : string(); } #else const char* grpspec = ""; #endif HLOGC(cnlog.Debug, log << CONID() << "updateAfterSrtHandshake: version=" << m_ConnRes.m_iVersion << " side=" << s_hs_side[m_SrtHsSide] << grpspec); #endif if (hsv > HS_VERSION_UDT4) { updateSrtRcvSettings(); updateSrtSndSettings(); } else if (m_SrtHsSide == HSD_INITIATOR) { // HSv4 INITIATOR is sender updateSrtSndSettings(); } else { // HSv4 RESPONDER is receiver updateSrtRcvSettings(); } } // XXX During sender buffer refax turn this into either returning // a sequence number or move it to the sender buffer facility. // [[using locked (m_RecvAckLock)]] pair CUDT::getCleanRexmitOffset() { // This function is required to look into the loss list and // drop all sequences that are already revoked from the sender // buffer, send the drop request if needed, and return the sender // buffer offset for the next packet to retransmit, or -1 if // there is no retransmission candidate at the moment. for (;;) { // REPEATABLE because we might need to drop this scheduled seq. // Pick up the first sequence. int32_t seq = m_pSndLossList->popLostSeq(); if (seq == SRT_SEQNO_NONE) { // No loss reports, we are clear here. return std::make_pair(SRT_SEQNO_NONE, -1); } int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); if (offset < 0) { // XXX Likely that this will never be executed because if the upper // sequence is not in the sender buffer, then most likely the loss // was completely ignored. LOGC(qslog.Error, log << CONID() << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() " << seq << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset << ". Continue, request DROP"); int32_t oldest_outdated_seq = seq; // We have received the first outdated sequence. // Loop on to find out more. // Interrupt on the first non-outdated sequence // or when the loss list gets empty. for (;;) { seq = m_pSndLossList->popLostSeq(); if (seq == SRT_SEQNO_NONE) { offset = -1; // set it because this will be returned. break; } offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); if (offset >= 0) break; } // Now we have 'seq' that is either non-sequence // or a valid sequence. We can safely exit with this // value from this branch. We just need to manage // the drop report. // No matter whether this is right or not (maybe the attack case should be // considered, and some LOSSREPORT flood prevention), send the drop request // to the peer. int32_t seqpair[2] = { oldest_outdated_seq, CSeqNo::decseq(m_iSndLastDataAck) }; HLOGC(qslog.Debug, log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" << seqpair[0] << " - " << seqpair[1] << ") (" << (-offset) << " packets)"); // See interpretation in processCtrlDropReq(). We don't know the message number, // so we request that the drop be exclusively sequence number based. int32_t msgno = SRT_MSGNO_CONTROL; sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); } // We have exit anyway with the right sequence or no sequence. if (seq == SRT_SEQNO_NONE) return std::make_pair(SRT_SEQNO_NONE, -1); // For the right sequence though check also the right time. const steady_clock::time_point time_now = steady_clock::now(); if (!checkRexmitRightTime(offset, time_now)) { // Ignore this, even though valid, sequence, and pick up // the next from the list. continue; } // Ok, we have what we needed one way or another. return std::make_pair(seq, offset); } } // [[using locked (m_RecvAckLock)]] bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time) { // If not configured, the time is always right if (!m_bPeerNakReport || m_config.iRetransmitAlgo == 0) return true; const steady_clock::time_point time_nak = current_time - microseconds_from(m_iSRTT - 4 * m_iRTTVar); const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); if (tsLastRexmit >= time_nak) { #if HVU_ENABLE_HEAVY_LOGGING ostringstream last_rextime; if (is_zero(tsLastRexmit)) { last_rextime << "REXMIT FIRST TIME"; } else { last_rextime << "REXMITTED " << FormatDuration(current_time - tsLastRexmit) << " ago at " << FormatTime(tsLastRexmit); } HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << CSeqNo::incseq(m_iSndLastDataAck, offset) << " " << last_rextime.str() << ", RTT: ~" << FormatValue(m_iSRTT, 1000, "ms") << " <>" << FormatValue(m_iRTTVar, 1000, "") << " now=" << FormatTime(current_time)); #endif return false; } return true; } // [[using locked (m_RecvAckLock)]] int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, srt::sync::steady_clock::time_point& w_tsOrigin) { // REPEATABLE BLOCK (not a real loop) for (;;) { typedef CSndBuffer::DropRange DropRange; DropRange buffer_drop; w_packet.set_seqno(seqno); const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_tsOrigin), (buffer_drop)); if (payload == CSndBuffer::READ_DROP) { SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); HLOGC(qslog.Debug, log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #" << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " << buffer_drop.seqno[DropRange::END] << ")"); sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno)); // skip all dropped packets m_pSndLossList->removeUpTo(buffer_drop.seqno[DropRange::END]); m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); continue; } if (payload == CSndBuffer::READ_NONE) { LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << w_packet.seqno() << " NOT FOUND in the sender buffer"); return 0; } return payload; } } int CUDT::packLostData(CPacket& w_packet) { #ifdef SRT_ENABLE_MAXREXMITBW #ifdef SRT_ENABLE_RATE_MEASUREMENT #define IF_RATE_MEA(expr) expr #else #define IF_RATE_MEA(expr) #endif // XXX NOTE: If you refactor the sender buffer so that the random access // is possible, you might be able to extract the exact packet size to // check for enough tokens and consume them in one step. bool maxrexmitbw_enabled = m_config.llMaxRexmitBW >= 0; if (maxrexmitbw_enabled) { IF_RATE_MEA( IF_HEAVY_LOGGING(const int64_t iRexmitRateMeasured = m_SndRexmitMeasurement.rateBytes()); ) size_t granted_size = m_zSndAveragePacketSize; size_t len = granted_size + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); if (!m_SndRexmitShaper.enoughTokens(len)) { HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED pkt est/len=" << len << " exceeds " << m_SndRexmitShaper.ntokens() << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) ); return 0; } HLOGC(qslog.Debug, log << "REXMIT-SH: ALLOWED pkt est/len=" << len << " allowed, budget " << m_SndRexmitShaper.ntokens() << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) ); } #undef IF_RATE_MEA #endif // Original sending time of the packet, to be stamped after filling. steady_clock::time_point tsOrigin; { // protect m_iSndLastDataAck from updating by ACK processing ScopedLock ackguard(m_RecvAckLock); int32_t seqno; int offset; // Get the first sequence for retransmission, bypassing and taking care of // those that are in the forgotten region, as well as required to be rejected. Tie(seqno, offset) = getCleanRexmitOffset(); if (seqno == SRT_SEQNO_NONE) return 0; // Extract the packet from the sender buffer that is mapped to the expected sequence // number, bypassing and taking care of those that are decided to be dropped. const int payload = extractCleanRexmitPacket(seqno, offset, (w_packet), (tsOrigin)); if (payload <= 0) return 0; } HLOGC(qslog.Debug, log << CONID() << "packed REXMIT packet %" << w_packet.seqno() << " size=" << w_packet.getLength() << " - still " << m_pSndLossList->getLossLength() << " LOSS ENTRIES left"); // POST-EXTRACTION length fixes. // The packet has been ecrypted, thus the authentication tag is expected to be stored // in the SND buffer as well right after the payload. if (m_CryptoControl.getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) { w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX); } #ifdef SRT_ENABLE_MAXREXMITBW if (maxrexmitbw_enabled) { // Token consumption will only happen when the retransmission // effectively happens. // XXX NOTE: In version 1.6.0 use the IP-version dependent value for UDP_HDR_SIZE size_t network_size = w_packet.getLength() + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); m_SndRexmitShaper.consumeTokens(network_size); HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens()); } #endif enterCS(m_StatsLock); m_stats.sndr.sentRetrans.count(w_packet.getLength()); leaveCS(m_StatsLock); // Despite the contextual interpretation of packet.m_iMsgNo around // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. // So, set here the rexmit flag if the peer understands it. if (m_bPeerRexmitFlag) { w_packet.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT); } setDataPacketTS(w_packet, tsOrigin); #ifdef SRT_ENABLE_MAXREXMITBW // XXX OLD rexmit measurement // m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength()); #endif // XXX Consider calculating the total packet length once you have a possibility // to get the in-connection used IP version. #ifdef SRT_ENABLE_RATE_MEASUREMENT m_SndRexmitMeasurement.dataUpdate(1, w_packet.getLength()); HLOGC(bslog.Debug, log << "RateMeasurement: REXMIT, pkt-size=" << w_packet.getLength() << " - COLLECTED pkts=" << m_SndRexmitMeasurement.m_nInstaPackets << " bytes=" << m_SndRexmitMeasurement.m_nInstaBytes); #endif return w_packet.getLength(); } #if SRT_DEBUG_TRACE_SND class snd_logger { typedef sync::steady_clock steady_clock; public: snd_logger() {} ~snd_logger() { ScopedLock lck(m_mtx); m_fout.close(); } struct { typedef sync::steady_clock steady_clock; long long usElapsed; steady_clock::time_point tsNow; int usSRTT; int usRTTVar; int msSndBuffSpan; int msTimespanTh; int msNextUniqueToSend; long long usElapsedLastDrop; bool canRexmit; int iPktSeqno; bool isRetransmitted; } state; void trace() { using namespace sync; ScopedLock lck(m_mtx); create_file(); m_fout << state.usElapsed << ","; m_fout << state.usSRTT << ","; m_fout << state.usRTTVar << ","; m_fout << state.msSndBuffSpan << ","; m_fout << state.msTimespanTh << ","; m_fout << state.msNextUniqueToSend << ","; m_fout << state.usElapsedLastDrop << ","; m_fout << state.canRexmit << ","; m_fout << state.iPktSeqno << ','; m_fout << state.isRetransmitted << '\n'; m_fout.flush(); } private: void print_header() { m_fout << "usElapsed,usSRTT,usRTTVar,msSndBuffTimespan,msTimespanTh,msNextUniqueToSend,usDLastDrop,canRexmit,sndPktSeqno,isRexmit"; m_fout << "\n"; } void create_file() { if (m_fout.is_open()) return; m_start_time = sync::steady_clock::now(); std::string str_tnow = sync::FormatTimeSys(m_start_time); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part while (str_tnow.find(':') != std::string::npos) { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "snd_trace_" + str_tnow + ".csv"; m_fout.open(fname, std::ofstream::out); if (!m_fout) std::cerr << "IPE: Failed to open " << fname << "!!!\n"; print_header(); } private: sync::Mutex m_mtx; std::ofstream m_fout; sync::steady_clock::time_point m_start_time; }; snd_logger g_snd_logger; #endif // SRT_DEBUG_TRACE_SND void CUDT::setPacketTS(CPacket& p, const time_point& ts) { enterCS(m_StatsLock); const time_point tsStart = m_stats.tsStartTime; leaveCS(m_StatsLock); p.set_timestamp(makeTS(ts, tsStart)); } void CUDT::setDataPacketTS(CPacket& p, const time_point& ts) { enterCS(m_StatsLock); const time_point tsStart = m_stats.tsStartTime; leaveCS(m_StatsLock); if (!m_bPeerTsbPd) { // If TSBPD is disabled, use the current time as the source (timestamp using the sending time). p.set_timestamp(makeTS(steady_clock::now(), tsStart)); return; } // TODO: Might be better for performance to ensure this condition is always false, and just use SRT_ASSERT here. if (ts < tsStart) { p.set_timestamp(makeTS(steady_clock::now(), tsStart)); LOGC(qslog.Warn, log << CONID() << "setPacketTS: reference time=" << FormatTime(ts) << " is in the past towards start time=" << FormatTime(tsStart) << " - setting NOW as reference time for the data packet"); return; } // Use the provided source time for the timestamp. p.set_timestamp(makeTS(ts, tsStart)); } bool CUDT::isRegularSendingPriority() { // In order to have regular packets take precedence over retransmitted: // - SRTO_TLPKTDROP = true // - SRTO_MESSAGEAPI = true // NOTE: // - tlpktdrop is ignored in stream mode // - messageapi without tlpktdrop is possible in non-live message mode // - Live mode without tlpktdrop is possible and in this mode also the // retransmitted packets should have priority. if (!m_bPeerTLPktDrop || !m_config.bMessageAPI) return false; // XXX NOTE: the current solution is simple - the regular packet takes precedence // over a retransmitted packet, if there is at least one such packet already // scheduled. // // This probably isn't the most wanted solution, some more elaborate condition // might be better in some situations. For example, it should be acceptable // that a packet that has very little time to be recovered is sent before a // regular packet that has still STT + Latency time to deliver. The regular // packets should still be favored, but not necessarily at the expense of // dismissing a recovery chance that wouldn't endanger the delivery of a regular // packet. Criteria might be various, for example, the number of scheduled // packets and their late delivery time might be taken into account. const time_point tsNextPacket = m_pSndBuffer->peekNextOriginal(); if (tsNextPacket != time_point()) { // Have regular packet and we decided they have a priority. HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED because regular packets have priority"); return true; } return false; } #ifdef SRT_ENABLE_MAXREXMITBW static sync::steady_clock::duration optimisticSingleTripDuration(int rttval, int rttvar) { int lowrtt = rttval - rttvar; if (lowrtt < 0) // bullshit? lowrtt = rttval; int stt = lowrtt/2; // Make sure that burst period has at least this value sync::steady_clock::duration stt_td = sync::microseconds_from(stt); // Still, prevent from setting enormous STT values if (stt_td < sync::seconds_from(4)) { return stt_td; } return sync::steady_clock::duration(); } #endif void CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) { const time_point tnow SRT_ATR_UNUSED = steady_clock::now(); #if SRT_DEBUG_TRACE_SND const int buffdelay_ms = count_milliseconds(m_pSndBuffer->getBufferingDelay(tnow)); // If there is a small loss, still better to retransmit. If timespan is already big, // then consider sending original packets. const int threshold_ms_min = (2 * m_iSRTT + 4 * m_iRTTVar + COMM_SYN_INTERVAL_US) / 1000; const int msNextUniqueToSend = count_milliseconds(tnow - tsNextPacket) + m_iPeerTsbPdDelay_ms; const time_point start_time = m_stats.tsStartTime; g_snd_logger.state.tsNow = tnow; g_snd_logger.state.usElapsed = count_microseconds(tnow - start_time); g_snd_logger.state.usSRTT = m_iSRTT; g_snd_logger.state.usRTTVar = m_iRTTVar; g_snd_logger.state.msSndBuffSpan = buffdelay_ms; g_snd_logger.state.msTimespanTh = threshold_ms_min; g_snd_logger.state.msNextUniqueToSend = msNextUniqueToSend; g_snd_logger.state.usElapsedLastDrop = count_microseconds(tnow - m_tsLastTLDrop); g_snd_logger.state.canRexmit = can_rexmit; #endif #ifdef SRT_ENABLE_MAXREXMITBW /* OLD rate estimator; CODE for packLostData()! m_SndRexmitRate.addSample(tnow, 0, 0); // Update the estimation. const int64_t iRexmitRateBps = m_SndRexmitRate.getRate(); if (iRexmitRateLimitBps >= 0 && iRexmitRateBps > iRexmitRateLimitBps) { // Too many retransmissions, so don't send anything. // TODO: When to wake up next time? return false; } */ const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW; if (iRexmitRateLimitBps >= 0) { int b4_tokens SRT_ATR_UNUSED = m_SndRexmitShaper.ntokens(); m_SndRexmitShaper.setBitrate(iRexmitRateLimitBps); duration stt_td = optimisticSingleTripDuration(m_iSRTT, m_iRTTVar); if (stt_td != duration()) m_SndRexmitShaper.setMinimumBurstPeriod(stt_td); m_SndRexmitShaper.tick(tnow); int a4_tokens SRT_ATR_UNUSED = m_SndRexmitShaper.ntokens(); HLOGC(qslog.Debug, log << "REXMIT-SW: tick tokens updated: " << b4_tokens << " -> " << a4_tokens << "/" << m_SndRexmitShaper.maxTokens() << " period=" << FormatDuration(m_SndRexmitShaper.burstPeriod())); } #endif } bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNetworkInterface& w_src_addr) { int payload = 0; bool probe = false; bool new_packet_packed = false; const steady_clock::time_point enter_time = steady_clock::now(); w_nexttime = enter_time; if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime) { m_tdSendTimeDiff = m_tdSendTimeDiff.load() + (enter_time - m_tsNextSendTime); } ScopedLock connectguard(m_ConnectionLock); // If a closing action is done simultaneously, then // m_bOpened should already be false, and it's set // just before releasing this lock. // // If this lock is caught BEFORE the closing could // start the dissolving process, this process will // not be started until this function is finished. if (!m_bOpened) return false; // This function returns true if there is a regular packet candidate waiting // for being sent and sending this packet has a prioriry over retransmission candidate. if (!isRegularSendingPriority()) { payload = packLostData((w_packet)); #ifdef SRT_ENABLE_MAXREXMITBW if (payload == 0) { HLOGC(qslog.Debug, log << "REXMIT-SH: no rexmit required, remain " << m_SndRexmitShaper.ntokens() << " tokens"); } #endif } else { HLOGC(qslog.Debug, log << "REXMIT: retransmission SUPERSEDED, proceed with regular candidate"); } // Updates the data that will be next used in packLostData() in next calls. updateSenderMeasurements(payload != 0); IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) if (payload > 0) { IF_HEAVY_LOGGING(reason = "reXmit"); } else if (m_PacketFilter && // XXX m_iSndCurrSeqNo requires locking m_RcvAckLock m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_CryptoControl.getSndCryptoFlags(), (w_packet))) { HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); payload = (int) w_packet.getLength(); IF_HEAVY_LOGGING(reason = "filter"); // Stats ScopedLock lg(m_StatsLock); m_stats.sndr.sentFilterExtra.count(1); } else { if (!packUniqueData(w_packet)) { m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = steady_clock::duration(); return false; } #if SRT_ENABLE_MAXREXMITBW if (m_zSndAveragePacketSize > 0) { m_zSndAveragePacketSize = avg_iir<16>(m_zSndAveragePacketSize, w_packet.getLength()); } else { m_zSndAveragePacketSize = w_packet.getLength(); } m_zSndMaxPacketSize = std::max(m_zSndMaxPacketSize, w_packet.getLength()); #endif new_packet_packed = true; // every 16 (0xF) packets, a packet pair is sent if ((w_packet.seqno() & PUMASK_SEQNO_PROBE) == 0) probe = true; payload = (int) w_packet.getLength(); IF_HEAVY_LOGGING(reason = "normal"); } w_packet.set_id(m_PeerID); // Set the destination SRT socket ID. if (new_packet_packed && m_PacketFilter) { HLOGC(qslog.Debug, log << CONID() << "filter: Feeding packet for source clip"); m_PacketFilter.feedSource((w_packet)); } #if HVU_ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, log << CONID() << "packData: " << reason << " packet seq=" << w_packet.seqno() << " (ACK=" << m_iSndLastAck << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif // Fix keepalive m_tsLastSndTime.store(enter_time); considerLegacySrtHandshake(steady_clock::time_point()); // WARNING: TEV_SEND is the only event that is reported from // the CSndQueue::worker thread. All others are reported from // CRcvQueue::worker. If you connect to this signal, make sure // that you are aware of prospective simultaneous access. updateCC(TEV_SEND, EventVariant(&w_packet)); // XXX This was a blocked code also originally in UDT. Probably not required. // Left untouched for historical reasons. // Might be possible that it was because of that this is send from // different thread than the rest of the signals. // m_pSndTimeWindow->onPktSent(w_packet.timestamp()); enterCS(m_StatsLock); m_stats.sndr.sent.count(payload); if (new_packet_packed) m_stats.sndr.sentUnique.count(payload); leaveCS(m_StatsLock); IF_HEAVY_LOGGING(std::string nexttime_reason); const duration sendint = m_tdSendInterval; if (probe) { // sends out probing packet pair m_tsNextSendTime = enter_time; // Sending earlier, need to adjust the pace later on. m_tdSendTimeDiff = m_tdSendTimeDiff.load() - sendint; probe = false; IF_HEAVY_LOGGING(nexttime_reason = "probe-pair"); } else { #if SRT_BUSY_WAITING m_tsNextSendTime = enter_time + m_tdSendInterval.load(); IF_HEAVY_LOGGING(nexttime_reason = "busy-waiting"); #else const duration sendbrw = m_tdSendTimeDiff; if (sendbrw >= sendint) { // Send immediately m_tsNextSendTime = enter_time; // ATOMIC NOTE: this is the only thread that // modifies this field m_tdSendTimeDiff = sendbrw - sendint; IF_HEAVY_LOGGING(nexttime_reason = "immediate(undertime " + FormatDurationAuto(m_tdSendTimeDiff) + ")"); } else { m_tsNextSendTime = enter_time + (sendint - sendbrw); m_tdSendTimeDiff = duration(); IF_HEAVY_LOGGING(nexttime_reason = "delayed(remain " + FormatDurationAuto(sendint - sendbrw) + ")"); } #endif } HLOGC(qslog.Debug, log << "packData: src.addr=" << m_SourceAddr.str() << " next.snd.time=" << FormatTime(m_tsNextSendTime) << " reason=" << nexttime_reason); w_src_addr = m_SourceAddr; w_nexttime = m_tsNextSendTime; return payload >= 0; // XXX shouldn't be > 0 ? == 0 is only when buffer range exceeded. } bool CUDT::packUniqueData(CPacket& w_packet) { int current_sequence_number; // reflexing variable int kflg; time_point tsOrigin; int pld_size; { ScopedLock lkrack (m_RecvAckLock); // Check the congestion/flow window limit const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); const int flightspan = getFlightSpan(); if (cwnd <= flightspan) { HLOGC(qslog.Debug, log << CONID() << "packUniqueData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_iCongestionWindow << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); return false; } // XXX Here it's needed to set kflg to msgno_bitset in the block stored in the // send buffer. This should be somehow avoided, the crypto flags should be set // together with encrypting, and the packet should be sent as is, when rexmitting. // It would be nice to research as to whether CSndBuffer::Block::m_iMsgNoBitset field // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. kflg = m_CryptoControl.getSndCryptoFlags(); int pktskipseqno = 0; pld_size = m_pSndBuffer->readData((w_packet), (tsOrigin), kflg, (pktskipseqno)); if (pktskipseqno) { // Some packets were skipped due to TTL expiry. m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo, pktskipseqno); HLOGC(qslog.Debug, log << "packUniqueData: reading skipped " << pktskipseqno << " seq up to %" << m_iSndCurrSeqNo << " due to TTL expiry"); } if (pld_size == 0) { HLOGC(qslog.Debug, log << "packUniqueData: nothing extracted from the buffer"); return false; } // A CHANGE. The sequence number is currently added to the packet // when scheduling, not when extracting. This is a inter-migration form, // only override extraction sequence with scheduling sequence in group mode. m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo); current_sequence_number = m_iSndCurrSeqNo; } #if SRT_ENABLE_BONDING // Fortunately the group itself isn't being accessed. if (!m_bClosing && m_parent->m_GroupOf) { const int packetspan = CSeqNo::seqoff(current_sequence_number, w_packet.seqno()); if (packetspan > 0) { // After increasing by 1, but being previously set as ISN-1, this should be == ISN, // if this is the very first packet to send. if (current_sequence_number == m_iISN) { // This is the very first packet to be sent; so there's nothing in // the sending buffer yet, and therefore we are in a situation as just // after connection. No packets in the buffer, no packets are sent, // no ACK to be awaited. We can screw up all the variables that are // initialized from ISN just after connection. LOGC(qslog.Note, log << CONID() << "packUniqueData: Fixing EXTRACTION sequence " << current_sequence_number << " from SCHEDULING sequence " << w_packet.seqno() << " for the first packet: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); } else { // There will be a serious data discrepancy between the agent and the peer. LOGC(qslog.Error, log << CONID() << "IPE: packUniqueData: Fixing EXTRACTION sequence " << current_sequence_number << " from SCHEDULING sequence " << w_packet.seqno() << " in the middle of transition: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); } // Additionally send the drop request to the peer so that it // won't stupidly request the packets to be retransmitted. // Don't do it if the difference isn't positive or exceeds the threshold. int32_t seqpair[2]; seqpair[0] = current_sequence_number; seqpair[1] = CSeqNo::decseq(w_packet.seqno()); const int32_t no_msgno = 0; LOGC(qslog.Debug, log << CONID() << "packUniqueData: Sending DROPREQ: SEQ: " << seqpair[0] << " - " << seqpair[1] << " (" << packetspan << " packets)"); sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); // In case when this message is lost, the peer will still get the // UMSG_DROPREQ message when the agent realizes that the requested // packet are not present in the buffer (preadte the send buffer). // Override extraction sequence with scheduling sequence. ScopedLock ackguard(m_RecvAckLock); m_iSndCurrSeqNo = w_packet.seqno(); m_iSndLastAck = w_packet.seqno(); m_iSndLastDataAck = w_packet.seqno(); m_iSndLastFullAck = w_packet.seqno(); m_iSndLastAck2 = w_packet.seqno(); } else if (packetspan < 0) { LOGC(qslog.Error, log << CONID() << "IPE: packData: SCHEDULING sequence " << w_packet.seqno() << " is behind of EXTRACTION sequence " << current_sequence_number << ", dropping this packet: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); // XXX: Probably also change the socket state to broken? return false; } } else #endif { HLOGC(qslog.Debug, log << CONID() << "packUniqueData: Applying EXTRACTION sequence " << current_sequence_number << " over SCHEDULING sequence " << w_packet.seqno() << " for socket not in group:" << " DIFF=" << CSeqNo::seqcmp(current_sequence_number, w_packet.seqno()) << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); // Do this always when not in a group. w_packet.set_seqno(current_sequence_number); } // Set missing fields before encrypting the packet, because those fields might be used for encryption. w_packet.set_id(m_PeerID); // Destination SRT Socket ID setDataPacketTS(w_packet, tsOrigin); if (kflg != EK_NOENC) { // Note that the packet header must have a valid seqno set, as it is used as a counter for encryption. // Other fields of the data packet header (e.g. timestamp, destination socket ID) are not used for the counter. // Cypher may change packet length! if (m_CryptoControl.encrypt((w_packet)) != ENCS_CLEAR) { // Encryption failed //>>Add stats for crypto failure LOGC(qslog.Warn, log << CONID() << "ENCRYPT FAILED - packet won't be sent, size=" << pld_size); return false; } checkSndKMRefresh(); } #if SRT_DEBUG_TRACE_SND g_snd_logger.state.iPktSeqno = w_packet.seqno(); g_snd_logger.state.isRetransmitted = w_packet.getRexmitFlag(); g_snd_logger.trace(); #endif #ifdef SRT_ENABLE_RATE_MEASUREMENT HLOGC(bslog.Debug, log << "RATE-MEASUREMENT: REGULAR, pkt-size=" << w_packet.getLength()); m_SndRegularMeasurement.dataUpdate(1, w_packet.getLength()); #endif return true; } // This is a close request, but called from the handler of the // buffer overflow in live mode. void CUDT::processClose() { uint32_t res[1] = { SRT_CLS_OVERFLOW }; sendCtrl(UMSG_SHUTDOWN, NULL, res, sizeof res); m_bShutdown = true; m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 60; HLOGP(smlog.Debug, "processClose: (closing=true) sent message and set flags"); if (m_bTsbPd) { HLOGP(smlog.Debug, "processClose: lock-and-signal TSBPD"); CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); } // Signal the sender and recver if they are waiting for data. releaseSynch(); // Unblock any call so they learn the connection_broken error uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true); HLOGP(smlog.Debug, "processClose: triggering timer event to spread the bad news"); CGlobEvent::triggerEvent(); } void CUDT::sendLossReport(const std::vector > &loss_seqs) { vector seqbuffer; seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i) { if (i->first == i->second) { seqbuffer.push_back(i->first); HLOGC(qrlog.Debug, log << "lost packet " << i->first << ": sending LOSSREPORT"); } else { seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST); seqbuffer.push_back(i->second); HLOGC(qrlog.Debug, log << "lost packets " << i->first << "-" << i->second << " (" << (1 + CSeqNo::seqcmp(i->second, i->first)) << " packets): sending LOSSREPORT"); } } if (!seqbuffer.empty()) { sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], (int) seqbuffer.size()); } } bool CUDT::overrideSndSeqNo(int32_t seq) { // This function is intended to be called from the socket // group management functions to synchronize the sequence in // all sockes in the bonding group. THIS sequence given // here is the sequence TO BE STAMPED AT THE EXACTLY NEXT // sent payload. Therefore, screw up the ISN to exactly this // value, and the send sequence to the value one less - because // the m_iSndCurrSeqNo is increased by one immediately before // stamping it to the packet. // This function can only be called: // - from the operation on an idle socket in the socket group // - IMMEDIATELY after connection established and BEFORE the first payload // - The corresponding socket at the peer side must be also // in this idle state! ScopedLock cg (m_RecvAckLock); // Both the scheduling and sending sequences should be fixed. // The new sequence normally should jump over several sequence numbers // towards what is currently in m_iSndCurrSeqNo. // Therefore it's not allowed that: // - the jump go backward: backward packets should be already there // - the jump go forward by a value larger than half the period: DISCREPANCY. const int diff = SeqNo(seq) - SeqNo(m_iSndCurrSeqNo); if (diff < 0 || diff > CSeqNo::m_iSeqNoTH) { LOGC(gslog.Error, log << CONID() << "IPE: Overriding with seq %" << seq << " DISCREPANCY against current %" << m_iSndCurrSeqNo << " and next sched %" << m_iSndNextSeqNo << " - diff=" << diff); return false; } int dbytes; const int dpkts SRT_ATR_UNUSED = m_pSndBuffer->dropAll((dbytes)); enterCS(m_StatsLock); m_stats.sndr.dropped.count(dbytes);; leaveCS(m_StatsLock); m_pSndLossList->removeUpTo(CSeqNo::decseq(seq)); // The peer will have to do the same, as a reaction on perceived // packet loss. When it recognizes that this initial screwing up // has happened, it should simply ignore the loss and go on. // ISN isn't being changed here - it doesn't make much sense now. setInitialSndSeq(seq); // m_iSndCurrSeqNo will be most likely lower than m_iSndNextSeqNo because // the latter is ahead with the number of packets already scheduled, but // not yet sent. HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo << " (unchanged) dropped-pkts=" << dpkts); return true; } int CUDT::checkLazySpawnTsbPdThread() { const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; if (!need_tsbpd) return 0; ScopedLock lock(m_RcvTsbPdStartupLock); if (!m_RcvTsbPdThread.joinable()) { if (m_bClosing) // Check m_bClosing to protect join() in CUDT::releaseSync(). return -1; HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread"); #if HVU_ENABLE_HEAVY_LOGGING ofmtbufstream buf; // Take the last 2 ciphers from the socket ID. string s = fmts(m_SocketID, fmtc().fillzero().width(2)); buf << "SRT:TsbPd:@" << s.substr(s.size()-2, 2); const string thname = buf.str(); #else const string thname = "SRT:TsbPd"; #endif if (!StartThread(m_RcvTsbPdThread, CUDT::tsbpd, this, thname)) return -1; } return 0; } CUDT::time_point CUDT::getPktTsbPdTime(void*, const CPacket& packet) { return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } SRT_ATR_UNUSED static const char *const s_rexmitstat_str[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; // [[using locked(m_RcvBufferLock)]] int CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) { bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added w_new_inserted = false; const int32_t bufseq = m_pRcvBuffer->getStartSeqNo(); // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', // the one that came in the input of this function. for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end() && !m_bBroken; ++unitIt) { CUnit * u = *unitIt; CPacket &rpkt = u->m_Packet; const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; const bool retransmitted = pktrexmitflag == 1; bool adding_successful = true; const int32_t bufidx = CSeqNo::seqoff(bufseq, rpkt.seqno()); IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); // bufidx < 0: the packet is in the past for the buffer // seqno <% m_iRcvLastAck : the sequence may be within the buffer, // but if so, it is in the acknowledged-but-not-retrieved area. // NOTE: if we have a situation when there are any packets in the // acknowledged area, but they aren't retrieved, this area DOES NOT // contain any losses. So a packet in this area is at best a duplicate. // In case when a loss would be abandoned (TLPKTDROP), there must at // some point happen to be an empty first cell in the buffer, followed // somewhere by a valid packet. If this state is achieved at some point, // the acknowledgement sequence should be equal to the beginning of the // buffer. Then, when TSBPD decides to drop these initial empty cells, // we'll have: (m_iRcvLastAck <% buffer->getStartSeqNo()) - and in this // case (bufidx < 0) condition will be satisfied also for this case. // // The only case when bufidx > 0, but packet seq is <% m_iRcvLastAck // is when the packet sequence is within the initial contiguous area, // which never contains losses, so discarding this packet does not // discard a loss coverage, even if this were past ACK. if (bufidx < 0 || CSeqNo::seqcmp(rpkt.seqno(), m_iRcvLastAck) < 0) { time_point pts = getPktTsbPdTime(NULL, rpkt); enterCS(m_StatsLock); const double bltime = (double) CountIIR( uint64_t(m_stats.traceBelatedTime) * 1000, count_microseconds(steady_clock::now() - pts), 0.2); m_stats.traceBelatedTime = bltime / 1000.0; m_stats.rcvr.recvdBelated.count(rpkt.getLength()); leaveCS(m_StatsLock); HLOGC(qrlog.Debug, log << CONID() << "RECEIVED: %" << rpkt.seqno() << " bufidx=" << bufidx << " (BELATED/" << s_rexmitstat_str[pktrexmitflag] << ") with ACK %" << m_iRcvLastAck << " FLAGS: " << rpkt.MessageFlagStr()); continue; } if (bufidx >= int(m_pRcvBuffer->capacity())) { // This is already a sequence discrepancy. Probably there could be found // some way to make it continue reception by overriding the sequence and // make a kinda TLKPTDROP, but there has been found no reliable way to do this. if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) { // Only in live mode. In File mode this shall not be possible // because the sender should stop sending in this situation. // In Live mode this means that there is a gap between the // lowest sequence in the empty buffer and the incoming sequence // that exceeds the buffer size. Receiving data in this situation // is no longer possible and this is a point of no return. LOGC(qrlog.Error, log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION." " %" << rpkt.seqno() << " buffer=(%" << bufseq << ":%" << m_iRcvCurrSeqNo // -1 = size to last index << "+%" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) << "), " << (m_pRcvBuffer->capacity() - bufidx + 1) << " past max. Reception no longer possible. REQUESTING TO CLOSE."); return -2; } else { LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.seqno() << ", insert offset " << bufidx << ". " << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) ); return -1; } } CRcvBuffer::InsertInfo info = m_pRcvBuffer->insert(u); // Remember this value in order to CHECK if there's a need // to request triggering TSBPD in case when TSBPD is in the // state of waiting forever and wants to know if there's any // possible time to wake up known earlier than that. // Note that in case of the "builtin group reader" (its own // buffer), there's no need to do it here because it has also // its own TSBPD thread. if (info.result == CRcvBuffer::InsertInfo::INSERTED) { // This may happen multiple times in the loop, so update only if earlier. if (w_next_tsbpd == time_point() || w_next_tsbpd > info.first_time) w_next_tsbpd = info.first_time; w_new_inserted = true; } const int buffer_add_result = int(info.result); if (buffer_add_result < 0) { // The insert() result is -1 if at the position evaluated from this packet's // sequence number there already is a packet. // So this packet is "redundant". IF_HEAVY_LOGGING(exc_type = "UNACKED"); adding_successful = false; } else { IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); excessive = false; if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) { // TODO: reset and restore the timestamp if TSBPD is disabled. // Reset retransmission flag (must be excluded from GCM auth tag). u->m_Packet.setRexmitFlag(false); const EncryptionStatus rc = m_CryptoControl.decrypt((u->m_Packet)); u->m_Packet.setRexmitFlag(retransmitted); // Recover the flag. if (rc != ENCS_CLEAR) { adding_successful = false; IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); // If TSBPD is disabled, then SRT either operates in buffer mode, of in message API without a restriction // of a single message packet. In that case just dropping a packet is not enough. // In message mode the whole message has to be dropped. // However, when decryption fails the message number in the packet cannot be trusted. // The packet has to be removed from the RCV buffer based on that pkt sequence number, // and the sequence number itself must go into the RCV loss list. // See issue ##2626. SRT_ASSERT(m_bTsbPd); // Drop the packet from the receiver buffer. // The packet was added to the buffer based on the sequence number, therefore sequence number should be used to drop it from the buffer. // A drawback is that it would prevent a valid packet with the same sequence number, if it happens to arrive later, to end up in the buffer. const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); const steady_clock::time_point tnow = steady_clock::now(); ScopedLock lg(m_StatsLock); m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * rpkt.getLength(), iDropCnt)); m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); string why; if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) { LOGC(qrlog.Warn, log << CONID() << "Decryption failed (seqno %" << u->m_Packet.getSeqNo() << "), dropped " << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "." << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE else { LOGC(qrlog.Warn, log << "SUPPRESSED: Decryption failed LOG: " << why); } #endif } } else if (m_CryptoControl.kmState().rcv == SRT_KM_S_SECURED) { // Unencrypted packets are not allowed. const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); const steady_clock::time_point tnow = steady_clock::now(); ScopedLock lg(m_StatsLock); m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt* rpkt.getLength(), iDropCnt)); m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); string why; if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) { LOGC(qrlog.Warn, log << CONID() << "Packet not encrypted (seqno %" << u->m_Packet.getSeqNo() << "), dropped " << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "."); } } } if (adding_successful) { ScopedLock statslock(m_StatsLock); m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); } #if HVU_ENABLE_HEAVY_LOGGING std::ostringstream expectspec; if (excessive) expectspec << "EXCESSIVE(" << exc_type << ")"; else expectspec << "ACCEPTED"; std::ostringstream bufinfo; if (m_pRcvBuffer) { // XXX Fix this when the end of contiguous region detection is added. const int ackidx = std::max(0, CSeqNo::seqoff(m_pRcvBuffer->getStartSeqNo(), m_iRcvLastAck)); bufinfo << " BUF.s=" << m_pRcvBuffer->capacity() << " avail=" << (int(m_pRcvBuffer->capacity()) - ackidx) << " buffer=(%" << bufseq << ":%" << m_iRcvCurrSeqNo // -1 = size to last index << "+%" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) << ")"; } // Empty buffer info in case of groupwise receiver. // There's no way to obtain this information here. LOGC(qrlog.Debug, log << CONID() << "RECEIVED: %" << rpkt.seqno() << bufinfo.str() << " RSL=" << expectspec.str() << " SN=" << s_rexmitstat_str[pktrexmitflag] << " FLAGS: " << rpkt.MessageFlagStr()); #endif // Decryption should have made the crypto flags EK_NOENC. // Otherwise it's an error. if (adding_successful) { HLOGC(qrlog.Debug, log << CONID() << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); if (CSeqNo::seqcmp(rpkt.seqno(), CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.seqno()); w_srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); HLOGC(qrlog.Debug, log << "pkt/LOSS DETECTED: %" << seqlo << " - %" << seqhi); } } // Update the current largest sequence number that has been received. // Or it is a retransmitted packet, remove it from receiver loss list. if (CSeqNo::seqcmp(rpkt.seqno(), m_iRcvCurrSeqNo) > 0) { m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received } else { unlose(rpkt); // was BELATED or RETRANSMITTED w_was_sent_in_order &= 0 != pktrexmitflag; } } return 0; } int CUDT::processData(CUnit* in_unit) { if (m_bClosing) return -1; CPacket &packet = in_unit->m_Packet; // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; m_tsLastRspTime.store(steady_clock::now()); // XXX Requires lock m_RecvAckLock // We are receiving data, start tsbpd thread if TsbPd is enabled if (-1 == checkLazySpawnTsbPdThread()) { return -1; } const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; const bool retransmitted = pktrexmitflag == 1; #if HVU_ENABLE_HEAVY_LOGGING string rexmit_reason; #endif if (retransmitted) { // This packet was retransmitted enterCS(m_StatsLock); m_stats.rcvr.recvdRetrans.count(packet.getLength()); leaveCS(m_StatsLock); #if HVU_ENABLE_HEAVY_LOGGING // Check if packet was retransmitted on request or on ack timeout // Search the sequence in the loss record. rexmit_reason = " by "; ScopedLock lock(m_RcvLossLock); if (!m_pRcvLossList->find(packet.seqno(), packet.seqno())) rexmit_reason += "BLIND"; else rexmit_reason += "NAKREPORT"; #endif } #if HVU_ENABLE_HEAVY_LOGGING { steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. steady_clock::time_point pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); steady_clock::time_point ets = pts - tsbpddelay; HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo() // XXX FIX IT. OTS should represent the original sending time, but it's relative. //<< " OTS=" << FormatTime(packet.getMsgTimeStamp()) << " ETS=" << FormatTime(ets) << " PTS=" << FormatTime(pts)); } #endif updateCC(TEV_RECEIVE, EventVariant(&packet)); ++m_iPktCount; const int pktsz = (int) packet.getLength(); // Update time information // XXX Note that this adds the byte size of a packet // of which we don't yet know as to whether this has // carried out some useful data or some excessive data // that will be later discarded. // FIXME: before adding this on the rcv time window, // make sure that this packet isn't going to be // effectively discarded, as repeated retransmission, // for example, burdens the link, but doesn't better the speed. m_RcvTimeWindow.onPktArrival(pktsz); // Probe the packet pair if needed. // Conditions and any extra data required for the packet // this function will extract and test as needed. const bool unordered = CSeqNo::seqcmp(packet.seqno(), m_iRcvCurrSeqNo) <= 0; // Retransmitted and unordered packets do not provide expected measurement. // We expect the 16th and 17th packet to be sent regularly, // otherwise measurement must be rejected. m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); enterCS(m_StatsLock); m_stats.rcvr.recvd.count(pktsz); leaveCS(m_StatsLock); loss_seqs_t filter_loss_seqs; loss_seqs_t srt_loss_seqs; vector incoming; bool was_sent_in_order = true; // If the peer doesn't understand REXMIT flag, send rexmit request // always immediately. int initial_loss_ttl = 0; if (m_bPeerRexmitFlag) initial_loss_ttl = m_iReorderTolerance; // Track packet loss in statistics early, because a packet filter (e.g. FEC) might recover it later on, // supply the missing packet(s), and the loss will no longer be visible for the code that follows. if (packet.getMsgSeq(m_bPeerRexmitFlag) != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing { const int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.seqno()); // Difference between these two sequence numbers is expected to be: // 0 - duplicated last packet (theory only) // 1 - subsequent packet (alright) // <0 - belated or recovered packet // >1 - jump over a packet loss (loss = seqdiff-1) if (diff > 1) { const int loss = diff - 1; // loss is all that is above diff == 1 ScopedLock lg(m_StatsLock); const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvr.lost.count(stats::BytesPackets(loss * avgpayloadsz, (uint32_t) loss)); HLOGC(qrlog.Debug, log << CONID() << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " << CSeqNo::decseq(packet.seqno()) << "]"); } if (diff > 0) { // Record if it was further than latest m_iRcvCurrPhySeqNo = packet.seqno(); } } // [[using locked()]]; // (NOTHING locked) #if SRT_ENABLE_BONDING // Switch to RUNNING even if there was a discrepancy, unless // it was long way forward. // XXX Important: This code is in the dead function defaultPacketArrival // but normally it should be called here regardless if the packet was // accepted or rejected because if it was belated it may result in a // "runaway train" problem as the IDLE links are being updated the base // reception sequence pointer stating that this link is not receiving. if (m_parent->m_GroupOf) { ExclusiveLock protect_group_existence (uglobal().m_GlobControlLock); groups::SocketData* gi = m_parent->m_GroupMemberData; // This check is needed as after getting the lock the socket // could be potentially removed. It is however granted that as long // as gi is non-NULL iterator, the group does exist and it does contain // this socket as member (that is, 'gi' cannot be a dangling pointer). if (gi != NULL) { if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, though PENDING is unlikely { HLOGC(qrlog.Debug, log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] << " -> RUNNING."); gi->rcvstate = SRT_GST_RUNNING; } else { HLOGC(qrlog.Debug, log << CONID() << "processData: IN-GROUP rcv state transition NOT DONE - state:" << srt_log_grp_state[gi->rcvstate]); } } } #endif // NULL time by default time_point next_tsbpd_avail; bool new_inserted = false; if (m_PacketFilter) { // Stuff this data into the filter m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); HLOGC(qrlog.Debug, log << CONID() << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) << " loss to report, " << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" : "REPORT ONLY THOSE")); } else { // Stuff in just one packet that has come in. incoming.push_back(in_unit); } { // Start of offset protected section // Prevent TsbPd thread from modifying Ack position while adding data // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() UniqueLock recvbuf_acklock(m_RcvBufferLock); // Needed for possibly check for needsQuickACK. const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.seqno(), m_pRcvBuffer->getStartSeqNo()) < 0); const int res = handleSocketPacketReception(incoming, (new_inserted), (next_tsbpd_avail), (was_sent_in_order), (srt_loss_seqs)); if (res == -2) { // This is a scoped lock with AckLock, but for the moment // when processClose() is called this lock must be taken out, // otherwise this will cause a deadlock. We don't need this // lock anymore, and at 'return' it will be unlocked anyway. recvbuf_acklock.unlock(); processClose(); return -1; } if (res == -1) { return -1; } if (!srt_loss_seqs.empty()) { ScopedLock lock(m_RcvLossLock); HLOGC(qrlog.Debug, log << CONID() << "processData: RECORDING LOSS: " << Printable(srt_loss_seqs) << " tolerance=" << initial_loss_ttl); for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) { m_pRcvLossList->insert(i->first, i->second); if (initial_loss_ttl) { // The LOSSREPORT will be sent after initial_loss_ttl. m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); } } } // This is moved earlier after introducing filter because it shouldn't // be executed in case when the packet was rejected by the receiver buffer. // However now the 'excessive' condition may be true also in case when // a truly non-excessive packet has been received, just it has been temporarily // stored for better times by the filter module. This way 'excessive' is also true, // although the old condition that a packet with a newer sequence number has arrived // or arrived out of order may still be satisfied. if (!incoming_belated && was_sent_in_order) { // Basing on some special case in the packet, it might be required // to enforce sending ACK immediately (earlier than normally after // a given period). if (m_CongCtl->needsQuickACK(packet)) { m_tsNextACKTime.store(steady_clock::now()); } } if (!new_inserted) { return -1; } } // End of recvbuf_acklock if (m_bClosing) { // RcvQueue worker thread can call processData while closing (or close while processData) // This race condition exists in the UDT design but the protection against TsbPd thread // (with AckLock) and decryption enlarged the probability window. // Application can crash deep in decrypt stack since crypto context is deleted in close. // RcvQueue worker thread will not necessarily be deleted with this connection as it can be // used by others (socket multiplexer). return -1; } // 1. This is set to true in case when TSBPD during the last check // has seen no packet candidate to ever deliver, hence it needs // an update on that. Note that this is also false if TSBPD thread // isn't running. // 2. If next_tsbpd_avail is set, it means that in the buffer there is // a new packet that precedes the previously earliest available packet. // This means that if TSBPD was sleeping up to the time of this earliest // delivery (after drop), this time we have received a packet to be delivered // earlier than that, so we need to notify TSBPD immediately so that it // updates this itself, not sleep until the previously set time. // The meaning of m_bTsbPdNeedsWakeup: // - m_bTsbPdNeedsWakeup is set by TSBPD thread and means that it wishes to be woken up // on every received packet. Hence we signal always if a new packet was inserted. // - even if TSBPD doesn't wish to be woken up on every reception (because it sleeps // until the play time of the next deliverable packet), it will be woken up when // next_tsbpd_avail is set because it means this time is earlier than the time until // which TSBPD sleeps, so it must be woken up prematurely. It might be more performant // to simply update the sleeping end time of TSBPD, but there's no way to do it, so // we simply wake TSBPD up and count on that it will update its sleeping settings. // XXX Consider: as CUniqueSync locks m_RecvLock, it means that the next instruction // gets run only when TSBPD falls asleep again. Might be a good idea to record the // TSBPD end sleeping time - as an alternative to m_bTsbPdNeedsWakeup - and after locking // a mutex check this time again and compare it against next_tsbpd_avail; might be // that if this difference is smaller than "dirac" (could be hard to reliably compare // this time, unless it's set from this very value), there's no need to wake the TSBPD // thread because it will wake up on time requirement at the right time anyway. if (m_bTsbPd && ((m_bTsbPdNeedsWakeup && new_inserted) || next_tsbpd_avail != time_point())) { HLOGC(qrlog.Debug, log << "processData: will SIGNAL TSBPD for socket. WakeOnRecv=" << m_bTsbPdNeedsWakeup << " new_inserted=" << new_inserted << " next_tsbpd_avail=" << FormatTime(next_tsbpd_avail)); CUniqueSync tsbpd_cc(m_RecvLock, m_RcvTsbPdCond); tsbpd_cc.notify_all(); } if (incoming.empty()) { // Treat as excessive. This is when a filter cumulates packets // until the loss is rebuilt, or eats up a filter control packet return -1; } if (!srt_loss_seqs.empty()) { const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS; if (!initial_loss_ttl && report_recorded_loss) { HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } } // Separately report loss records of those reported by a filter. // ALWAYS report whatever has been reported back by a filter. Note that // the filter never reports anything when rexmit fallback level is ALWAYS or NEVER. // With ALWAYS only those are reported that were recorded here by SRT. // With NEVER, nothing is to be reported. if (!filter_loss_seqs.empty()) { HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); } // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. // PERFORMANCE CONSIDERATIONS: // This list is quite inefficient as a data type and finding the candidate to send UMSG_LOSSREPORT // is linear time. On the other hand, there are some special cases that are important for performance: // - only the first (plus some following) could have had TTL drown to 0 // - the only (little likely) possibility that the next-to-first record has TTL=0 is when there was // a loss range split (due to dropFromLossLists() of one sequence) // - first found record with TTL>0 means end of "ready to LOSSREPORT" records // So: // All you have to do is: // - start with first element and continue with next elements, as long as they have TTL=0 // If so, send the loss report and remove this element. // - Since the first element that has TTL>0, iterate until the end of container and decrease TTL. // // This will be efficient because the loop to increment one field (without any condition check) // can be quite well optimized. vector lossdata; { ScopedLock lg(m_RcvLossLock); // XXX There was a mysterious crash around m_FreshLoss. When the initial_loss_ttl is 0 // (that is, "belated loss report" feature is off), don't even touch m_FreshLoss. if (initial_loss_ttl && !m_FreshLoss.empty()) { deque::iterator i = m_FreshLoss.begin(); // Phase 1: take while TTL <= 0. // There can be more than one record with the same TTL, if it has happened before // that there was an 'unlost' (@c dropFromLossLists) sequence that has split one detected loss // into two records. for (; i != m_FreshLoss.end() && i->ttl <= 0; ++i) { HLOGC(qrlog.Debug, log << "Packet seq " << i->seq[0] << "-" << i->seq[1] << " (" << (CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1) << " packets) considered lost - sending LOSSREPORT"); addLossRecord(lossdata, i->seq[0], i->seq[1]); } // Remove elements that have been processed and prepared for lossreport. if (i != m_FreshLoss.begin()) { m_FreshLoss.erase(m_FreshLoss.begin(), i); i = m_FreshLoss.begin(); } if (m_FreshLoss.empty()) { HLOGP(qrlog.Debug, "NO MORE FRESH LOSS RECORDS."); } else { HLOGC(qrlog.Debug, log << "STILL " << m_FreshLoss.size() << " FRESH LOSS RECORDS, FIRST: " << i->seq[0] << "-" << i->seq[1] << " (" << (1 + CSeqNo::seqoff(i->seq[0], i->seq[1])) << ") TTL: " << i->ttl); } // Phase 2: rest of the records should have TTL decreased. for (; i != m_FreshLoss.end(); ++i) --i->ttl; } } if (!lossdata.empty()) { sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int) lossdata.size()); } // was_sent_in_order means either of: // - packet was sent in order (first if branch above) // - packet was sent as old, but was a retransmitted packet if (m_bPeerRexmitFlag && was_sent_in_order) { ++m_iConsecOrderedDelivery; if (m_iConsecOrderedDelivery >= 50) { m_iConsecOrderedDelivery = 0; if (m_iReorderTolerance > 0) { m_iReorderTolerance--; enterCS(m_StatsLock); m_stats.traceReorderDistance--; leaveCS(m_StatsLock); HLOGC(qrlog.Debug, log << "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to " << m_iReorderTolerance); } } } return 0; } #if SRT_ENABLE_BONDING // NOTE: this is updated from the value of m_iRcvLastAck, // which might be past the buffer and potentially cause setting // the value to the last received and re-requiring retransmission. // Worst case is that there could be a few packets to tear the transmission // even more (as there will be likely no time to recover them), but // if the transmission was already torn in the previously active link // this shouldn't be a problem that these packets won't be recovered // after activating the second link, although will be retried this way. void CUDT::updateIdleLinkFrom(CUDT* source) { int bufseq; { ScopedLock lg (source->m_RcvBufferLock); bufseq = source->m_pRcvBuffer->getStartSeqNo(); } ScopedLock lg (m_RecvLock); if (!m_pRcvBuffer->empty()) { HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": receiver buffer not empty"); return; } int32_t new_last_rcv = source->m_iRcvLastAck; if (CSeqNo::seqcmp(new_last_rcv, bufseq) < 0) { // Emergency check whether the last ACK was behind the // buffer. This may happen when TSBPD dropped empty cells. // This may cause that the newly activated link may derive // these empty cells which will never be recovered. new_last_rcv = bufseq; } // if (new_last_rcv <=% m_iRcvCurrSeqNo) if (CSeqNo::seqcmp(new_last_rcv, m_iRcvCurrSeqNo) <= 0) { // Reject the change because that would shift the reception pointer backwards. HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": backward setting rejected: %" << m_iRcvCurrSeqNo << " -> %" << new_last_rcv); return; } HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID << " from @" << source->m_SocketID << ": %" << new_last_rcv); setInitialRcvSeq(new_last_rcv); } #endif /// This function is called when a packet has arrived, which was behind the current /// received sequence - that is, belated or retransmitted. Try to remove the packet /// from both loss records: the general loss record and the fresh loss record. /// /// Additionally, check - if supported by the peer - whether the "latecoming" packet /// has been sent due to retransmission or due to reordering, by checking the rexmit /// support flag and rexmit flag itself. If this packet was surely ORIGINALLY SENT /// it means that the current network connection suffers of packet reordering. This /// way try to introduce a dynamic tolerance by calculating the difference between /// the current packet reception sequence and this packet's sequence. This value /// will be set to the tolerance value, which means that later packet retransmission /// will not be required immediately, but only after receiving N next packets that /// do not include the lacking packet. /// The tolerance is not increased infinitely - it's bordered by iMaxReorderTolerance. /// This value can be set in options - SRT_LOSSMAXTTL. void CUDT::unlose(const CPacket &packet) { ScopedLock lg(m_RcvLossLock); int32_t sequence = packet.seqno(); m_pRcvLossList->remove(sequence); // Rest of this code concerns only the "belated lossreport" feature. bool has_increased_tolerance = false; bool was_reordered = false; if (m_bPeerRexmitFlag) { // If the peer understands the REXMIT flag, it means that the REXMIT flag is contained // in the PH_MSGNO field. // The packet is considered coming originally (just possibly out of order), if REXMIT // flag is NOT set. was_reordered = !packet.getRexmitFlag(); if (was_reordered) { HLOGC(qrlog.Debug, log << "received out-of-band packet %" << sequence); const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.seqno())); enterCS(m_StatsLock); m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance); leaveCS(m_StatsLock); if (seqdiff > m_iReorderTolerance) { const int new_tolerance = min(seqdiff, m_config.iMaxReorderTolerance); HLOGC(qrlog.Debug, log << "Belated by " << seqdiff << " seqs - Reorder tolerance " << (new_tolerance == m_iReorderTolerance ? "REMAINS with " : "increased to ") << new_tolerance); m_iReorderTolerance = new_tolerance; has_increased_tolerance = true; // Yes, even if reorder tolerance is already at maximum - this prevents decreasing tolerance. } } else { HLOGC(qrlog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence); } } else { HLOGC(qrlog.Debug, log << "received reXmitted or belated packet seq " << sequence << " (distinction not supported by peer)"); } // Don't do anything if "belated loss report" feature is not used. // In that case the FreshLoss list isn't being filled in at all, the // loss report is sent directly. // Note that this condition blocks two things being done in this function: // - remove given sequence from the fresh loss record // (in this case it's empty anyway) // - decrease current reorder tolerance based on whether packets come in order // (current reorder tolerance is 0 anyway) if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; int had_ttl = 0; if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl))) { HLOGC(qrlog.Debug, log << "sequence " << sequence << " removed from belated lossreport record"); } if (was_reordered) { m_iConsecOrderedDelivery = 0; if (has_increased_tolerance) { m_iConsecEarlyDelivery = 0; // reset counter } else if (had_ttl > 2) { ++m_iConsecEarlyDelivery; // otherwise, and if it arrived quite earlier, increase counter HLOGC(qrlog.Debug, log << "... arrived at TTL " << had_ttl << " case " << m_iConsecEarlyDelivery); // After 10 consecutive if (m_iConsecEarlyDelivery >= 10) { m_iConsecEarlyDelivery = 0; if (m_iReorderTolerance > 0) { m_iReorderTolerance--; enterCS(m_StatsLock); m_stats.traceReorderDistance--; leaveCS(m_StatsLock); HLOGC(qrlog.Debug, log << "... reached " << m_iConsecEarlyDelivery << " times - decreasing tolerance to " << m_iReorderTolerance); } } } // If hasn't increased tolerance, but the packet appeared at TTL less than 2, do nothing. } } void CUDT::dropFromLossLists(int32_t from, int32_t to) { ScopedLock lg(m_RcvLossLock); IF_HEAVY_LOGGING(bool autodetected = false); int32_t begin SRT_ATR_UNUSED; if (from == SRT_SEQNO_NONE) { begin = m_pRcvLossList->removeUpTo(to); IF_HEAVY_LOGGING(autodetected = true); } else { begin = from; m_pRcvLossList->remove(from, to); } #if HVU_ENABLE_HEAVY_LOGGING ostringstream range; if (begin == SRT_SEQNO_NONE) { range << "no"; } else { int off = CSeqNo::seqoff(begin, to); if (off < 0) { range << "WEIRD NUMBER OF"; } else { range << (off + 1); } } static const char* const beginwhere[2] = {"explicit", "detected"}; const char* const reqtype = (from == SRT_SEQNO_NONE) ? "TLPKTDROP" : "DROPREQ"; HLOGC(qrlog.Debug, log << CONID() << "DROP PER " << reqtype << " %" << begin << "[" << beginwhere[1*autodetected] << "]-" << to << " (" << range.str() << " packets)"); #endif if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; // All code below concerns only "belated lossreport" feature. // It's highly unlikely that this is waiting to send a belated UMSG_LOSSREPORT, // so treat it rather as a sanity check. // It's enough to check if the first element of the list starts with a sequence older than 'to'. // If not, just do nothing. size_t delete_index = 0; for (size_t i = 0; i < m_FreshLoss.size(); ++i) { CRcvFreshLoss::Emod result = m_FreshLoss[i].revoke(from, to); switch (result) { case CRcvFreshLoss::DELETE: delete_index = i + 1; // PAST THE END continue; // There may be further ranges that are included in this one, so check on. case CRcvFreshLoss::NONE: case CRcvFreshLoss::STRIPPED: break; // THIS BREAKS ONLY 'switch', not 'for'! case CRcvFreshLoss::SPLIT:; // This function never returns it. It's only a compiler shut-up. } break; // Now this breaks also FOR. } m_FreshLoss.erase(m_FreshLoss.begin(), m_FreshLoss.begin() + delete_index); // with delete_index == 0 will do nothing } // This function, as the name states, should bake a new cookie. int32_t CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int correction) { static unsigned int distractor = 0; unsigned int rollover = distractor + 10; for (;;) { // SYN cookie char clienthost[NI_MAXHOST]; char clientport[NI_MAXSERV]; getnameinfo(addr.get(), addr.size(), clienthost, sizeof(clienthost), clientport, sizeof(clientport), NI_NUMERICHOST | NI_NUMERICSERV); time_point start_time = m_stats.tsStartTime; int64_t timestamp = (count_microseconds(steady_clock::now() - start_time) / 60000000) + distractor + correction; // secret changes every one minute stringstream cookiestr; cookiestr << clienthost << ":" << clientport << ":" << timestamp; union { unsigned char cookie[16]; int32_t cookie_val; }; CMD5::compute(cookiestr.str().c_str(), cookie); if (cookie_val != current_cookie) return cookie_val; ++distractor; // This is just to make the loop formally breakable, // but this is virtually impossible to happen. if (distractor == rollover) return cookie_val; } } // XXX This is quite a mystery, why this function has a return value // and what the purpose for it was. There's just one call of this // function in the whole code and in that call the return value is // ignored. Actually this call happens in the CRcvQueue::worker thread, // where it makes a response for incoming UDP packet that might be // a connection request. Should any error occur in this process, there // is no way to "report error" that happened here. Basing on that // these values in original UDT code were quite like the values // for m_iReqType, they have been changed to URQ_* symbols, which // may mean that the intent for the return value was to send this // value back as a control packet back to the connector. // // This function is run when the CRcvQueue object is reading packets // from the multiplexer (@c CRcvQueue::worker_RetrieveUnit) and the // target socket ID is 0. // // XXX Make this function return EConnectStatus enum type (extend if needed), // and this will be directly passed to the caller. // [[using locked(m_RcvQueue.m_LSLock)]]; int CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) { // XXX ASSUMPTIONS: // [[using assert(packet.id() == 0)]] HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received a connection request"); // NOTE (IMPORTANT!!!) // // The current CUDT object represents a LISTENER SOCKET to which // the request was redirected from the receiver queue. if (m_bClosing) { m_RejectReason = SRT_REJ_CLOSE; HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... NOT. Rejecting because closing."); return m_RejectReason; } /* * Closing a listening socket only set bBroken * If a connect packet is received while closing it gets through * processing and crashes later. */ if (m_bBroken) { m_RejectReason = SRT_REJ_CLOSE; HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... NOT. Rejecting because broken."); return m_RejectReason; } // When CHandShake::m_iContentSize is used in log, the file fails to link! size_t exp_len = CHandShake::m_iContentSize; // NOTE!!! Old version of SRT code checks if the size of the HS packet // is EQUAL to the above CHandShake::m_iContentSize. // Changed to < exp_len because we actually need that the packet // be at least of a size for handshake, although it may contain // more data, depending on what's inside. if (packet.getLength() < exp_len) { m_RejectReason = SRT_REJ_ROGUE; HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len << ")"); return m_RejectReason; } // Don't know why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE. // It doesn't seem to make sense to deserialize it into the handshake structure if we are not // sure that the packet contains the handshake at all! if (!packet.isControl(UMSG_HANDSHAKE)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "processConnectRequest: the packet received as handshake is not a handshake message"); return m_RejectReason; } CHandShake hs; hs.load_from(packet.m_pcData, packet.getLength()); // XXX MOST LIKELY this hs should be now copied into m_ConnRes field, which holds // the handshake structure sent from the peer (no matter the role or mode). // This should simplify the createSrtHandshake() function which can this time // simply write the crafted handshake structure into m_ConnReq, which needs no // participation of the local handshake and passing it as a parameter through // newConnection() -> acceptAndRespond() -> createSrtHandshake(). This is also // required as a source of the peer's information used in processing in other // structures. int32_t cookie_val = bake(addr); HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: new cookie: " << fmt(cookie_val, hex)); // Remember the incoming destination address here and use it as a source // address when responding. It's not possible to record this address yet // because this happens still in the frames of the listener socket. Only // when processing switches to the newly spawned accepted socket can the // address be recorded in its m_SourceAddr field. CNetworkInterface use_source_addr = packet.udpDestAddr(); // REQUEST:INDUCTION. // Set a cookie, a target ID, and send back the same as // RESPONSE:INDUCTION. if (hs.m_iReqType == URQ_INDUCTION) { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received type=induction, sending back with cookie+socket"); // XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp // is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies // only the first 4 bytes. Moreover, it's dangerous on some platforms because the char // array need not be aligned to int32_t - changed to union in a hope that using int32_t // inside a union will enforce whole union to be aligned to int32_t. hs.m_iCookie = cookie_val; packet.set_id(hs.m_iID); // Ok, now's the time. The listener sets here the version 5 handshake, // even though the request was 4. This is because the old client would // simply return THE SAME version, not even looking into it, giving the // listener false impression as if it supported version 5. // // If the caller was really HSv4, it will simply ignore the version 5 in INDUCTION; // it will respond with CONCLUSION, but with its own set version, which is version 4. // // If the caller was really HSv5, it will RECOGNIZE this version 5 in INDUCTION, so // it will respond with version 5 when sending CONCLUSION. hs.m_iVersion = HS_VERSION_SRT1; // Additionally, set this field to a MAGIC value. This field isn't used during INDUCTION // by HSv4 client, HSv5 client can use it to additionally verify that this is a HSv5 listener. // In this field we also advertise the PBKEYLEN value. When 0, it's considered not advertised. hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_config.iSndCryptoKeyLen); bool whether SRT_ATR_UNUSED = m_config.iSndCryptoKeyLen != 0; HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); size_t size = packet.getLength(); hs.store_to((packet.m_pcData), (size)); setPacketTS(packet, steady_clock::now()); // Display the HS before sending it to peer HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (i): " << hs.show()); channel()->sendto(addr, packet, use_source_addr); return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code. } // Otherwise this should be REQUEST:CONCLUSION. // Should then come with the correct cookie that was // set in the above INDUCTION, in the HS_VERSION_SRT1 // should also contain extra data. if (!hs.valid()) { LOGC(cnlog.Error, log << CONID() << "processConnectRequest: ROGUE HS RECEIVED. Rejecting"); m_RejectReason = SRT_REJ_ROGUE; return SRT_REJ_ROGUE; } HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie..."); if (hs.m_iCookie != cookie_val) { cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie if (hs.m_iCookie != cookie_val) { m_RejectReason = SRT_REJ_RDVCOOKIE; HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ...wrong cookie " << fmt(cookie_val, hex) << ". Ignoring."); return m_RejectReason; } HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); } else { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); } SRTSOCKET id = hs.m_iID; // HANDSHAKE: The old client sees the version that does not match HS_VERSION_UDT4 (5). // In this case it will respond with URQ_ERROR_REJECT. Rest of the data are the same // as in the handshake request. When this message is received, the connector side should // switch itself to the version number HS_VERSION_UDT4 and continue the old way (that is, // continue sending URQ_INDUCTION, but this time with HS_VERSION_UDT4). bool accepted_hs = true; if (hs.m_iVersion == HS_VERSION_SRT1) { // No further check required. // The m_iType contains handshake extension flags. } else if (hs.m_iVersion == HS_VERSION_UDT4) { // In UDT, and so in older SRT version, the hs.m_iType field should contain // the socket type, although SRT only allowed this field to be UDT_DGRAM. // Older SRT version contained that value in a field, but now that this can // only contain UDT_DGRAM the field itself has been abandoned. // For the sake of any old client that reports version 4 handshake, interpret // this hs.m_iType field as a socket type and check if it's UDT_DGRAM. // Note that in HSv5 hs.m_iType contains extension flags. if (hs.m_iType != UDT_DGRAM) { m_RejectReason = SRT_REJ_ROGUE; accepted_hs = false; } } else { // Unsupported version // (NOTE: This includes "version=0" which is a rejection flag). m_RejectReason = SRT_REJ_VERSION; accepted_hs = false; } if (!accepted_hs) { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason << " MSG: " << srt_rejectreason_str(m_RejectReason)); // mismatch, reject the request hs.m_iReqType = URQFailure(m_RejectReason); size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); packet.set_id(id); setPacketTS((packet), steady_clock::now()); HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (e): " << hs.show()); channel()->sendto(addr, packet, use_source_addr); } else { // IMPORTANT!!! // If the newConnection() detects there is already a socket connection associated with the remote peer, // it returns the socket via `acpu`, and the `result` returned is 0. // Else if a new connection is successfully created, the conclusion handshake response // is sent by the function itself (it calls the acceptAndRespond(..)), the `acpu` remains null, the `result` is 1. int error = SRT_REJ_UNKNOWN; CUDT* acpu = NULL; int result = uglobal().newConnection(m_SocketID, addr, packet, (hs), (error), (acpu)); // This is listener - m_RejectReason need not be set // because listener has no functionality of giving the app // insight into rejected callers. // ---> // (global.) CUDTUnited::updateListenerMux // (new Socket.) CUDT::acceptAndRespond if (result == -1) { hs.m_iReqType = URQFailure(error); LOGC(cnlog.Warn, log << "processConnectRequest: rsp(REJECT): " << hs.m_iReqType << " - " << srt_rejectreason_str(error)); } // The `acpu` not NULL means connection exists, the `result` should be 0. It is not checked here though. // The `newConnection(..)` only sends response for newly created connection. // The connection already exists (no new connection has been created, no response sent). // Send the conclusion response manually here in case the peer has missed the first one. // The value `result` here should be 0. if (acpu) { // This is an existing connection, so the handshake is only needed // because of the rule that every handshake request must be covered // by the handshake response. It wouldn't be good to call interpretSrtHandshake // here because the data from the handshake have been already interpreted // and recorded. We just need to craft a response. HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: sending REPEATED handshake response req=" << RequestTypeStr(hs.m_iReqType)); // Rewrite already updated previously data in acceptAndRespond acpu->rewriteHandshakeData(acpu->m_PeerAddr, (hs)); uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; EConnectStatus conn = CONN_ACCEPT; if (hs.m_iVersion >= HS_VERSION_SRT1) { // Always attach extension. hs.m_extensionType = SRT_CMD_HSRSP; conn = acpu->craftKmResponse((kmdata), (kmdatasize)); } else { kmdatasize = 0; } if (conn != CONN_ACCEPT) return conn; packet.setLength(m_iMaxSRTPayloadSize); // XXX REQUIRES LOCK ON acpu->m_ConnectionLock. // Check clashes with m_LSLock! if (!acpu->createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (packet), (hs))) { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: rejecting due to problems in createSrtHandshake."); result = -1; // enforce fallthrough for the below condition! hs.m_iReqType = URQFailure(m_RejectReason == SRT_REJ_UNKNOWN ? int(SRT_REJ_IPE) : m_RejectReason.load()); } else { // Send the crafted handshake HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING (repeated) HS (a): " << hs.show()); acpu->addressAndSend((packet)); } } if (result == -1) { // The new connection failed // or the connection already existed, but manually sending the HS response above has failed. // HSv4: Send the SHUTDOWN message to the peer (see PR #2010) in order to disallow the peer to connect. // The HSv4 clients do not interpret the error handshake response correctly. // HSv5: Send a handshake with an error code (hs.m_iReqType set earlier) to the peer. if (hs.m_iVersion < HS_VERSION_SRT1) { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: HSv4 caller, sending SHUTDOWN after rejection with " << RequestTypeStr(hs.m_iReqType)); CPacket rsp; setPacketTS((rsp), steady_clock::now()); rsp.pack(UMSG_SHUTDOWN); rsp.set_id(m_PeerID); channel()->sendto(addr, rsp, use_source_addr); } else { HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: sending ABNORMAL handshake info req=" << RequestTypeStr(hs.m_iReqType)); size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); packet.setLength(size); packet.set_id(id); setPacketTS(packet, steady_clock::now()); HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (a): " << hs.show()); channel()->sendto(addr, packet, use_source_addr); } } } LOGC(cnlog.Debug, log << CONID() << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); return RejectReasonForURQ(hs.m_iReqType); } void CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) { if (lo == hi) lr.push_back(lo); else { lr.push_back(lo | LOSSDATA_SEQNO_RANGE_FIRST); lr.push_back(hi); } } int CUDT::checkACKTimer(const steady_clock::time_point &currtime) { int because_decision = BECAUSE_NO_REASON; if (currtime > m_tsNextACKTime.load() // ACK time has come // OR the number of sent packets since last ACK has reached // the congctl-defined value of ACK Interval // (note that none of the builtin congctls defines ACK Interval) || (m_CongCtl->ACKMaxPackets() > 0 && m_iPktCount >= m_CongCtl->ACKMaxPackets())) { // ACK timer expired or ACK interval is reached sendCtrl(UMSG_ACK); const steady_clock::duration ack_interval = m_CongCtl->ACKTimeout_us() > 0 ? microseconds_from(m_CongCtl->ACKTimeout_us()) : m_tdACKInterval; m_tsNextACKTime.store(currtime + ack_interval); m_iPktCount = 0; m_iLightACKCount = 1; because_decision = BECAUSE_ACK; } // Or the transfer rate is so high that the number of packets // have reached the value of SelfClockInterval * LightACKCount before // the time has come according to m_tsNextACKTime. In this case a "lite ACK" // is sent, which doesn't contain statistical data and nothing more // than just the ACK number. The "fat ACK" packets will be still sent // normally according to the timely rules. else if (m_iPktCount >= SELF_CLOCK_INTERVAL * m_iLightACKCount) { // send a "light" ACK sendCtrl(UMSG_ACK, NULL, NULL, SEND_LITE_ACK); ++m_iLightACKCount; because_decision = BECAUSE_LITEACK; } return because_decision; } int CUDT::checkNAKTimer(const steady_clock::time_point& currtime) { // XXX The problem with working NAKREPORT with SRT_ARQ_ONREQ // is not that it would be inappropriate, but because it's not // implemented. The reason for it is that the structure of the // loss list container (m_pRcvLossList) is such that it is expected // that the loss records are ordered by sequence numbers (so // that two ranges sticking together are merged in place). // Unfortunately in case of SRT_ARQ_ONREQ losses must be recorded // as before, but they should not be reported, until confirmed // by the filter. By this reason they appear often out of order // and for adding them properly the loss list container wasn't // prepared. This then requires some more effort to implement. if (!m_config.bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) return BECAUSE_NO_REASON; /* * m_config.bRcvNakReport enables NAK reports for SRT. * Retransmission based on timeout is bandwidth consuming, * not knowing what to retransmit when the only NAK sent by receiver is lost, * all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT). */ enterCS(m_RcvLossLock); const int loss_len = m_pRcvLossList->getLossLength(); leaveCS(m_RcvLossLock); SRT_ASSERT(loss_len >= 0); int debug_decision = BECAUSE_NO_REASON; if (loss_len > 0) { if (currtime <= m_tsNextNAKTime.load()) return BECAUSE_NO_REASON; // wait for next NAK time sendCtrl(UMSG_LOSSREPORT); debug_decision = BECAUSE_NAKREPORT; } m_tsNextNAKTime.store(currtime + m_tdNAKInterval); return debug_decision; } bool CUDT::checkExpTimer(const steady_clock::time_point& currtime, int check_reason SRT_ATR_UNUSED) { // VERY HEAVY LOGGING #if HVU_ENABLE_HEAVY_LOGGING & 1 static const char* const decisions [] = { "ACK", "LITE-ACK", "NAKREPORT" }; string decision = "NOTHING"; if (check_reason) { ostringstream decd; decision = ""; for (int i = 0; i < LAST_BECAUSE_BIT; ++i) { int flag = 1 << i; if (check_reason & flag) decd << decisions[i] << " "; } decision = decd.str(); } HLOGC(xtlog.Debug, log << CONID() << "checkTimer: ACTIVITIES PERFORMED: " << decision); #endif // In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class. // There's nothing in the original code that alters these values. // XXX lock on m_RecvAckLock ? steady_clock::time_point next_exp_time; if (m_CongCtl->RTO()) { next_exp_time = m_tsLastRspTime.load() + microseconds_from(m_CongCtl->RTO()); } else { steady_clock::duration exp_timeout = microseconds_from(m_iEXPCount * (m_iSRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US); if (exp_timeout < (m_iEXPCount * m_tdMinExpInterval)) exp_timeout = m_iEXPCount * m_tdMinExpInterval; next_exp_time = m_tsLastRspTime.load() + exp_timeout; } if (currtime <= next_exp_time && !m_bBreakAsUnstable) return false; // ms -> us const int PEER_IDLE_TMO_US = m_config.iPeerIdleTimeout_ms * 1000; // Haven't received any information from the peer, is it dead?! // timeout: at least 16 expirations and must be greater than 5 seconds time_point last_rsp_time = m_tsLastRspTime.load(); if (m_bBreakAsUnstable || ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) && (currtime - last_rsp_time > microseconds_from(PEER_IDLE_TMO_US)))) { setAgentCloseReason(SRT_CLS_PEERIDLE); // // Connection is broken. // UDT does not signal any information about this instead of to stop quietly. // Application will detect this when it calls any UDT methods next time. // HLOGC(xtlog.Debug, log << CONID() << "CONNECTION EXPIRED after " << FormatDuration(currtime - last_rsp_time) << " - BREAKING"); m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 30; // update snd U list to remove this socket m_pMuxer->updateSendFast(m_parent); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! return true; } HLOGC(xtlog.Debug, log << CONID() << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed=" << (count_microseconds(currtime - last_rsp_time)) << "/" << (+PEER_IDLE_TMO_US) << "us"); ++m_iEXPCount; /* * (keepalive fix) * duB: * It seems there is confusion of the direction of the Response here. * lastRspTime is supposed to be when receiving (data/ctrl) from peer * as shown in processCtrl and processData, * Here we set because we sent something? * * Disabling this code that prevent quick reconnection when peer disappear */ // Reset last response time since we've just sent a heart-beat. // (fixed) m_tsLastRspTime = currtime_tk; return false; } void CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) { // Check if HSv4 should be retransmitted, and if KM_REQ should be resent if the side is INITIATOR. checkSndTimers(); // There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT. // // LATEREXMIT is only used with FileCC. // The RTO is triggered when some time has passed since the last ACK from // the receiver, while there is still some unacknowledged data in the sender's buffer, // and the loss list is empty at the moment of RTO (nothing to retransmit yet). // // FASTREXMIT is only used with LiveCC. // The RTO is triggered if the receiver is not configured to send periodic NAK reports, // when some time has passed since the last ACK from the receiver, // while there is still some unacknowledged data in the sender's buffer. // // In case the above conditions are met, the unacknowledged packets // in the sender's buffer will be added to the SND loss list and retransmitted. // { ScopedLock ack_lock(m_RecvAckLock); const uint64_t rtt_syn = (m_iSRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US); const uint64_t exp_int_us = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US); if (currtime <= (m_tsLastRspAckTime + microseconds_from(exp_int_us))) return; } // If there is no unacknowledged data in the sending buffer, // then there is nothing to retransmit. if (m_pSndBuffer->getCurrBufSize() <= 0) return; const bool is_laterexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_LATEREXMIT; // FileCC const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT; // LiveCC // If the receiver will send periodic NAK reports, then FASTREXMIT (live) is inactive. // TODO: Probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off. if (is_fastrexmit && m_bPeerNakReport) return; // Schedule a retransmission IF: // - there are packets in flight (getFlightSpan() > 0); // - in case of LATEREXMIT (File Mode): the sender loss list is empty // (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track). // - in case of FASTREXMIT (Live Mode): the RTO (rtt_syn) was triggered, therefore // schedule unacknowledged packets for retransmission regardless of the loss list emptiness. if ((!is_laterexmit || m_pSndLossList->getLossLength() == 0)) { ScopedLock acklock(m_RecvAckLock); // Protect packet retransmission if (getFlightSpan() > 0) { // Sender: Insert all the packets sent after last received acknowledgement into the sender loss list. // Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list const int32_t csn = m_iSndCurrSeqNo; const int num = m_pSndLossList->insert(m_iSndLastAck, csn); if (num > 0) { enterCS(m_StatsLock); m_stats.sndr.lost.count(num); leaveCS(m_StatsLock); HLOGC(xtlog.Debug, log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT") << " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " (" << CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)"); } } } { ScopedLock ack_lock(m_RecvAckLock); ++m_iReXmitCount; } const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT; updateCC(TEV_CHECKTIMER, EventVariant(stage)); // schedule sending if not scheduled already m_pMuxer->updateSendNormal(m_parent); } void CUDT::checkTimers() { // update CC parameters updateCC(TEV_CHECKTIMER, EventVariant(TEV_CHT_INIT)); const steady_clock::time_point currtime = steady_clock::now(); // This is a very heavy log, unblock only for temporary debugging! #if 0 HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=+" << FormatDuration(m_tsNextACKTime.load() - steady_clock::now()) << " AckInterval=" << FormatDuration(m_tdACKInterval) << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount); #endif #ifdef SRT_ENABLE_RATE_MEASUREMENT m_SndRegularMeasurement.pickup(currtime); m_SndRexmitMeasurement.pickup(currtime); #endif // Check if it is time to send ACK int debug_decision = checkACKTimer(currtime); // Check if it is time to send a loss report debug_decision |= checkNAKTimer(currtime); // Check if the connection is expired if (checkExpTimer(currtime, debug_decision)) return; // Check if FAST or LATE packet retransmission is required checkRexmitTimer(currtime); if (currtime > m_tsLastSndTime.load() + microseconds_from(COMM_KEEPALIVE_PERIOD_US)) { sendCtrl(UMSG_KEEPALIVE); #if SRT_ENABLE_BONDING // if (m_parent->m_GroupOf) <-- this is only an extra mutex-preventing check, but it's racy. { SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { // Pass socket ID because it's about changing group socket data m_parent->m_GroupOf->internalKeepalive(m_parent->m_GroupMemberData); // NOTE: GroupLock is unnecessary here because the only data read and // modified is the target of the iterator from m_GroupMemberData. The // iterator will be valid regardless of any container modifications. } } #endif HLOGP(xtlog.Debug, "KEEPALIVE"); } } void CUDT::updateBrokenConnection() { HLOGC(smlog.Debug, log << "updateBrokenConnection: setting closing=true and taking out epoll events"); m_bClosing = true; releaseSynch(); // app can call any UDT API to learn the connection_broken error uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); CGlobEvent::triggerEvent(); } void CUDT::completeBrokenConnectionDependencies(int errorcode) { int token = -1; #if SRT_ENABLE_BONDING bool pending_broken = false; { SharedLock guard_group_existence (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { ScopedLock lock_group (*m_parent->m_GroupOf->exp_groupLock()); token = m_parent->m_GroupMemberData->token; if (m_parent->m_GroupMemberData->sndstate == SRT_GST_PENDING) { HLOGC(gmlog.Debug, log << CONID() << "updateBrokenConnection: a pending link was broken - will be removed"); pending_broken = true; } else { HLOGC(gmlog.Debug, log << CONID() << "updateBrokenConnection: state=" << CUDTGroup::StateStr(m_parent->m_GroupMemberData->sndstate) << " a used link was broken - not closing automatically"); } m_parent->m_GroupMemberData->sndstate = SRT_GST_BROKEN; m_parent->m_GroupMemberData->rcvstate = SRT_GST_BROKEN; } } #endif if (m_cbConnectHook) { CALLBACK_CALL(m_cbConnectHook, m_SocketID, errorcode, m_PeerAddr.get(), token); } #if SRT_ENABLE_BONDING { // Lock GlobControlLock in order to make sure that // the state if the socket having the group and the // existence of the group will not be changed during // the operation. The attempt of group deletion will // have to wait until this operation completes. ExclusiveLock lock(uglobal().m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) { // Bound to one call because this requires locking pg->updateFailedLink(); } // Sockets that never succeeded to connect must be deleted // explicitly, otherwise they will never be deleted. OTOH // the socket can be on the path of deletion already, so // this only makes sure that the socket will be deleted, // one way or another. if (pending_broken) { // XXX This somehow can cause a deadlock // uglobal()->close(m_parent); LOGC(smlog.Debug, log << "updateBrokenConnection...: BROKEN SOCKET @" << m_SocketID << " - CLOSING, to be removed from group."); m_parent->setBrokenClosed(); } } #endif } // [[using locked_shared(m_GlobControlLock)]] void CUDT::addEPoll(const int eid) { enterCS(uglobal().m_EPoll.m_EPollLock); m_sPollID.insert(eid); leaveCS(uglobal().m_EPoll.m_EPollLock); if (m_bListening) { // A listener socket can only get readiness on SRT_EPOLL_ACCEPT // (which has the same value as SRT_EPOLL_IN), or sometimes // also SRT_EPOLL_UPDATE. All interesting fields for that purpose // are contained in the CUDTSocket class, so redirect there. // NOTE: m_GlobControlLock is required here, but it's already applied // on this function (see CUDTUnited::epoll_add_usock_INTERNAL) SRT_EPOLL_T events = m_parent->getListenerEvents(); // Only light up the events that were returned, do nothing if none is ready, // the "no event" state is the default. if (events) uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, events, true); // You don't check anything else here - a listener socket can be only // used for listening and nothing else. return; } if (!stillConnected()) return; enterCS(m_RecvLock); if (isRcvBufferReady()) { uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } leaveCS(m_RecvLock); if (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize()) { uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); } } void CUDT::removeEPollEvents(const int eid) { // clear IO events notifications; // since this happens after the epoll ID has been removed, they cannot be set again set remove; remove.insert(eid); uglobal().m_EPoll.update_events(m_SocketID, remove, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); } void CUDT::removeEPollID(const int eid) { enterCS(uglobal().m_EPoll.m_EPollLock); m_sPollID.erase(eid); leaveCS(uglobal().m_EPoll.m_EPollLock); } void CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) { if (evt >= TEV_E_SIZE) return; // sanity check m_Slots[evt].push_back(sl); } void CUDT::DisconnectSignal(ETransmissionEvent evt) { if (evt >= TEV_E_SIZE) return; // sanity check m_Slots[evt].clear(); } void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) { for (std::vector::iterator i = m_Slots[tev].begin(); i != m_Slots[tev].end(); ++i) { i->emit(tev, var); } } int CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) { CUDTSocket *s = uglobal().locateSocket(u); if (!s) return -1; CSndBuffer *b = s->core().m_pSndBuffer; if (!b) return -1; int bytecount, timespan; int count = b->getCurrBufSize((bytecount), (timespan)); if (blocks) *blocks = count; if (bytes) *bytes = bytecount; return std::abs(timespan); } int CUDT::rejectReason(SRTSOCKET u) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) return SRT_REJ_UNKNOWN; return s->core().m_RejectReason; } SRTSTATUS CUDT::rejectReason(SRTSOCKET u, int value) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) return APIError(MJ_NOTSUP, MN_SIDINVAL); if (value < SRT_REJC_PREDEFINED) return APIError(MJ_NOTSUP, MN_INVAL); s->core().m_RejectReason = value; return SRT_STATUS_OK; } int64_t CUDT::socketStartTime(SRTSOCKET u) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) return APIError(MJ_NOTSUP, MN_SIDINVAL).as(); const time_point& start_time = s->core().socketStartTime(); return count_microseconds(start_time.time_since_epoch()); } bool CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) { // Prepare the information for the hook. // We need streamid. char target[CSrtConfig::MAX_SID_LENGTH + 1]; memset((target), 0, CSrtConfig::MAX_SID_LENGTH + 1); // Just for a case, check the length. // This wasn't done before, and we could risk memory crash. // In case of error, this will remain unset and the empty // string will be passed as streamid. int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); #if SRT_ENABLE_BONDING bool have_group = false; SRT_GROUP_TYPE gt = SRT_GTYPE_UNDEFINED; #endif // This tests if there are any extensions. if (hspkt.getLength() > CHandShake::m_iContentSize + 4 && IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) { uint32_t *begin = reinterpret_cast(hspkt.m_pcData + CHandShake::m_iContentSize); size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 uint32_t *next = 0; size_t length = size / sizeof(uint32_t); size_t blocklen = 0; for (;;) // ONE SHOT, but continuable loop { int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); const size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_SID) { if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { LOGC(cnlog.Error, log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // See comment at CUDT::interpretSrtHandshake(). memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA(((uint32_t *)target), (uint32_t *)target, blocklen); } #if SRT_ENABLE_BONDING else if (cmd == SRT_CMD_GROUP) { uint32_t* groupdata = begin + 1; have_group = true; // Even if parse error happes if (bytelen / sizeof(int32_t) >= GRPD_E_SIZE) { uint32_t gd = groupdata[GRPD_GROUPDATA]; gt = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); } } #endif else if (cmd == SRT_CMD_NONE) { // End of blocks break; } // Any other kind of message extracted. Search on. if (!NextExtensionBlock((begin), next, (length))) break; } } #if SRT_ENABLE_BONDING if (have_group && acore->m_config.iGroupConnect == 0) { HLOGC(cnlog.Debug, log << CONID() << "runAcceptHook: REJECTING connection WITHOUT calling the hook - groups not allowed"); return false; } // Update the groupconnect flag acore->m_config.iGroupConnect = have_group ? 1 : 0; acore->m_HSGroupType = gt; #endif // Set the default value acore->m_RejectReason = SRT_REJX_FALLBACK; try { int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs.m_iVersion, peer, target); if (result == -1) return false; } catch (...) { LOGP(cnlog.Warn, "runAcceptHook: hook interrupted by exception"); return false; } acore->m_RejectReason = SRT_REJ_UNKNOWN; return true; } void CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) { // Here can be handled some protocol definition // for extra data sent through keepalive. #if SRT_ENABLE_BONDING if (m_parent->m_GroupOf) { // Lock GlobControlLock in order to make sure that // the state of the socket having the group and the // existence of the group will not be changed during // the operation. The attempt of group deletion will // have to wait until this operation completes. ExclusiveLock lock(uglobal().m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) { // Whether anything is to be done with this socket // about the fact that keepalive arrived, let the // group handle it pg->processKeepalive(m_parent->m_GroupMemberData); } } #endif ScopedLock lck(m_RcvBufferLock); m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); if (m_config.bDriftTracer) m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); } // This function should be called when closing the socket internally. void CUDT::setAgentCloseReason(int reason) { m_AgentCloseReason.compare_exchange(SRT_CLS_UNKNOWN, reason); // Do not touch m_PeerCloseReason, it should remain SRT_CLS_UNKNOWN. // If this reason is already set to some value, then m_AgentCloseReason // should have been already set to SRT_CLS_PEER. m_CloseTimeStamp.compare_exchange(time_point(), steady_clock::now()); } // This function should be called in a handler of UMSG_SHUTDOWN. void CUDT::setPeerCloseReason(int reason) { m_AgentCloseReason.compare_exchange(SRT_CLS_UNKNOWN, SRT_CLS_PEER); if (m_AgentCloseReason == SRT_CLS_PEER) { m_PeerCloseReason.compare_exchange(SRT_CLS_UNKNOWN, reason); m_CloseTimeStamp.compare_exchange(time_point(), steady_clock::now()); } } void CUDT::copyCloseInfo(SRT_CLOSE_INFO& info) { info.agent = SRT_CLOSE_REASON(m_AgentCloseReason.load()); info.peer = SRT_CLOSE_REASON(m_PeerCloseReason.load()); info.time = m_CloseTimeStamp.load().time_since_epoch().count(); } size_t CUDT::payloadSize() const { HLOGC(cnlog.Debug, log << "payloadSize Q: config/exp=" << m_config.zExpPayloadSize << " max=" << m_iMaxSRTPayloadSize << " " << (m_bConnected? "+":"-") << "connected"); // If payloadsize is set, it should already be checked that // it is less than the possible maximum payload size. So return it // if it is set to nonzero value. In case when the connection isn't // yet established, return also 0, if the value wasn't set. if (!m_bConnected || m_config.zExpPayloadSize) return m_config.zExpPayloadSize; // If SRTO_PAYLOADSIZE was remaining with 0 (default for FILE mode) // then return the maximum payload size per packet. return m_iMaxSRTPayloadSize; } HandshakeSide getHandshakeSide(SRTSOCKET u) { return CUDT::handshakeSide(u); } HandshakeSide CUDT::handshakeSide(SRTSOCKET u) { CUDTSocket *s = uglobal().locateSocket(u); return s ? s->core().handshakeSide() : HSD_DRAW; } } // END namespace srt