mirror of
https://github.com/ossrs/srs.git
synced 2025-02-13 11:51:57 +00:00
9705 lines
367 KiB
C++
9705 lines
367 KiB
C++
/*
|
|
* SRT - Secure, Reliable, Transport
|
|
* Copyright (c) 2018 Haivision Systems Inc.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
*/
|
|
|
|
/*****************************************************************************
|
|
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are
|
|
met:
|
|
|
|
* Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the
|
|
above copyright notice, this list of conditions
|
|
and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the University of Illinois
|
|
nor the names of its contributors may be used to
|
|
endorse or promote products derived from this
|
|
software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
written by
|
|
Yunhong Gu, last updated 02/28/2012
|
|
modified by
|
|
Haivision Systems Inc.
|
|
*****************************************************************************/
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#else
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#endif
|
|
#include <cmath>
|
|
#include <sstream>
|
|
#include "srt.h"
|
|
#include "queue.h"
|
|
#include "core.h"
|
|
#include "logging.h"
|
|
#include "crypto.h"
|
|
#include "logging_api.h" // Required due to containing extern srt_logger_config
|
|
|
|
// Again, just in case when some "smart guy" provided such a global macro
|
|
#ifdef min
|
|
#undef min
|
|
#endif
|
|
#ifdef max
|
|
#undef max
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
namespace srt_logging
|
|
{
|
|
|
|
struct AllFaOn
|
|
{
|
|
LogConfig::fa_bitset_t allfa;
|
|
|
|
AllFaOn()
|
|
{
|
|
// allfa.set(SRT_LOGFA_BSTATS, true);
|
|
allfa.set(SRT_LOGFA_CONTROL, true);
|
|
allfa.set(SRT_LOGFA_DATA, true);
|
|
allfa.set(SRT_LOGFA_TSBPD, true);
|
|
allfa.set(SRT_LOGFA_REXMIT, true);
|
|
allfa.set(SRT_LOGFA_CONGEST, true);
|
|
#if ENABLE_HAICRYPT_LOGGING
|
|
allfa.set(SRT_LOGFA_HAICRYPT, true);
|
|
#endif
|
|
}
|
|
} logger_fa_all;
|
|
|
|
} // namespace srt_logging
|
|
|
|
// We need it outside the namespace to preserve the global name.
|
|
// It's a part of "hidden API" (used by applications)
|
|
SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa);
|
|
|
|
namespace srt_logging
|
|
{
|
|
|
|
Logger glog(SRT_LOGFA_GENERAL, srt_logger_config, "SRT.g");
|
|
// Unused. If not found useful, maybe reuse for another FA.
|
|
// Logger blog(SRT_LOGFA_BSTATS, srt_logger_config, "SRT.b");
|
|
Logger mglog(SRT_LOGFA_CONTROL, srt_logger_config, "SRT.c");
|
|
Logger dlog(SRT_LOGFA_DATA, srt_logger_config, "SRT.d");
|
|
Logger tslog(SRT_LOGFA_TSBPD, srt_logger_config, "SRT.t");
|
|
Logger rxlog(SRT_LOGFA_REXMIT, srt_logger_config, "SRT.r");
|
|
Logger cclog(SRT_LOGFA_CONGEST, srt_logger_config, "SRT.cc");
|
|
|
|
} // namespace srt_logging
|
|
|
|
using namespace srt_logging;
|
|
|
|
CUDTUnited CUDT::s_UDTUnited;
|
|
|
|
const SRTSOCKET UDT::INVALID_SOCK = CUDT::INVALID_SOCK;
|
|
const int UDT::ERROR = CUDT::ERROR;
|
|
|
|
// SRT Version constants
|
|
#define SRT_VERSION_UNK 0
|
|
#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */
|
|
#define SRT_VERSION_MAJ(v) (0xFF0000 & (v)) /* Major number ensuring backward compatibility */
|
|
#define SRT_VERSION_MIN(v) (0x00FF00 & (v))
|
|
#define SRT_VERSION_PCH(v) (0x0000FF & (v))
|
|
|
|
// NOTE: SRT_VERSION is primarily defined in the build file.
|
|
const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION);
|
|
|
|
//#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */
|
|
#define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */
|
|
#define SRT_CMD_HSREQ_SZ 12 /* Current version packet size */
|
|
#if SRT_CMD_HSREQ_SZ > SRT_CMD_MAXSZ
|
|
#error SRT_CMD_MAXSZ too small
|
|
#endif
|
|
/* 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) */
|
|
#define SRT_CMD_HSRSP_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */
|
|
#define SRT_CMD_HSRSP_SZ 12 /* Current version packet size */
|
|
#if SRT_CMD_HSRSP_SZ > SRT_CMD_MAXSZ
|
|
#error SRT_CMD_MAXSZ too small
|
|
#endif
|
|
/* 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
|
|
*/
|
|
|
|
void CUDT::construct()
|
|
{
|
|
m_pSndBuffer = NULL;
|
|
m_pRcvBuffer = NULL;
|
|
m_pSndLossList = NULL;
|
|
m_pRcvLossList = NULL;
|
|
m_iReorderTolerance = 0;
|
|
m_iMaxReorderTolerance = 0; // Sensible optimal value is 10, 0 preserves old behavior
|
|
m_iConsecEarlyDelivery = 0; // how many times so far the packet considered lost has been received before TTL expires
|
|
m_iConsecOrderedDelivery = 0;
|
|
|
|
m_pSndQueue = NULL;
|
|
m_pRcvQueue = NULL;
|
|
m_pPeerAddr = NULL;
|
|
m_pSNode = NULL;
|
|
m_pRNode = NULL;
|
|
|
|
m_ullSndHsLastTime_us = 0;
|
|
m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; // Will be reset to 0 for HSv5, this value is important for HSv4
|
|
|
|
// Initial status
|
|
m_bOpened = false;
|
|
m_bListening = false;
|
|
m_bConnecting = false;
|
|
m_bConnected = false;
|
|
m_bClosing = false;
|
|
m_bShutdown = false;
|
|
m_bBroken = false;
|
|
m_bPeerHealth = true;
|
|
m_RejectReason = SRT_REJ_UNKNOWN;
|
|
m_ullLingerExpiration = 0;
|
|
m_llLastReqTime = 0;
|
|
|
|
m_lSrtVersion = SRT_DEF_VERSION;
|
|
m_lPeerSrtVersion = 0; // not defined until connected.
|
|
m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1;
|
|
|
|
m_iTsbPdDelay_ms = 0;
|
|
m_iPeerTsbPdDelay_ms = 0;
|
|
|
|
m_bPeerTsbPd = false;
|
|
m_iPeerTsbPdDelay_ms = 0;
|
|
m_bTsbPd = false;
|
|
m_bTsbPdAckWakeup = false;
|
|
m_bPeerTLPktDrop = false;
|
|
|
|
m_uKmRefreshRatePkt = 0;
|
|
m_uKmPreAnnouncePkt = 0;
|
|
|
|
// Initilize mutex and condition variables
|
|
initSynch();
|
|
}
|
|
|
|
CUDT::CUDT()
|
|
{
|
|
construct();
|
|
|
|
(void)SRT_DEF_VERSION;
|
|
|
|
// Default UDT configurations
|
|
m_iMSS = 1500;
|
|
m_bSynSending = true;
|
|
m_bSynRecving = true;
|
|
m_iFlightFlagSize = 25600;
|
|
m_iSndBufSize = 8192;
|
|
m_iRcvBufSize = 8192; // Rcv buffer MUST NOT be bigger than Flight Flag size
|
|
|
|
// Linger: LIVE mode defaults, please refer to `SRTO_TRANSTYPE` option
|
|
// for other modes.
|
|
m_Linger.l_onoff = 0;
|
|
m_Linger.l_linger = 0;
|
|
m_iUDPSndBufSize = 65536;
|
|
m_iUDPRcvBufSize = m_iRcvBufSize * m_iMSS;
|
|
m_iSockType = UDT_DGRAM;
|
|
m_iIPversion = AF_INET;
|
|
m_bRendezvous = false;
|
|
#ifdef SRT_ENABLE_CONNTIMEO
|
|
m_iConnTimeOut = 3000;
|
|
#endif
|
|
m_iSndTimeOut = -1;
|
|
m_iRcvTimeOut = -1;
|
|
m_bReuseAddr = true;
|
|
m_llMaxBW = -1;
|
|
#ifdef SRT_ENABLE_IPOPTS
|
|
m_iIpTTL = -1;
|
|
m_iIpToS = -1;
|
|
#endif
|
|
m_CryptoSecret.len = 0;
|
|
m_iSndCryptoKeyLen = 0;
|
|
// Cfg
|
|
m_bDataSender = false; // Sender only if true: does not recv data
|
|
m_bOPT_TsbPd = true; // Enable TsbPd on sender
|
|
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
|
|
m_iOPT_PeerTsbPdDelay = 0; // Peer's TsbPd delay as receiver (here is its minimum value, if used)
|
|
m_bOPT_TLPktDrop = true;
|
|
m_iOPT_SndDropDelay = 0;
|
|
m_bOPT_StrictEncryption = true;
|
|
m_iOPT_PeerIdleTimeout = COMM_RESPONSE_TIMEOUT_MS;
|
|
m_bTLPktDrop = true; // Too-late Packet Drop
|
|
m_bMessageAPI = true;
|
|
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
|
|
m_iIpV6Only = -1;
|
|
// Runtime
|
|
m_bRcvNakReport = true; // Receiver's Periodic NAK Reports
|
|
m_llInputBW = 0; // Application provided input bandwidth (internal input rate sampling == 0)
|
|
m_iOverheadBW = 25; // Percent above input stream rate (applies if m_llMaxBW == 0)
|
|
m_OPT_PktFilterConfigString = "";
|
|
|
|
m_pCache = NULL;
|
|
|
|
// Default congctl is "live".
|
|
// Available builtin congctl: "file".
|
|
// Other congctls can be registerred.
|
|
|
|
// Note that 'select' returns false if there's no such congctl.
|
|
// If so, congctl becomes unselected. Calling 'configure' on an
|
|
// unselected congctl results in exception.
|
|
m_CongCtl.select("live");
|
|
}
|
|
|
|
CUDT::CUDT(const CUDT &ancestor)
|
|
{
|
|
construct();
|
|
|
|
// XXX Consider all below fields (except m_bReuseAddr) to be put
|
|
// into a separate class for easier copying.
|
|
|
|
// Default UDT configurations
|
|
m_iMSS = ancestor.m_iMSS;
|
|
m_bSynSending = ancestor.m_bSynSending;
|
|
m_bSynRecving = ancestor.m_bSynRecving;
|
|
m_iFlightFlagSize = ancestor.m_iFlightFlagSize;
|
|
m_iSndBufSize = ancestor.m_iSndBufSize;
|
|
m_iRcvBufSize = ancestor.m_iRcvBufSize;
|
|
m_Linger = ancestor.m_Linger;
|
|
m_iUDPSndBufSize = ancestor.m_iUDPSndBufSize;
|
|
m_iUDPRcvBufSize = ancestor.m_iUDPRcvBufSize;
|
|
m_iSockType = ancestor.m_iSockType;
|
|
m_iIPversion = ancestor.m_iIPversion;
|
|
m_bRendezvous = ancestor.m_bRendezvous;
|
|
#ifdef SRT_ENABLE_CONNTIMEO
|
|
m_iConnTimeOut = ancestor.m_iConnTimeOut;
|
|
#endif
|
|
m_iSndTimeOut = ancestor.m_iSndTimeOut;
|
|
m_iRcvTimeOut = ancestor.m_iRcvTimeOut;
|
|
m_bReuseAddr = true; // this must be true, because all accepted sockets share the same port with the listener
|
|
m_llMaxBW = ancestor.m_llMaxBW;
|
|
#ifdef SRT_ENABLE_IPOPTS
|
|
m_iIpTTL = ancestor.m_iIpTTL;
|
|
m_iIpToS = ancestor.m_iIpToS;
|
|
#endif
|
|
m_llInputBW = ancestor.m_llInputBW;
|
|
m_iOverheadBW = ancestor.m_iOverheadBW;
|
|
m_bDataSender = ancestor.m_bDataSender;
|
|
m_bOPT_TsbPd = ancestor.m_bOPT_TsbPd;
|
|
m_iOPT_TsbPdDelay = ancestor.m_iOPT_TsbPdDelay;
|
|
m_iOPT_PeerTsbPdDelay = ancestor.m_iOPT_PeerTsbPdDelay;
|
|
m_bOPT_TLPktDrop = ancestor.m_bOPT_TLPktDrop;
|
|
m_iOPT_SndDropDelay = ancestor.m_iOPT_SndDropDelay;
|
|
m_bOPT_StrictEncryption = ancestor.m_bOPT_StrictEncryption;
|
|
m_iOPT_PeerIdleTimeout = ancestor.m_iOPT_PeerIdleTimeout;
|
|
m_zOPT_ExpPayloadSize = ancestor.m_zOPT_ExpPayloadSize;
|
|
m_bTLPktDrop = ancestor.m_bTLPktDrop;
|
|
m_bMessageAPI = ancestor.m_bMessageAPI;
|
|
m_iIpV6Only = ancestor.m_iIpV6Only;
|
|
m_iReorderTolerance = ancestor.m_iMaxReorderTolerance; // Initialize with maximum value
|
|
m_iMaxReorderTolerance = ancestor.m_iMaxReorderTolerance;
|
|
// Runtime
|
|
m_bRcvNakReport = ancestor.m_bRcvNakReport;
|
|
m_OPT_PktFilterConfigString = ancestor.m_OPT_PktFilterConfigString;
|
|
|
|
m_CryptoSecret = ancestor.m_CryptoSecret;
|
|
m_iSndCryptoKeyLen = ancestor.m_iSndCryptoKeyLen;
|
|
|
|
m_uKmRefreshRatePkt = ancestor.m_uKmRefreshRatePkt;
|
|
m_uKmPreAnnouncePkt = ancestor.m_uKmPreAnnouncePkt;
|
|
|
|
m_pCache = ancestor.m_pCache;
|
|
|
|
// SrtCongestion's copy constructor copies the selection,
|
|
// but not the underlying congctl object. After
|
|
// copy-constructed, the 'configure' must be called on it again.
|
|
m_CongCtl = ancestor.m_CongCtl;
|
|
}
|
|
|
|
CUDT::~CUDT()
|
|
{
|
|
// release mutex/condtion variables
|
|
destroySynch();
|
|
|
|
// Wipeout critical data
|
|
memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret));
|
|
|
|
// destroy the data structures
|
|
delete m_pSndBuffer;
|
|
delete m_pRcvBuffer;
|
|
delete m_pSndLossList;
|
|
delete m_pRcvLossList;
|
|
delete m_pPeerAddr;
|
|
delete m_pSNode;
|
|
delete m_pRNode;
|
|
}
|
|
|
|
// This function is to make it possible for both C and C++
|
|
// API to accept both bool and int types for boolean options.
|
|
// (it's not that C couldn't use <stdbool.h>, it's that people
|
|
// often forget to use correct type).
|
|
static bool bool_int_value(const void *optval, int optlen)
|
|
{
|
|
if (optlen == sizeof(bool))
|
|
{
|
|
return *(bool *)optval;
|
|
}
|
|
|
|
if (optlen == sizeof(int))
|
|
{
|
|
return 0 != *(int *)optval; // 0!= is a windows warning-killer int-to-bool conversion
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CUDT::setOpt(SRT_SOCKOPT optName, const void *optval, int optlen)
|
|
{
|
|
if (m_bBroken || m_bClosing)
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
|
|
CGuard cg(m_ConnectionLock);
|
|
CGuard sendguard(m_SendLock);
|
|
CGuard recvguard(m_RecvLock);
|
|
|
|
switch (optName)
|
|
{
|
|
case SRTO_MSS:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
|
|
if (*(int *)optval < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
m_iMSS = *(int *)optval;
|
|
|
|
// Packet size cannot be greater than UDP buffer size
|
|
if (m_iMSS > m_iUDPSndBufSize)
|
|
m_iMSS = m_iUDPSndBufSize;
|
|
if (m_iMSS > m_iUDPRcvBufSize)
|
|
m_iMSS = m_iUDPRcvBufSize;
|
|
|
|
break;
|
|
|
|
case SRTO_SNDSYN:
|
|
m_bSynSending = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_RCVSYN:
|
|
m_bSynRecving = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_FC:
|
|
if (m_bConnecting || m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
if (*(int *)optval < 1)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL);
|
|
|
|
// Mimimum recv flight flag size is 32 packets
|
|
if (*(int *)optval > 32)
|
|
m_iFlightFlagSize = *(int *)optval;
|
|
else
|
|
m_iFlightFlagSize = 32;
|
|
|
|
break;
|
|
|
|
case SRTO_SNDBUF:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
|
|
if (*(int *)optval <= 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
m_iSndBufSize = *(int *)optval / (m_iMSS - CPacket::UDP_HDR_SIZE);
|
|
|
|
break;
|
|
|
|
case SRTO_RCVBUF:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
|
|
if (*(int *)optval <= 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
{
|
|
// This weird cast through int is required because
|
|
// API requires 'int', and internals require 'size_t';
|
|
// their size is different on 64-bit systems.
|
|
size_t val = size_t(*(int *)optval);
|
|
|
|
// Mimimum recv buffer size is 32 packets
|
|
size_t mssin_size = m_iMSS - CPacket::UDP_HDR_SIZE;
|
|
|
|
// XXX This magic 32 deserves some constant
|
|
if (val > mssin_size * 32)
|
|
m_iRcvBufSize = val / mssin_size;
|
|
else
|
|
m_iRcvBufSize = 32;
|
|
|
|
// recv buffer MUST not be greater than FC size
|
|
if (m_iRcvBufSize > m_iFlightFlagSize)
|
|
m_iRcvBufSize = m_iFlightFlagSize;
|
|
}
|
|
|
|
break;
|
|
|
|
case SRTO_LINGER:
|
|
m_Linger = *(linger *)optval;
|
|
break;
|
|
|
|
case SRTO_UDP_SNDBUF:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
|
|
m_iUDPSndBufSize = *(int *)optval;
|
|
|
|
if (m_iUDPSndBufSize < m_iMSS)
|
|
m_iUDPSndBufSize = m_iMSS;
|
|
|
|
break;
|
|
|
|
case SRTO_UDP_RCVBUF:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
|
|
m_iUDPRcvBufSize = *(int *)optval;
|
|
|
|
if (m_iUDPRcvBufSize < m_iMSS)
|
|
m_iUDPRcvBufSize = m_iMSS;
|
|
|
|
break;
|
|
|
|
case SRTO_RENDEZVOUS:
|
|
if (m_bConnecting || m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
m_bRendezvous = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_SNDTIMEO:
|
|
m_iSndTimeOut = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_RCVTIMEO:
|
|
m_iRcvTimeOut = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_REUSEADDR:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
m_bReuseAddr = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_MAXBW:
|
|
m_llMaxBW = *(int64_t *)optval;
|
|
|
|
// This can be done on both connected and unconnected socket.
|
|
// When not connected, this will do nothing, however this
|
|
// event will be repeated just after connecting anyway.
|
|
if (m_bConnected)
|
|
updateCC(TEV_INIT, TEV_INIT_RESET);
|
|
break;
|
|
|
|
#ifdef SRT_ENABLE_IPOPTS
|
|
case SRTO_IPTTL:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
if (!(*(int *)optval == -1) && !((*(int *)optval >= 1) && (*(int *)optval <= 255)))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
m_iIpTTL = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_IPTOS:
|
|
if (m_bOpened)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
|
|
m_iIpToS = *(int *)optval;
|
|
break;
|
|
#endif
|
|
|
|
case SRTO_INPUTBW:
|
|
m_llInputBW = *(int64_t *)optval;
|
|
// (only if connected; if not, then the value
|
|
// from m_iOverheadBW will be used initially)
|
|
if (m_bConnected)
|
|
updateCC(TEV_INIT, TEV_INIT_INPUTBW);
|
|
break;
|
|
|
|
case SRTO_OHEADBW:
|
|
if ((*(int *)optval < 5) || (*(int *)optval > 100))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
m_iOverheadBW = *(int *)optval;
|
|
|
|
// Changed overhead BW, so spread the change
|
|
// (only if connected; if not, then the value
|
|
// from m_iOverheadBW will be used initially)
|
|
if (m_bConnected)
|
|
updateCC(TEV_INIT, TEV_INIT_OHEADBW);
|
|
break;
|
|
|
|
case SRTO_SENDER:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_bDataSender = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_TSBPDMODE:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_bOPT_TsbPd = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_LATENCY:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_iOPT_TsbPdDelay = *(int *)optval;
|
|
m_iOPT_PeerTsbPdDelay = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_RCVLATENCY:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_iOPT_TsbPdDelay = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_PEERLATENCY:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_iOPT_PeerTsbPdDelay = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_TLPKTDROP:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_bOPT_TLPktDrop = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_SNDDROPDELAY:
|
|
// Surprise: you may be connected to alter this option.
|
|
// The application may manipulate this option on sender while transmitting.
|
|
m_iOPT_SndDropDelay = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_PASSPHRASE:
|
|
// For consistency, throw exception when connected,
|
|
// no matter if otherwise the password can be set.
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
#ifdef SRT_ENABLE_ENCRYPTION
|
|
// Password must be 10-80 characters.
|
|
// Or it can be empty to clear the password.
|
|
if ((optlen != 0) && (optlen < 10 || optlen > HAICRYPT_SECRET_MAX_SZ))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret));
|
|
m_CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE;
|
|
m_CryptoSecret.len = (optlen <= (int)sizeof(m_CryptoSecret.str) ? optlen : (int)sizeof(m_CryptoSecret.str));
|
|
memcpy(m_CryptoSecret.str, optval, m_CryptoSecret.len);
|
|
#else
|
|
if (optlen == 0)
|
|
break;
|
|
|
|
LOGC(mglog.Error, log << "SRTO_PASSPHRASE: encryption not enabled at compile time");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
#endif
|
|
break;
|
|
|
|
case SRTO_PBKEYLEN:
|
|
case _DEPRECATED_SRTO_SNDPBKEYLEN:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
#ifdef SRT_ENABLE_ENCRYPTION
|
|
{
|
|
int v = *(int *)optval;
|
|
int allowed[4] = {
|
|
0, // Default value, if this results for initiator, defaults to 16. See below.
|
|
16, // AES-128
|
|
24, // AES-192
|
|
32 // AES-256
|
|
};
|
|
int *allowed_end = allowed + 4;
|
|
if (find(allowed, allowed_end, v) == allowed_end)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "Invalid value for option SRTO_PBKEYLEN: " << v << "; allowed are: 0, 16, 24, 32");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
// Note: This works a little different in HSv4 and HSv5.
|
|
|
|
// HSv4:
|
|
// The party that is set SRTO_SENDER will send KMREQ, and it will
|
|
// use default value 16, if SRTO_PBKEYLEN is the default value 0.
|
|
// The responder that receives KMRSP has nothing to say about
|
|
// PBKEYLEN anyway and it will take the length of the key from
|
|
// the initiator (sender) as a good deal.
|
|
//
|
|
// HSv5:
|
|
// The initiator (independently on the sender) will send KMREQ,
|
|
// and as it should be the sender to decide about the PBKEYLEN.
|
|
// Your application should do the following then:
|
|
// 1. The sender should set PBKEYLEN to the required value.
|
|
// 2. If the sender is initiator, it will create the key using
|
|
// its preset PBKEYLEN (or default 16, if not set) and the
|
|
// receiver-responder will take it as a good deal.
|
|
// 3. Leave the PBKEYLEN value on the receiver as default 0.
|
|
// 4. If sender is responder, it should then advertise the PBKEYLEN
|
|
// value in the initial handshake messages (URQ_INDUCTION if
|
|
// listener, and both URQ_WAVEAHAND and URQ_CONCLUSION in case
|
|
// of rendezvous, as it is the matter of luck who of them will
|
|
// eventually become the initiator). This way the receiver
|
|
// being an initiator will set m_iSndCryptoKeyLen before setting
|
|
// up KMREQ for sending to the sender-responder.
|
|
//
|
|
// Note that in HSv5 if both sides set PBKEYLEN, the responder
|
|
// wins, unless the initiator is a sender (the effective PBKEYLEN
|
|
// will be the one advertised by the responder). If none sets,
|
|
// PBKEYLEN will default to 16.
|
|
|
|
m_iSndCryptoKeyLen = v;
|
|
}
|
|
#else
|
|
LOGC(mglog.Error, log << "SRTO_PBKEYLEN: encryption not enabled at compile time");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
#endif
|
|
break;
|
|
|
|
case SRTO_NAKREPORT:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_bRcvNakReport = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
#ifdef SRT_ENABLE_CONNTIMEO
|
|
case SRTO_CONNTIMEO:
|
|
m_iConnTimeOut = *(int *)optval;
|
|
break;
|
|
#endif
|
|
|
|
case SRTO_LOSSMAXTTL:
|
|
m_iMaxReorderTolerance = *(int *)optval;
|
|
if (!m_bConnected)
|
|
m_iReorderTolerance = m_iMaxReorderTolerance;
|
|
break;
|
|
|
|
case SRTO_VERSION:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_lSrtVersion = *(uint32_t *)optval;
|
|
break;
|
|
|
|
case SRTO_MINVERSION:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_lMinimumPeerSrtVersion = *(uint32_t *)optval;
|
|
break;
|
|
|
|
case SRTO_STREAMID:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
if (size_t(optlen) > MAX_SID_LENGTH)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
m_sStreamName.assign((const char *)optval, optlen);
|
|
break;
|
|
|
|
case SRTO_CONGESTION:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
{
|
|
string val;
|
|
if (optlen == -1)
|
|
val = (const char *)optval;
|
|
else
|
|
val.assign((const char *)optval, optlen);
|
|
|
|
// Translate alias
|
|
if (val == "vod")
|
|
val = "file";
|
|
|
|
bool res = m_CongCtl.select(val);
|
|
if (!res)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
break;
|
|
|
|
case SRTO_MESSAGEAPI:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
m_bMessageAPI = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_PAYLOADSIZE:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
if (*(int *)optval > SRT_LIVE_MAX_PLSIZE)
|
|
{
|
|
LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE, maximum payload per MTU.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
if (m_OPT_PktFilterConfigString != "")
|
|
{
|
|
// This means that the filter might have been installed before,
|
|
// and the fix to the maximum payload size was already applied.
|
|
// This needs to be checked now.
|
|
SrtFilterConfig fc;
|
|
if (!ParseFilterConfig(m_OPT_PktFilterConfigString, fc))
|
|
{
|
|
// Break silently. This should not happen
|
|
LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size;
|
|
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE decreased by " << fc.extra_size
|
|
<< " required for packet filter header");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
}
|
|
|
|
m_zOPT_ExpPayloadSize = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_TRANSTYPE:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
// XXX Note that here the configuration for SRTT_LIVE
|
|
// is the same as DEFAULT VALUES for these fields set
|
|
// in CUDT::CUDT.
|
|
switch (*(SRT_TRANSTYPE *)optval)
|
|
{
|
|
case SRTT_LIVE:
|
|
// Default live options:
|
|
// - tsbpd: on
|
|
// - latency: 120ms
|
|
// - linger: off
|
|
// - congctl: live
|
|
// - extraction method: message (reading call extracts one message)
|
|
m_bOPT_TsbPd = true;
|
|
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
|
|
m_iOPT_PeerTsbPdDelay = 0;
|
|
m_bOPT_TLPktDrop = true;
|
|
m_iOPT_SndDropDelay = 0;
|
|
m_bMessageAPI = true;
|
|
m_bRcvNakReport = true;
|
|
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
|
|
m_Linger.l_onoff = 0;
|
|
m_Linger.l_linger = 0;
|
|
m_CongCtl.select("live");
|
|
break;
|
|
|
|
case SRTT_FILE:
|
|
// File transfer mode:
|
|
// - tsbpd: off
|
|
// - latency: 0
|
|
// - linger: 2 minutes (180s)
|
|
// - congctl: file (original UDT congestion control)
|
|
// - extraction method: stream (reading call extracts as many bytes as available and fits in buffer)
|
|
m_bOPT_TsbPd = false;
|
|
m_iOPT_TsbPdDelay = 0;
|
|
m_iOPT_PeerTsbPdDelay = 0;
|
|
m_bOPT_TLPktDrop = false;
|
|
m_iOPT_SndDropDelay = -1;
|
|
m_bMessageAPI = false;
|
|
m_bRcvNakReport = false;
|
|
m_zOPT_ExpPayloadSize = 0; // use maximum
|
|
m_Linger.l_onoff = 1;
|
|
m_Linger.l_linger = 180; // 2 minutes
|
|
m_CongCtl.select("file");
|
|
break;
|
|
|
|
default:
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
break;
|
|
|
|
case SRTO_KMREFRESHRATE:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
// If you first change the KMREFRESHRATE, KMPREANNOUNCE
|
|
// will be set to the maximum allowed value
|
|
m_uKmRefreshRatePkt = *(int *)optval;
|
|
if (m_uKmPreAnnouncePkt == 0 || m_uKmPreAnnouncePkt > (m_uKmRefreshRatePkt - 1) / 2)
|
|
{
|
|
m_uKmPreAnnouncePkt = (m_uKmRefreshRatePkt - 1) / 2;
|
|
LOGC(mglog.Warn,
|
|
log << "SRTO_KMREFRESHRATE=0x" << hex << m_uKmRefreshRatePkt << ": setting SRTO_KMPREANNOUNCE=0x"
|
|
<< hex << m_uKmPreAnnouncePkt);
|
|
}
|
|
break;
|
|
|
|
case SRTO_KMPREANNOUNCE:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
{
|
|
int val = *(int *)optval;
|
|
int kmref = m_uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_uKmRefreshRatePkt;
|
|
if (val > (kmref - 1) / 2)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "SRTO_KMPREANNOUNCE=0x" << hex << val << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2)
|
|
<< " - OPTION REJECTED.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
m_uKmPreAnnouncePkt = val;
|
|
}
|
|
break;
|
|
|
|
case SRTO_ENFORCEDENCRYPTION:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
m_bOPT_StrictEncryption = bool_int_value(optval, optlen);
|
|
break;
|
|
|
|
case SRTO_PEERIDLETIMEO:
|
|
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
m_iOPT_PeerIdleTimeout = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_IPV6ONLY:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
m_iIpV6Only = *(int *)optval;
|
|
break;
|
|
|
|
case SRTO_PACKETFILTER:
|
|
if (m_bConnected)
|
|
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
|
|
|
|
{
|
|
string arg((char *)optval, optlen);
|
|
// Parse the configuration string prematurely
|
|
SrtFilterConfig fc;
|
|
if (!ParseFilterConfig(arg, fc))
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "SRTO_FILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. "
|
|
"FILTERTYPE ("
|
|
<< fc.type << ") must be installed (or builtin)");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size;
|
|
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
|
|
{
|
|
LOGC(mglog.Warn,
|
|
log << "Due to filter-required extra " << fc.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to "
|
|
<< efc_max_payload_size << " bytes");
|
|
m_zOPT_ExpPayloadSize = efc_max_payload_size;
|
|
}
|
|
|
|
m_OPT_PktFilterConfigString = arg;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
}
|
|
|
|
void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen)
|
|
{
|
|
CGuard cg(m_ConnectionLock);
|
|
|
|
switch (optName)
|
|
{
|
|
case SRTO_MSS:
|
|
*(int *)optval = m_iMSS;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_SNDSYN:
|
|
*(bool *)optval = m_bSynSending;
|
|
optlen = sizeof(bool);
|
|
break;
|
|
|
|
case SRTO_RCVSYN:
|
|
*(bool *)optval = m_bSynRecving;
|
|
optlen = sizeof(bool);
|
|
break;
|
|
|
|
case SRTO_ISN:
|
|
*(int *)optval = m_iISN;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_FC:
|
|
*(int *)optval = m_iFlightFlagSize;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_SNDBUF:
|
|
*(int *)optval = m_iSndBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE);
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_RCVBUF:
|
|
*(int *)optval = m_iRcvBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE);
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_LINGER:
|
|
if (optlen < (int)(sizeof(linger)))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
*(linger *)optval = m_Linger;
|
|
optlen = sizeof(linger);
|
|
break;
|
|
|
|
case SRTO_UDP_SNDBUF:
|
|
*(int *)optval = m_iUDPSndBufSize;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_UDP_RCVBUF:
|
|
*(int *)optval = m_iUDPRcvBufSize;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_RENDEZVOUS:
|
|
*(bool *)optval = m_bRendezvous;
|
|
optlen = sizeof(bool);
|
|
break;
|
|
|
|
case SRTO_SNDTIMEO:
|
|
*(int *)optval = m_iSndTimeOut;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_RCVTIMEO:
|
|
*(int *)optval = m_iRcvTimeOut;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_REUSEADDR:
|
|
*(bool *)optval = m_bReuseAddr;
|
|
optlen = sizeof(bool);
|
|
break;
|
|
|
|
case SRTO_MAXBW:
|
|
*(int64_t *)optval = m_llMaxBW;
|
|
optlen = sizeof(int64_t);
|
|
break;
|
|
|
|
case SRTO_STATE:
|
|
*(int32_t *)optval = s_UDTUnited.getStatus(m_SocketID);
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_EVENT:
|
|
{
|
|
int32_t event = 0;
|
|
if (m_bBroken)
|
|
event |= UDT_EPOLL_ERR;
|
|
else
|
|
{
|
|
CGuard::enterCS(m_RecvLock);
|
|
if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady())
|
|
event |= UDT_EPOLL_IN;
|
|
CGuard::leaveCS(m_RecvLock);
|
|
if (m_pSndBuffer && (m_iSndBufSize > m_pSndBuffer->getCurrBufSize()))
|
|
event |= UDT_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)
|
|
{
|
|
CGuard::enterCS(m_RecvLock);
|
|
*(int32_t *)optval = m_pRcvBuffer->getRcvDataSize();
|
|
CGuard::leaveCS(m_RecvLock);
|
|
}
|
|
else
|
|
*(int32_t *)optval = 0;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
#ifdef SRT_ENABLE_IPOPTS
|
|
case SRTO_IPTTL:
|
|
if (m_bOpened)
|
|
*(int32_t *)optval = m_pSndQueue->getIpTTL();
|
|
else
|
|
*(int32_t *)optval = m_iIpTTL;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_IPTOS:
|
|
if (m_bOpened)
|
|
*(int32_t *)optval = m_pSndQueue->getIpToS();
|
|
else
|
|
*(int32_t *)optval = m_iIpToS;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
#endif
|
|
|
|
case SRTO_SENDER:
|
|
*(int32_t *)optval = m_bDataSender;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_TSBPDMODE:
|
|
*(int32_t *)optval = m_bOPT_TsbPd;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_LATENCY:
|
|
case SRTO_RCVLATENCY:
|
|
*(int32_t *)optval = m_iTsbPdDelay_ms;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_PEERLATENCY:
|
|
*(int32_t *)optval = m_iPeerTsbPdDelay_ms;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_TLPKTDROP:
|
|
*(int32_t *)optval = m_bTLPktDrop;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_SNDDROPDELAY:
|
|
*(int32_t *)optval = m_iOPT_SndDropDelay;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_PBKEYLEN:
|
|
if (m_pCryptoControl)
|
|
*(int32_t *)optval = m_pCryptoControl->KeyLen(); // Running Key length.
|
|
else
|
|
*(int32_t *)optval = m_iSndCryptoKeyLen; // May be 0.
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_KMSTATE:
|
|
if (!m_pCryptoControl)
|
|
*(int32_t *)optval = SRT_KM_S_UNSECURED;
|
|
else if (m_bDataSender)
|
|
*(int32_t *)optval = m_pCryptoControl->m_SndKmState;
|
|
else
|
|
*(int32_t *)optval = m_pCryptoControl->m_RcvKmState;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_SNDKMSTATE: // State imposed by Agent depending on PW and KMX
|
|
if (m_pCryptoControl)
|
|
*(int32_t *)optval = m_pCryptoControl->m_SndKmState;
|
|
else
|
|
*(int32_t *)optval = SRT_KM_S_UNSECURED;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_RCVKMSTATE: // State returned by Peer as informed during KMX
|
|
if (m_pCryptoControl)
|
|
*(int32_t *)optval = m_pCryptoControl->m_RcvKmState;
|
|
else
|
|
*(int32_t *)optval = SRT_KM_S_UNSECURED;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_LOSSMAXTTL:
|
|
*(int32_t*)optval = m_iMaxReorderTolerance;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_NAKREPORT:
|
|
*(bool *)optval = m_bRcvNakReport;
|
|
optlen = sizeof(bool);
|
|
break;
|
|
|
|
case SRTO_VERSION:
|
|
*(int32_t *)optval = m_lSrtVersion;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
case SRTO_PEERVERSION:
|
|
*(int32_t *)optval = m_lPeerSrtVersion;
|
|
optlen = sizeof(int32_t);
|
|
break;
|
|
|
|
#ifdef SRT_ENABLE_CONNTIMEO
|
|
case SRTO_CONNTIMEO:
|
|
*(int *)optval = m_iConnTimeOut;
|
|
optlen = sizeof(int);
|
|
break;
|
|
#endif
|
|
|
|
case SRTO_MINVERSION:
|
|
*(uint32_t *)optval = m_lMinimumPeerSrtVersion;
|
|
optlen = sizeof(uint32_t);
|
|
break;
|
|
|
|
case SRTO_STREAMID:
|
|
if (size_t(optlen) < m_sStreamName.size() + 1)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
strcpy((char *)optval, m_sStreamName.c_str());
|
|
optlen = m_sStreamName.size();
|
|
break;
|
|
|
|
case SRTO_CONGESTION:
|
|
{
|
|
string tt = m_CongCtl.selected_name();
|
|
strcpy((char *)optval, tt.c_str());
|
|
optlen = tt.size();
|
|
}
|
|
break;
|
|
|
|
case SRTO_MESSAGEAPI:
|
|
optlen = sizeof(bool);
|
|
*(bool *)optval = m_bMessageAPI;
|
|
break;
|
|
|
|
case SRTO_PAYLOADSIZE:
|
|
optlen = sizeof(int);
|
|
*(int *)optval = m_zOPT_ExpPayloadSize;
|
|
break;
|
|
|
|
case SRTO_ENFORCEDENCRYPTION:
|
|
optlen = sizeof(int32_t); // also with TSBPDMODE and SENDER
|
|
*(int32_t *)optval = m_bOPT_StrictEncryption;
|
|
break;
|
|
|
|
case SRTO_IPV6ONLY:
|
|
optlen = sizeof(int);
|
|
*(int *)optval = m_iIpV6Only;
|
|
break;
|
|
|
|
case SRTO_PEERIDLETIMEO:
|
|
*(int *)optval = m_iOPT_PeerIdleTimeout;
|
|
optlen = sizeof(int);
|
|
break;
|
|
|
|
case SRTO_PACKETFILTER:
|
|
if (size_t(optlen) < m_OPT_PktFilterConfigString.size() + 1)
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
|
|
strcpy((char *)optval, m_OPT_PktFilterConfigString.c_str());
|
|
optlen = m_OPT_PktFilterConfigString.size();
|
|
break;
|
|
|
|
default:
|
|
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
|
|
}
|
|
}
|
|
|
|
bool CUDT::setstreamid(SRTSOCKET u, const std::string &sid)
|
|
{
|
|
CUDT *that = getUDTHandle(u);
|
|
if (!that)
|
|
return false;
|
|
|
|
if (sid.size() > MAX_SID_LENGTH)
|
|
return false;
|
|
|
|
if (that->m_bConnected)
|
|
return false;
|
|
|
|
that->m_sStreamName = sid;
|
|
return true;
|
|
}
|
|
|
|
std::string CUDT::getstreamid(SRTSOCKET u)
|
|
{
|
|
CUDT *that = getUDTHandle(u);
|
|
if (!that)
|
|
return "";
|
|
|
|
return that->m_sStreamName;
|
|
}
|
|
|
|
// XXX REFACTOR: Make common code for CUDT constructor and clearData,
|
|
// possibly using CUDT::construct.
|
|
void CUDT::clearData()
|
|
{
|
|
// Initial sequence number, loss, acknowledgement, etc.
|
|
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
|
|
|
|
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
|
|
|
|
HLOGC(mglog.Debug, log << "clearData: PAYLOAD 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_ullLastAckTime_tk = 0;
|
|
|
|
// trace information
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.startTime = CTimer::getTime();
|
|
m_stats.sentTotal = m_stats.recvTotal = m_stats.sndLossTotal = m_stats.rcvLossTotal = m_stats.retransTotal =
|
|
m_stats.sentACKTotal = m_stats.recvACKTotal = m_stats.sentNAKTotal = m_stats.recvNAKTotal = 0;
|
|
m_stats.lastSampleTime = CTimer::getTime();
|
|
m_stats.traceSent = m_stats.traceRecv = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans =
|
|
m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0;
|
|
m_stats.traceRcvRetrans = 0;
|
|
m_stats.traceReorderDistance = 0;
|
|
m_stats.traceBelatedTime = 0.0;
|
|
m_stats.traceRcvBelated = 0;
|
|
|
|
m_stats.sndDropTotal = 0;
|
|
m_stats.traceSndDrop = 0;
|
|
m_stats.rcvDropTotal = 0;
|
|
m_stats.traceRcvDrop = 0;
|
|
|
|
m_stats.m_rcvUndecryptTotal = 0;
|
|
m_stats.traceRcvUndecrypt = 0;
|
|
|
|
m_stats.bytesSentTotal = 0;
|
|
m_stats.bytesRecvTotal = 0;
|
|
m_stats.bytesRetransTotal = 0;
|
|
m_stats.traceBytesSent = 0;
|
|
m_stats.traceBytesRecv = 0;
|
|
m_stats.sndFilterExtra = 0;
|
|
m_stats.rcvFilterExtra = 0;
|
|
m_stats.rcvFilterSupply = 0;
|
|
m_stats.rcvFilterLoss = 0;
|
|
|
|
m_stats.traceBytesRetrans = 0;
|
|
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
|
|
m_stats.traceRcvBytesLoss = 0;
|
|
#endif
|
|
m_stats.sndBytesDropTotal = 0;
|
|
m_stats.rcvBytesDropTotal = 0;
|
|
m_stats.traceSndBytesDrop = 0;
|
|
m_stats.traceRcvBytesDrop = 0;
|
|
m_stats.m_rcvBytesUndecryptTotal = 0;
|
|
m_stats.traceRcvBytesUndecrypt = 0;
|
|
|
|
m_stats.sndDuration = m_stats.m_sndDurationTotal = 0;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
// Resetting these data because this happens when agent isn't connected.
|
|
m_bPeerTsbPd = false;
|
|
m_iPeerTsbPdDelay_ms = 0;
|
|
|
|
m_bTsbPd = m_bOPT_TsbPd; // Take the values from user-configurable options
|
|
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
|
|
m_bTLPktDrop = m_bOPT_TLPktDrop;
|
|
m_bPeerTLPktDrop = false;
|
|
|
|
m_bPeerNakReport = false;
|
|
|
|
m_bPeerRexmitFlag = false;
|
|
|
|
m_RdvState = CHandShake::RDV_INVALID;
|
|
m_ullRcvPeerStartTime = 0;
|
|
}
|
|
|
|
void CUDT::open()
|
|
{
|
|
CGuard cg(m_ConnectionLock);
|
|
|
|
clearData();
|
|
|
|
// structures for queue
|
|
if (m_pSNode == NULL)
|
|
m_pSNode = new CSNode;
|
|
m_pSNode->m_pUDT = this;
|
|
m_pSNode->m_llTimeStamp_tk = 1;
|
|
m_pSNode->m_iHeapLoc = -1;
|
|
|
|
if (m_pRNode == NULL)
|
|
m_pRNode = new CRNode;
|
|
m_pRNode->m_pUDT = this;
|
|
m_pRNode->m_llTimeStamp_tk = 1;
|
|
m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL;
|
|
m_pRNode->m_bOnList = false;
|
|
|
|
m_iRTT = 10 * COMM_SYN_INTERVAL_US;
|
|
m_iRTTVar = m_iRTT >> 1;
|
|
m_ullCPUFrequency = CTimer::getCPUFrequency();
|
|
|
|
// set minimum NAK and EXP timeout to 300ms
|
|
/*
|
|
XXX This code is blocked because the value of
|
|
m_ullMinNakInt_tk will be overwritten again in setupCC.
|
|
And in setupCC it will have an opportunity to make the
|
|
value overridden according to the statements in the SrtCongestion.
|
|
|
|
#ifdef SRT_ENABLE_NAKREPORT
|
|
if (m_bRcvNakReport)
|
|
m_ullMinNakInt_tk = m_iMinNakInterval_us * m_ullCPUFrequency;
|
|
else
|
|
#endif
|
|
*/
|
|
// Set up timers
|
|
m_ullMinNakInt_tk = 300000 * m_ullCPUFrequency;
|
|
m_ullMinExpInt_tk = 300000 * m_ullCPUFrequency;
|
|
|
|
m_ullACKInt_tk = COMM_SYN_INTERVAL_US * m_ullCPUFrequency;
|
|
m_ullNAKInt_tk = m_ullMinNakInt_tk;
|
|
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
m_ullLastRspTime_tk = currtime_tk;
|
|
m_ullNextACKTime_tk = currtime_tk + m_ullACKInt_tk;
|
|
m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk;
|
|
m_ullLastRspAckTime_tk = currtime_tk;
|
|
m_ullLastSndTime_tk = currtime_tk;
|
|
m_iReXmitCount = 1;
|
|
|
|
m_iPktCount = 0;
|
|
m_iLightACKCount = 1;
|
|
|
|
m_ullTargetTime_tk = 0;
|
|
m_ullTimeDiff_tk = 0;
|
|
|
|
// Now UDT is opened.
|
|
m_bOpened = true;
|
|
}
|
|
|
|
void CUDT::setListenState()
|
|
{
|
|
CGuard cg(m_ConnectionLock);
|
|
|
|
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 (m_bListening)
|
|
return;
|
|
|
|
// if there is already another socket listening on the same port
|
|
if (m_pRcvQueue->setListener(this) < 0)
|
|
throw CUDTException(MJ_NOTSUP, MN_BUSY, 0);
|
|
|
|
m_bListening = true;
|
|
}
|
|
|
|
size_t CUDT::fillSrtHandshake(uint32_t *srtdata, size_t srtlen, int msgtype, int hs_version)
|
|
{
|
|
if (srtlen < SRT_HS__SIZE)
|
|
{
|
|
LOGC(mglog.Fatal,
|
|
log << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS__SIZE << ")");
|
|
return 0;
|
|
}
|
|
|
|
srtlen = SRT_HS__SIZE; // We use only that much space.
|
|
|
|
memset(srtdata, 0, sizeof(uint32_t) * srtlen);
|
|
/* Current version (1.x.x) SRT handshake */
|
|
srtdata[SRT_HS_VERSION] = m_lSrtVersion; /* Required version */
|
|
srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities();
|
|
|
|
switch (msgtype)
|
|
{
|
|
case SRT_CMD_HSREQ:
|
|
return fillSrtHandshake_HSREQ(srtdata, srtlen, hs_version);
|
|
case SRT_CMD_HSRSP:
|
|
return fillSrtHandshake_HSRSP(srtdata, srtlen, hs_version);
|
|
default:
|
|
LOGC(mglog.Fatal, log << "IPE: createSrtHandshake/sendSrtMsg called with value " << msgtype);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *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_bOPT_TsbPd)
|
|
{
|
|
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
|
|
m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay;
|
|
/*
|
|
* Sent data is real-time, use Time-based Packet Delivery,
|
|
* set option bit and configured delay
|
|
*/
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND;
|
|
|
|
if (hs_version < CUDT::HS_VERSION_SRT1)
|
|
{
|
|
// HSv4 - this uses only one value.
|
|
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.
|
|
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms);
|
|
|
|
m_bTsbPd = true;
|
|
// And in the reverse direction.
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV;
|
|
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)
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP;
|
|
}
|
|
}
|
|
|
|
if (m_bRcvNakReport)
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT;
|
|
|
|
// I support SRT_OPT_REXMITFLG. Do you?
|
|
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_bMessageAPI)
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM;
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY])
|
|
<< " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS["
|
|
<< SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]");
|
|
|
|
return 3;
|
|
}
|
|
|
|
size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version)
|
|
{
|
|
// Setting m_ullRcvPeerStartTime 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 (m_ullRcvPeerStartTime != 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 (m_bTsbPd)
|
|
{
|
|
/*
|
|
* We got and transposed peer start time (HandShake request timestamp),
|
|
* we can support Timestamp-based Packet Delivery
|
|
*/
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV;
|
|
|
|
if (hs_version < HS_VERSION_SRT1)
|
|
{
|
|
// HSv4 - this uses only one value
|
|
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.
|
|
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "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.
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND;
|
|
srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5)
|
|
<< " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND");
|
|
}
|
|
|
|
if (m_bTLPktDrop)
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP;
|
|
}
|
|
else
|
|
{
|
|
LOGC(mglog.Fatal, log << "IPE: fillSrtHandshake_HSRSP: m_ullRcvPeerStartTime NOT SET!");
|
|
return 0;
|
|
}
|
|
|
|
if (m_bRcvNakReport)
|
|
{
|
|
// HSv5: Note that this setting is independent on the value of
|
|
// m_bPeerNakReport, which represent this setting in the peer.
|
|
|
|
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_lPeerSrtVersion <= SrtVersion(1, 0, 7))
|
|
srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP;
|
|
}
|
|
|
|
if (m_lSrtVersion >= SrtVersion(1, 2, 0))
|
|
{
|
|
if (!m_bPeerRexmitFlag)
|
|
{
|
|
// Peer does not request to use rexmit flag, if so,
|
|
// we won't use as well.
|
|
HLOGC(mglog.Debug, log << "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.
|
|
srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG;
|
|
HLOGF(mglog.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.
|
|
HLOGF(mglog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag");
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY])
|
|
<< " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS["
|
|
<< SrtFlagString(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());
|
|
HLOGF(mglog.Debug,
|
|
"CMD:%s(%d) Len:%d Version: %s Flags: %08X (%s) sdelay:%d",
|
|
MessageTypeStr(UMSG_EXT, cmd).c_str(),
|
|
cmd,
|
|
(int)(srtlen * sizeof(int32_t)),
|
|
SrtVersionString(srtdata[SRT_HS_VERSION]).c_str(),
|
|
srtdata[SRT_HS_FLAGS],
|
|
SrtFlagString(srtdata[SRT_HS_FLAGS]).c_str(),
|
|
srtdata[SRT_HS_LATENCY]);
|
|
|
|
return srtlen;
|
|
}
|
|
|
|
void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in)
|
|
{
|
|
CPacket srtpkt;
|
|
int32_t srtcmd = (int32_t)cmd;
|
|
|
|
static const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ / sizeof(int32_t);
|
|
|
|
// This is in order to issue a compile error if the SRT_CMD_MAXSZ is
|
|
// too small to keep all the data. As this is "static const", declaring
|
|
// an array of such specified size in C++ isn't considered VLA.
|
|
static const int SRTDATA_SIZE = SRTDATA_MAXSIZE >= SRT_HS__SIZE ? SRTDATA_MAXSIZE : -1;
|
|
|
|
// This will be effectively larger than SRT_HS__SIZE, but it will be also used
|
|
// for incoming data. We have a guarantee that it won't be larger than SRTDATA_MAXSIZE.
|
|
uint32_t srtdata[SRTDATA_SIZE];
|
|
|
|
int 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_SIZE);
|
|
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_pCryptoControl->updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT
|
|
|
|
break;
|
|
|
|
default:
|
|
LOGF(mglog.Error, "sndSrtMsg: cmd=%d unsupported", cmd);
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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(ref_t<CPacket> r_pkt,
|
|
ref_t<CHandShake> r_hs,
|
|
int srths_cmd,
|
|
int srtkm_cmd,
|
|
const uint32_t * kmdata,
|
|
size_t kmdata_wordsize /* IN WORDS, NOT BYTES!!! */)
|
|
{
|
|
CPacket & pkt = *r_pkt;
|
|
CHandShake &hs = *r_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)
|
|
{
|
|
hs.m_iVersion = HS_VERSION_UDT4;
|
|
hs.m_iType = UDT_DGRAM;
|
|
if (hs.m_extension)
|
|
{
|
|
// Should be impossible
|
|
LOGC(mglog.Error, log << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing...");
|
|
hs.m_extension = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hs.m_iType = 0; // Prepare it for flags
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: buf size=" << pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd)
|
|
<< " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize
|
|
<< " version=" << 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 advertized
|
|
// 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 (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_ullRcvPeerStartTime == 0,
|
|
// it will return size 0, which will mess up with further extension procedures;
|
|
// PREVENT THIS HERE.
|
|
if (hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && m_ullRcvPeerStartTime == 0)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. "
|
|
"BLOCKING extensions.");
|
|
hs.m_extension = false;
|
|
}
|
|
|
|
// 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.
|
|
hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_iSndCryptoKeyLen);
|
|
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: " << (whether ? "" : "NOT ")
|
|
<< " Advertising PBKEYLEN - value = " << m_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
|
|
{
|
|
hs.m_iType = UDT_DGRAM;
|
|
}
|
|
|
|
// values > URQ_CONCLUSION include also error types
|
|
// if (hs.m_iVersion == HS_VERSION_UDT4 || hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and
|
|
// it's only valid for caller-listener mode
|
|
if (!hs.m_extension)
|
|
{
|
|
// Serialize only the basic handshake, if this is predicted for
|
|
// Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND.
|
|
size_t hs_size = pkt.getLength();
|
|
hs.store_to(pkt.m_pcData, Ref(hs_size));
|
|
pkt.setLength(hs_size);
|
|
HLOGC(mglog.Debug, log << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << 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(mglog.Fatal, log << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!");
|
|
return false; // should cause rejection
|
|
}
|
|
|
|
string logext = "HSX";
|
|
|
|
bool have_kmreq = false;
|
|
bool have_sid = false;
|
|
bool have_congctl = false;
|
|
bool have_filter = false;
|
|
|
|
// Install the SRT extensions
|
|
hs.m_iType |= CHandShake::HS_EXT_HSREQ;
|
|
|
|
if (srths_cmd == SRT_CMD_HSREQ)
|
|
{
|
|
if (m_sStreamName != "")
|
|
{
|
|
have_sid = true;
|
|
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_lPeerSrtFlags, 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.
|
|
if (peer_filter_capable && m_OPT_PktFilterConfigString != "")
|
|
{
|
|
have_filter = true;
|
|
hs.m_iType |= CHandShake::HS_EXT_CONFIG;
|
|
logext += ",filter";
|
|
}
|
|
|
|
string sm = m_CongCtl.selected_name();
|
|
if (sm != "" && sm != "live")
|
|
{
|
|
have_congctl = true;
|
|
hs.m_iType |= CHandShake::HS_EXT_CONFIG;
|
|
logext += ",CONGCTL";
|
|
}
|
|
|
|
// 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_CryptoSecret.len > 0 || kmdata_wordsize > 0)
|
|
{
|
|
have_kmreq = true;
|
|
hs.m_iType |= CHandShake::HS_EXT_KMREQ;
|
|
logext += ",KMX";
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "createSrtHandshake: (ext: " << logext << ") data: " << 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 = pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data
|
|
hs.store_to(pkt.m_pcData, Ref(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<uint32_t *>(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(ra_size);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size
|
|
<< " space left: " << (total_ra_size - offset));
|
|
|
|
if (have_sid)
|
|
{
|
|
// Use only in REQ phase and only if stream name is set
|
|
offset += ra_size;
|
|
pcmdspec = p + offset;
|
|
++offset;
|
|
|
|
// 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_sStreamName.size() >= size_limit)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes");
|
|
return false;
|
|
}
|
|
|
|
size_t wordsize = (m_sStreamName.size() + 3) / 4;
|
|
size_t aligned_bytesize = wordsize * 4;
|
|
|
|
memset(p + offset, 0, aligned_bytesize);
|
|
memcpy(p + offset, m_sStreamName.data(), m_sStreamName.size());
|
|
// Preswap to little endian (in place due to possible padding zeros)
|
|
HtoILA((uint32_t *)(p + offset), (uint32_t *)(p + offset), wordsize);
|
|
|
|
ra_size = wordsize;
|
|
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_SID) | HS_CMDSPEC_SIZE::wrap(ra_size);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: after SID [" << m_sStreamName << "] length=" << m_sStreamName.size()
|
|
<< " alignedln=" << aligned_bytesize << ": 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).
|
|
|
|
// XXX Consider change the congctl settings in the listener socket to "adaptive"
|
|
// congctl and also "adaptive" value of CUDT::m_bMessageAPI so that the caller
|
|
// may ask for whatever kind of transmission it wants, or select transmission
|
|
// type differently for different connections, however with the same listener.
|
|
|
|
offset += ra_size;
|
|
pcmdspec = p + offset;
|
|
++offset;
|
|
|
|
size_t wordsize = (sm.size() + 3) / 4;
|
|
size_t aligned_bytesize = wordsize * 4;
|
|
|
|
memset(p + offset, 0, aligned_bytesize);
|
|
|
|
memcpy(p + offset, sm.data(), sm.size());
|
|
// Preswap to little endian (in place due to possible padding zeros)
|
|
HtoILA((uint32_t *)(p + offset), (uint32_t *)(p + offset), wordsize);
|
|
|
|
ra_size = wordsize;
|
|
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_CONGESTION) | HS_CMDSPEC_SIZE::wrap(ra_size);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size()
|
|
<< " alignedln=" << aligned_bytesize << ": offset=" << offset << " CONGCTL size=" << ra_size
|
|
<< " space left: " << (total_ra_size - offset));
|
|
}
|
|
|
|
if (have_filter)
|
|
{
|
|
offset += ra_size;
|
|
pcmdspec = p + offset;
|
|
++offset;
|
|
|
|
size_t wordsize = (m_OPT_PktFilterConfigString.size() + 3) / 4;
|
|
size_t aligned_bytesize = wordsize * 4;
|
|
|
|
memset(p + offset, 0, aligned_bytesize);
|
|
memcpy(p + offset, m_OPT_PktFilterConfigString.data(), m_OPT_PktFilterConfigString.size());
|
|
|
|
ra_size = wordsize;
|
|
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_FILTER) | HS_CMDSPEC_SIZE::wrap(ra_size);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: after filter [" << m_OPT_PktFilterConfigString << "] length="
|
|
<< m_OPT_PktFilterConfigString.size() << " alignedln=" << aligned_bytesize << ": offset=" << offset
|
|
<< " filter size=" << ra_size << " space left: " << (total_ra_size - offset));
|
|
}
|
|
|
|
// When encryption turned on
|
|
if (have_kmreq)
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: "
|
|
<< (m_CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION"));
|
|
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_pCryptoControl->getKmMsg_needSend(ki, false))
|
|
continue;
|
|
|
|
m_pCryptoControl->getKmMsg_markSent(ki, false);
|
|
|
|
offset += ra_size;
|
|
|
|
size_t msglen = m_pCryptoControl->getKmMsg_size(ki);
|
|
// Make ra_size back in element unit
|
|
// Add one extra word if the size isn't aligned to 32-bit.
|
|
ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0);
|
|
|
|
// Store the CMD + SIZE in the next field
|
|
*(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size);
|
|
++offset;
|
|
|
|
// 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<const uint32_t *>(m_pCryptoControl->getKmMsg_data(ki));
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "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(p + offset, keydata, ra_size);
|
|
have_any_keys = true;
|
|
}
|
|
|
|
if (!have_any_keys)
|
|
{
|
|
m_RejectReason = SRT_REJ_IPE;
|
|
LOGC(mglog.Error, log << "createSrtHandshake: IPE: all keys have expired, no KM to send.");
|
|
return false;
|
|
}
|
|
}
|
|
else if (srtkm_cmd == SRT_CMD_KMRSP)
|
|
{
|
|
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.
|
|
offset += ra_size;
|
|
|
|
if (kmdata_wordsize == 0)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "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
|
|
m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Agent has PW, but Peer won't decrypt
|
|
m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Peer won't encrypt as well.
|
|
}
|
|
else
|
|
{
|
|
if (!kmdata)
|
|
{
|
|
m_RejectReason = SRT_REJ_IPE;
|
|
LOGC(mglog.Fatal, log << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!");
|
|
return false;
|
|
}
|
|
ra_size = kmdata_wordsize;
|
|
keydata = reinterpret_cast<const uint32_t *>(kmdata);
|
|
}
|
|
|
|
*(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size);
|
|
++offset; // Once cell, containting CMD spec and size
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: KMRSP: applying returned key length="
|
|
<< ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata,
|
|
// kmdata_wordsize*sizeof(uint32_t)) << "]";
|
|
|
|
NtoHLA(p + offset, keydata, ra_size);
|
|
}
|
|
else
|
|
{
|
|
m_RejectReason = SRT_REJ_IPE;
|
|
LOGC(mglog.Fatal, log << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ra_size + offset has a value in element unit.
|
|
// Switch it again to byte unit.
|
|
pkt.setLength((ra_size + offset) * sizeof(int32_t));
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(hs.m_iType)
|
|
<< " length: " << pkt.getLength() << " bytes");
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
FindExtensionBlock(uint32_t *begin, size_t total_length, ref_t<size_t> r_out_len, ref_t<uint32_t *> r_next_block)
|
|
{
|
|
// Check if there's anything to process
|
|
if (total_length == 0)
|
|
{
|
|
*r_next_block = NULL;
|
|
*r_out_len = 0;
|
|
return SRT_CMD_NONE;
|
|
}
|
|
|
|
size_t & out_len = *r_out_len;
|
|
uint32_t *&next_block = *r_next_block;
|
|
// 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 out_len.
|
|
// The beginning of the prospective next block is stored in 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 - out_len, or
|
|
// simply, as 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;
|
|
|
|
out_len = size;
|
|
|
|
if (total_length == size + 1)
|
|
next_block = NULL;
|
|
else
|
|
next_block = begin + 1 + size;
|
|
|
|
return cmd;
|
|
}
|
|
|
|
static inline bool NextExtensionBlock(ref_t<uint32_t *> begin, uint32_t *next, ref_t<size_t> length)
|
|
{
|
|
if (!next)
|
|
return false;
|
|
|
|
*length = *length - (next - *begin);
|
|
*begin = next;
|
|
return true;
|
|
}
|
|
|
|
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->m_iTimeStamp;
|
|
|
|
int res = SRT_CMD_NONE;
|
|
|
|
HLOGC(mglog.Debug, log << "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_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, srtdata_out, Ref(len_out), CUDT::HS_VERSION_UDT4);
|
|
if (res == SRT_CMD_KMRSP)
|
|
{
|
|
if (len_out == 1)
|
|
{
|
|
if (m_bOPT_StrictEncryption)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))
|
|
<< " - rejecting per strict encryption");
|
|
return false;
|
|
}
|
|
HLOGC(mglog.Debug,
|
|
log << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])));
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "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(mglog.Error, log << "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_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, CUDT::HS_VERSION_UDT4);
|
|
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 len, 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)
|
|
*/
|
|
#if 0 // Debug PeerStartTime if not 1st HS packet
|
|
{
|
|
uint64_t oldPeerStartTime = m_ullRcvPeerStartTime;
|
|
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
|
|
if (oldPeerStartTime) {
|
|
LOGC(mglog.Note, log << "rcvSrtMsg: 2nd PeerStartTime diff=" <<
|
|
(m_ullRcvPeerStartTime - oldPeerStartTime) << " usec");
|
|
|
|
}
|
|
}
|
|
#else
|
|
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
|
|
#endif
|
|
|
|
// 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_iOPT_TsbPdDelay;
|
|
m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay;
|
|
|
|
if (len < SRT_CMD_HSREQ_MINSZ)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
/* Packet smaller than minimum compatible packet size */
|
|
LOGF(mglog.Error, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " invalid", SRT_CMD_HSREQ, len);
|
|
return SRT_CMD_NONE;
|
|
}
|
|
|
|
LOGF(mglog.Note,
|
|
"HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " vers=0x%x opts=0x%x delay=%d",
|
|
SRT_CMD_HSREQ,
|
|
len,
|
|
srtdata[SRT_HS_VERSION],
|
|
srtdata[SRT_HS_FLAGS],
|
|
SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]));
|
|
|
|
m_lPeerSrtVersion = srtdata[SRT_HS_VERSION];
|
|
m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS];
|
|
|
|
if (hsv == CUDT::HS_VERSION_UDT4)
|
|
{
|
|
if (m_lPeerSrtVersion >= SRT_VERSION_FEAT_HSv5)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5)
|
|
<< " is not acceptable.");
|
|
return SRT_CMD_REJECT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_lPeerSrtVersion < SRT_VERSION_FEAT_HSv5)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "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_lPeerSrtVersion < m_lMinimumPeerSrtVersion)
|
|
{
|
|
m_RejectReason = SRT_REJ_VERSION;
|
|
LOGC(mglog.Error,
|
|
log << "HSREQ/rcv: Peer version: " << SrtVersionString(m_lPeerSrtVersion)
|
|
<< " is too old for requested: " << SrtVersionString(m_lMinimumPeerSrtVersion) << " - REJECTING");
|
|
return SRT_CMD_REJECT;
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_lPeerSrtVersion) << " Flags: " << m_lPeerSrtFlags
|
|
<< "(" << SrtFlagString(m_lPeerSrtFlags) << ")");
|
|
|
|
m_bPeerRexmitFlag = IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG);
|
|
HLOGF(mglog.Debug, "HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND");
|
|
|
|
// Check if both use the same API type. Reject if not.
|
|
bool peer_message_api = !IsSet(m_lPeerSrtFlags, SRT_OPT_STREAM);
|
|
if (peer_message_api != m_bMessageAPI)
|
|
{
|
|
m_RejectReason = SRT_REJ_MESSAGEAPI;
|
|
LOGC(mglog.Error,
|
|
log << "HSREQ/rcv: Agent uses " << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, but the Peer declares "
|
|
<< (peer_message_api ? "MESSAGE" : "STREAM") << " API. Not compatible transmission type, rejecting.");
|
|
return SRT_CMD_REJECT;
|
|
}
|
|
|
|
if (len < SRT_HS_LATENCY + 1)
|
|
{
|
|
// 3 is the size when containing VERSION, FLAGS and LATENCY. Less size
|
|
// makes it contain only the first two. Let's make it acceptable, as long
|
|
// as the latency flags aren't set.
|
|
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting.");
|
|
return SRT_CMD_REJECT;
|
|
}
|
|
|
|
LOGC(mglog.Warn, log << "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;
|
|
}
|
|
|
|
uint32_t latencystr = srtdata[SRT_HS_LATENCY];
|
|
|
|
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND))
|
|
{
|
|
// TimeStamp-based Packet Delivery feature enabled
|
|
if (!m_bTsbPd)
|
|
{
|
|
LOGC(mglog.Warn, log << "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(mglog.Debug,
|
|
log << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency
|
|
<< " Selecting:" << maxdelay);
|
|
m_iTsbPdDelay_ms = maxdelay;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent";
|
|
HLOGC(mglog.Debug, log << "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_lPeerSrtFlags, 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(mglog.Debug,
|
|
log << "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 = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent";
|
|
HLOGC(mglog.Debug, log << "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_lPeerSrtFlags, SRT_OPT_TLPKTDROP))
|
|
{
|
|
// Too late packets dropping feature supported
|
|
m_bPeerTLPktDrop = true;
|
|
}
|
|
if (IsSet(m_lPeerSrtFlags, 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 len, uint32_t ts, int hsv)
|
|
{
|
|
// XXX Check for mis-version
|
|
// With HSv4 we accept only version less than 1.2.0
|
|
if (hsv == CUDT::HS_VERSION_UDT4 && srtdata[SRT_HS_VERSION] >= SRT_VERSION_FEAT_HSv5)
|
|
{
|
|
LOGC(mglog.Error, log << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable.");
|
|
return SRT_CMD_NONE;
|
|
}
|
|
|
|
if (len < SRT_CMD_HSRSP_MINSZ)
|
|
{
|
|
/* Packet smaller than minimum compatible packet size */
|
|
LOGF(mglog.Error, "HSRSP/rcv: cmd=%d(HSRSP) len=%" PRIzu " invalid", SRT_CMD_HSRSP, len);
|
|
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 0 // Debug PeerStartTime if not 1st HS packet
|
|
{
|
|
uint64_t oldPeerStartTime = m_ullRcvPeerStartTime;
|
|
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
|
|
if (oldPeerStartTime) {
|
|
LOGC(mglog.Note, log << "rcvSrtMsg: 2nd PeerStartTime diff=" <<
|
|
(m_ullRcvPeerStartTime - oldPeerStartTime) << " usec");
|
|
|
|
}
|
|
}
|
|
#else
|
|
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
|
|
#endif
|
|
|
|
m_lPeerSrtVersion = srtdata[SRT_HS_VERSION];
|
|
m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS];
|
|
|
|
HLOGF(mglog.Debug,
|
|
"HSRSP/rcv: Version: %s Flags: SND:%08X (%s)",
|
|
SrtVersionString(m_lPeerSrtVersion).c_str(),
|
|
m_lPeerSrtFlags,
|
|
SrtFlagString(m_lPeerSrtFlags).c_str());
|
|
|
|
if (hsv == CUDT::HS_VERSION_UDT4)
|
|
{
|
|
// The old HSv4 way: extract just one value and put it under peer.
|
|
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
|
|
{
|
|
// TsbPd feature enabled
|
|
m_bPeerTsbPd = true;
|
|
m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]);
|
|
HLOGC(mglog.Debug,
|
|
log << "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.
|
|
|
|
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
|
|
{
|
|
// TsbPd feature enabled
|
|
m_bPeerTsbPd = true;
|
|
m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]);
|
|
HLOGC(mglog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "HSRSP/rcv: Peer (responder) DOES NOT USE latency");
|
|
}
|
|
|
|
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND))
|
|
{
|
|
if (!m_bTsbPd)
|
|
{
|
|
LOGC(mglog.Warn,
|
|
log << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD.");
|
|
}
|
|
else
|
|
{
|
|
// 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(mglog.Debug, log << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms");
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((m_lSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP))
|
|
{
|
|
// Too late packets dropping feature supported
|
|
m_bPeerTLPktDrop = true;
|
|
}
|
|
|
|
if ((m_lSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT))
|
|
{
|
|
// Peer will send Periodic NAK Reports
|
|
m_bPeerNakReport = true;
|
|
}
|
|
|
|
if (m_lSrtVersion >= SrtVersion(1, 2, 0))
|
|
{
|
|
if (IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG))
|
|
{
|
|
// Peer will use REXMIT flag in packet retransmission.
|
|
m_bPeerRexmitFlag = true;
|
|
HLOGP(mglog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer.");
|
|
}
|
|
else
|
|
{
|
|
HLOGP(mglog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGF(mglog.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.
|
|
bool CUDT::interpretSrtHandshake(const CHandShake &hs,
|
|
const CPacket & hspkt,
|
|
uint32_t *out_data SRT_ATR_UNUSED,
|
|
size_t * out_len)
|
|
{
|
|
// Initialize out_len to 0 to handle the unencrypted case
|
|
if (out_len)
|
|
*out_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(mglog.Error, log << "HS VERSION = 0, meaning the handshake has been rejected.");
|
|
return false;
|
|
}
|
|
|
|
if (hs.m_iVersion < HS_VERSION_SRT1)
|
|
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(mglog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!");
|
|
return false;
|
|
}
|
|
|
|
// We still believe it should work, let's check the flags.
|
|
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType);
|
|
if (ext_flags == 0)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!");
|
|
return false;
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "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<uint32_t *>(hspkt.m_pcData + CHandShake::m_iContentSize);
|
|
size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0
|
|
|
|
if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ))
|
|
{
|
|
HLOGC(mglog.Debug, log << "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, Ref(blocklen), Ref(next));
|
|
|
|
size_t bytelen = blocklen * sizeof(uint32_t);
|
|
|
|
if (cmd == SRT_CMD_HSREQ)
|
|
{
|
|
// Set is the size as it should, then give it for interpretation for
|
|
// the proper function.
|
|
if (blocklen < SRT_HS__SIZE)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS__SIZE
|
|
<< ")");
|
|
return false; // don't interpret
|
|
}
|
|
|
|
int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1);
|
|
// Interpreted? Then it should be responded with SRT_CMD_HSRSP.
|
|
if (rescmd != SRT_CMD_HSRSP)
|
|
{
|
|
// m_RejectReason already set
|
|
LOGC(mglog.Error,
|
|
log << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd);
|
|
return false;
|
|
}
|
|
handshakeDone();
|
|
updateAfterSrtHandshake(SRT_CMD_HSREQ, HS_VERSION_SRT1);
|
|
}
|
|
else if (cmd == SRT_CMD_HSRSP)
|
|
{
|
|
// Set is the size as it should, then give it for interpretation for
|
|
// the proper function.
|
|
if (blocklen < SRT_HS__SIZE)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS__SIZE
|
|
<< ")");
|
|
|
|
return false; // don't interpret
|
|
}
|
|
|
|
int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.m_iTimeStamp, 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.
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error,
|
|
log << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd);
|
|
return false;
|
|
}
|
|
handshakeDone();
|
|
updateAfterSrtHandshake(SRT_CMD_HSRSP, HS_VERSION_SRT1);
|
|
}
|
|
else if (cmd == SRT_CMD_NONE)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error, log << "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(mglog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ");
|
|
|
|
// Now check the encrypted
|
|
|
|
bool encrypted = false;
|
|
|
|
if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ))
|
|
{
|
|
HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting KMREQ/RSP type extension");
|
|
|
|
#ifdef SRT_ENABLE_ENCRYPTION
|
|
if (!m_pCryptoControl->hasPassphrase())
|
|
{
|
|
if (m_bOPT_StrictEncryption)
|
|
{
|
|
m_RejectReason = SRT_REJ_UNSECURE;
|
|
LOGC(
|
|
mglog.Error,
|
|
log << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per strict requirement");
|
|
return false;
|
|
}
|
|
|
|
LOGC(mglog.Error,
|
|
log << "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, Ref(blocklen), Ref(next));
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd));
|
|
|
|
size_t bytelen = blocklen * sizeof(uint32_t);
|
|
if (cmd == SRT_CMD_KMREQ)
|
|
{
|
|
if (!out_data || !out_len)
|
|
{
|
|
m_RejectReason = SRT_REJ_IPE;
|
|
LOGC(mglog.Fatal, log << "IPE: HS/KMREQ extracted without passing target buffer!");
|
|
return false;
|
|
}
|
|
|
|
int res =
|
|
m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, out_data, Ref(*out_len), HS_VERSION_SRT1);
|
|
if (res != SRT_CMD_KMRSP)
|
|
{
|
|
m_RejectReason = SRT_REJ_IPE;
|
|
// Something went wrong.
|
|
HLOGC(mglog.Debug,
|
|
log << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res);
|
|
return false;
|
|
}
|
|
if (*out_len == 1)
|
|
{
|
|
// This means that there was an abnormal encryption situation occurred.
|
|
// This is inacceptable in case of strict encryption.
|
|
if (m_bOPT_StrictEncryption)
|
|
{
|
|
if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADSECRET)
|
|
{
|
|
m_RejectReason = SRT_REJ_BADSECRET;
|
|
}
|
|
else
|
|
{
|
|
m_RejectReason = SRT_REJ_UNSECURE;
|
|
}
|
|
LOGC(mglog.Error,
|
|
log << "interpretSrtHandshake: KMREQ result abnornal - rejecting per strict encryption");
|
|
return false;
|
|
}
|
|
}
|
|
encrypted = true;
|
|
}
|
|
else if (cmd == SRT_CMD_KMRSP)
|
|
{
|
|
int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, HS_VERSION_SRT1);
|
|
if (m_bOPT_StrictEncryption && res == -1)
|
|
{
|
|
m_RejectReason = SRT_REJ_UNSECURE;
|
|
LOGC(mglog.Error, log << "KMRSP failed - rejecting connection as per strict encryption.");
|
|
return false;
|
|
}
|
|
encrypted = true;
|
|
}
|
|
else if (cmd == SRT_CMD_NONE)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error, log << "HS KMREQ expected - none found!");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
|
|
if (NextExtensionBlock(Ref(begin), next, Ref(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_bOPT_StrictEncryption)
|
|
{
|
|
m_RejectReason = SRT_REJ_UNSECURE;
|
|
LOGC(mglog.Error,
|
|
log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting "
|
|
"per strict requirement");
|
|
return false;
|
|
}
|
|
|
|
LOGC(mglog.Error,
|
|
log << "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_CongCtl.selected_name();
|
|
if (agsm == "")
|
|
{
|
|
agsm = "live";
|
|
m_CongCtl.select("live");
|
|
}
|
|
|
|
if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG))
|
|
{
|
|
HLOGC(mglog.Debug, log << "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, Ref(blocklen), Ref(next));
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd));
|
|
|
|
const size_t bytelen = blocklen * sizeof(uint32_t);
|
|
if (cmd == SRT_CMD_SID)
|
|
{
|
|
if (!bytelen || bytelen > MAX_SID_LENGTH)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +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 lenght 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[MAX_SID_LENGTH + 1];
|
|
memset(target, 0, MAX_SID_LENGTH + 1);
|
|
memcpy(target, begin + 1, bytelen);
|
|
|
|
// Un-swap on big endian machines
|
|
ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen);
|
|
|
|
m_sStreamName = target;
|
|
HLOGC(mglog.Debug,
|
|
log << "CONNECTOR'S REQUESTED SID [" << m_sStreamName << "] (bytelen=" << bytelen
|
|
<< " blocklen=" << blocklen << ")");
|
|
}
|
|
else if (cmd == SRT_CMD_CONGESTION)
|
|
{
|
|
if (have_congctl)
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
LOGC(mglog.Error, log << "CONGCTL BLOCK REPEATED!");
|
|
return false;
|
|
}
|
|
|
|
if (!bytelen || bytelen > MAX_SID_LENGTH)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > "
|
|
<< +MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING");
|
|
return false;
|
|
}
|
|
// Declare that congctl has been received
|
|
have_congctl = true;
|
|
|
|
char target[MAX_SID_LENGTH + 1];
|
|
memset(target, 0, MAX_SID_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(mglog.Error,
|
|
log << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'");
|
|
return false;
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen
|
|
<< ")");
|
|
}
|
|
else if (cmd == SRT_CMD_FILTER)
|
|
{
|
|
if (have_filter)
|
|
{
|
|
m_RejectReason = SRT_REJ_FILTER;
|
|
LOGC(mglog.Error, log << "FILTER BLOCK REPEATED!");
|
|
return false;
|
|
}
|
|
// Declare that filter has been received
|
|
have_filter = true;
|
|
|
|
// XXX This is the maximum string, but filter config
|
|
// shall be normally limited somehow, especially if used
|
|
// together with SID!
|
|
char target[MAX_SID_LENGTH + 1];
|
|
memset(target, 0, MAX_SID_LENGTH + 1);
|
|
memcpy(target, begin + 1, bytelen);
|
|
string fltcfg = target;
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen
|
|
<< ")");
|
|
|
|
if (!checkApplyFilterConfig(fltcfg))
|
|
{
|
|
LOGC(mglog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected");
|
|
return false;
|
|
}
|
|
}
|
|
else if (cmd == SRT_CMD_NONE)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Found some block that is not interesting here. Skip this and get the next one.
|
|
HLOGC(mglog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
|
|
}
|
|
|
|
if (!NextExtensionBlock(Ref(begin), next, Ref(length)))
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Post-checks
|
|
// Check if peer declared encryption
|
|
if (!encrypted && m_CryptoSecret.len > 0)
|
|
{
|
|
if (m_bOPT_StrictEncryption)
|
|
{
|
|
m_RejectReason = SRT_REJ_UNSECURE;
|
|
LOGC(mglog.Error,
|
|
log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per strict "
|
|
"requirement.");
|
|
return false;
|
|
}
|
|
|
|
LOGC(mglog.Error,
|
|
log << "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_pCryptoControl->createFakeSndContext();
|
|
m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Because Peer did not send KMX, though Agent has pw
|
|
m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Because Peer has no PW, as has sent no KMREQ.
|
|
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(mglog.Error,
|
|
log << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live').");
|
|
return false;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Now parse your own string, if you have it.
|
|
if (m_OPT_PktFilterConfigString != "")
|
|
{
|
|
// - for rendezvous, both must be exactly the same, or only one side specified.
|
|
if (m_bRendezvous && m_OPT_PktFilterConfigString != confstr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SrtFilterConfig mycfg;
|
|
if (!ParseFilterConfig(m_OPT_PktFilterConfigString, 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<string, string>::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x)
|
|
{
|
|
mycfg.parameters[x->first] = x->second;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// On a listener, only apply those that you haven't set
|
|
for (map<string, string>::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x)
|
|
{
|
|
if (!mycfg.parameters.count(x->first))
|
|
mycfg.parameters[x->first] = x->second;
|
|
}
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters)
|
|
<< " FORGN: " << Printable(cfg.parameters));
|
|
|
|
ostringstream myos;
|
|
myos << mycfg.type;
|
|
for (map<string, string>::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x)
|
|
{
|
|
myos << "," << x->first << ":" << x->second;
|
|
}
|
|
|
|
m_OPT_PktFilterConfigString = myos.str();
|
|
|
|
HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Effective config: " << m_OPT_PktFilterConfigString);
|
|
}
|
|
else
|
|
{
|
|
// Take the foreign configuration as a good deal.
|
|
HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Good deal config: " << m_OPT_PktFilterConfigString);
|
|
m_OPT_PktFilterConfigString = confstr;
|
|
}
|
|
|
|
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size;
|
|
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
|
|
{
|
|
LOGC(mglog.Warn,
|
|
log << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to "
|
|
<< efc_max_payload_size << " bytes");
|
|
m_zOPT_ExpPayloadSize = efc_max_payload_size;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn)
|
|
{
|
|
CGuard cg(m_ConnectionLock);
|
|
|
|
HLOGC(mglog.Debug, log << "startConnect: -> " << SockaddrToString(serv_addr) << "...");
|
|
|
|
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);
|
|
|
|
// record peer/server address
|
|
delete m_pPeerAddr;
|
|
m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6;
|
|
memcpy(m_pPeerAddr, serv_addr, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
|
|
|
|
// register this socket in the rendezvous queue
|
|
// RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this
|
|
// function
|
|
#ifdef SRT_ENABLE_CONNTIMEO
|
|
uint64_t ttl = m_iConnTimeOut * uint64_t(1000);
|
|
#else
|
|
uint64_t ttl = 3000000;
|
|
#endif
|
|
// XXX DEBUG
|
|
// ttl = 0x1000000000000000;
|
|
// XXX
|
|
if (m_bRendezvous)
|
|
ttl *= 10;
|
|
ttl += CTimer::getTime();
|
|
m_pRcvQueue->registerConnector(m_SocketID, this, m_iIPversion, serv_addr, ttl);
|
|
|
|
// 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;
|
|
|
|
// This is my current configuration
|
|
if (m_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_iSndCryptoKeyLen);
|
|
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
|
|
HLOGC(mglog.Debug,
|
|
log << "startConnect (rnd): " << (whether ? "" : "NOT ")
|
|
<< " Advertising PBKEYLEN - value = " << m_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_iMSS;
|
|
m_ConnReq.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
|
|
m_ConnReq.m_iID = m_SocketID;
|
|
CIPAddress::ntop(serv_addr, m_ConnReq.m_piPeerIP, m_iIPversion);
|
|
|
|
if (forced_isn == 0)
|
|
{
|
|
// Random Initial Sequence Number (normal mode)
|
|
srand((unsigned int)CTimer::getTime());
|
|
m_iISN = m_ConnReq.m_iISN = (int32_t)(CSeqNo::m_iMaxSeqNo * (double(rand()) / RAND_MAX));
|
|
}
|
|
else
|
|
{
|
|
// Predefined ISN (for debug purposes)
|
|
m_iISN = m_ConnReq.m_iISN = forced_isn;
|
|
}
|
|
|
|
m_iLastDecSeq = m_iISN - 1;
|
|
m_iSndLastAck = m_iISN;
|
|
m_iSndLastDataAck = m_iISN;
|
|
m_iSndLastFullAck = m_iISN;
|
|
m_iSndCurrSeqNo = m_iISN - 1;
|
|
m_iSndLastAck2 = m_iISN;
|
|
m_ullSndLastAck2Time = CTimer::getTime();
|
|
|
|
// 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.m_iID = 0;
|
|
|
|
size_t hs_size = m_iMaxSRTPayloadSize;
|
|
m_ConnReq.store_to(reqpkt.m_pcData, Ref(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);
|
|
|
|
uint64_t now = CTimer::getTime();
|
|
reqpkt.m_iTimeStamp = int32_t(now - m_stats.startTime);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (" << now << "). 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_llLastReqTime = now;
|
|
m_bConnecting = true;
|
|
m_pSndQueue->sendto(serv_addr, reqpkt);
|
|
|
|
//
|
|
///
|
|
//// ---> CONTINUE TO: <PEER>.CUDT::processConnectRequest()
|
|
/// (Take the part under condition: hs.m_iReqType == URQ_INDUCTION)
|
|
//// <--- RETURN WHEN: m_pSndQueue->sendto() is called.
|
|
//// .... SKIP UNTIL m_pRcvQueue->recvfrom() HERE....
|
|
//// (the first "sendto" will not be called due to being too early)
|
|
///
|
|
//
|
|
|
|
// asynchronous connect, return immediately
|
|
if (!m_bSynRecving)
|
|
{
|
|
HLOGC(mglog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker");
|
|
return;
|
|
}
|
|
|
|
// Wait for the negotiated configurations from the peer side.
|
|
|
|
// This packet only prepares the storage where we will read the
|
|
// next incoming packet.
|
|
CPacket response;
|
|
response.setControl(UMSG_HANDSHAKE);
|
|
response.allocate(m_iMaxSRTPayloadSize);
|
|
|
|
CUDTException e;
|
|
EConnectStatus cst = CONN_CONTINUE;
|
|
|
|
while (!m_bClosing)
|
|
{
|
|
int64_t tdiff = CTimer::getTime() - m_llLastReqTime;
|
|
// avoid sending too many requests, at most 1 request per 250ms
|
|
|
|
// SHORT VERSION:
|
|
// The immediate first run of this loop WILL SKIP THIS PART, so
|
|
// the processing really begins AFTER THIS CONDITION.
|
|
//
|
|
// Note that some procedures inside may set m_llLastReqTime to 0,
|
|
// which will result of this condition to trigger immediately in
|
|
// the next iteration.
|
|
if (tdiff > 250000)
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "startConnect: LOOP: time to send (" << tdiff << " > 250000). size=" << reqpkt.getLength());
|
|
|
|
if (m_bRendezvous)
|
|
reqpkt.m_iID = m_ConnRes.m_iID;
|
|
|
|
now = CTimer::getTime();
|
|
#if ENABLE_HEAVY_LOGGING
|
|
{
|
|
CHandShake debughs;
|
|
debughs.load_from(reqpkt.m_pcData, reqpkt.getLength());
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "startConnect: REQ-TIME HIGH (" << now
|
|
<< "). cont/sending HS to peer: " << debughs.show());
|
|
}
|
|
#endif
|
|
|
|
m_llLastReqTime = now;
|
|
reqpkt.m_iTimeStamp = int32_t(now - m_stats.startTime);
|
|
m_pSndQueue->sendto(serv_addr, reqpkt);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "startConnect: LOOP: too early to send - " << tdiff << " < 250000");
|
|
}
|
|
|
|
cst = CONN_CONTINUE;
|
|
response.setLength(m_iMaxSRTPayloadSize);
|
|
if (m_pRcvQueue->recvfrom(m_SocketID, Ref(response)) > 0)
|
|
{
|
|
HLOGC(mglog.Debug, log << CONID() << "startConnect: got response for connect request");
|
|
cst = processConnectResponse(response, &e, true /*synchro*/);
|
|
|
|
HLOGC(mglog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst));
|
|
|
|
// Expected is that:
|
|
// - the peer responded with URQ_INDUCTION + cookie. This above function
|
|
// should check that and craft the URQ_CONCLUSION handshake, in which
|
|
// case this function returns CONN_CONTINUE. As an extra action taken
|
|
// for that case, we set the SECURING mode if encryption requested,
|
|
// and serialize again the handshake, possibly together with HS extension
|
|
// blocks, if HSv5 peer responded. The serialized handshake will be then
|
|
// sent again, as the loop is repeated.
|
|
// - the peer responded with URQ_CONCLUSION. This handshake was accepted
|
|
// as a connection, and for >= HSv5 the HS extension blocks have been
|
|
// also read and interpreted. In this case this function returns:
|
|
// - CONN_ACCEPT, if everything was correct - break this loop and return normally
|
|
// - CONN_REJECT in case of any problems with the delivered handshake
|
|
// (incorrect data or data conflict) - throw error exception
|
|
// - the peer responded with any of URQ_ERROR_*. - throw error exception
|
|
//
|
|
// The error exception should make the API connect() function fail, if blocking
|
|
// or mark the failure for that socket in epoll, if non-blocking.
|
|
|
|
if (cst == CONN_RENDEZVOUS)
|
|
{
|
|
// When this function returned CONN_RENDEZVOUS, this requires
|
|
// very special processing for the Rendezvous-v5 algorithm. This MAY
|
|
// involve also preparing a new handshake form, also interpreting the
|
|
// SRT handshake extension and crafting SRT handshake extension for the
|
|
// peer, which should be next sent. When this function returns CONN_CONTINUE,
|
|
// it means that it has done all that was required, however none of the below
|
|
// things has to be done (this function will do it by itself if needed).
|
|
// Otherwise the handshake rolling can be interrupted and considered complete.
|
|
cst = processRendezvous(Ref(reqpkt), response, serv_addr, true /*synchro*/, RST_OK);
|
|
if (cst == CONN_CONTINUE)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
if (cst == CONN_REJECT)
|
|
sendCtrl(UMSG_SHUTDOWN);
|
|
|
|
if (cst != CONN_CONTINUE && cst != CONN_CONFUSED)
|
|
break; // --> OUTSIDE-LOOP
|
|
|
|
// IMPORTANT
|
|
// [[using assert(m_pCryptoControl != nullptr)]];
|
|
|
|
// new request/response should be sent out immediately on receving a response
|
|
HLOGC(mglog.Debug,
|
|
log << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) << ", REQ-TIME: LOW.");
|
|
m_llLastReqTime = 0;
|
|
|
|
// Now serialize the handshake again to the existing buffer so that it's
|
|
// then sent later in this loop.
|
|
|
|
// First, set the size back to the original size, m_iMaxSRTPayloadSize because
|
|
// this is the size of the originally allocated space. It might have been
|
|
// shrunk by serializing the INDUCTION handshake (which was required before
|
|
// sending this packet to the output queue) and therefore be too
|
|
// small to store the CONCLUSION handshake (with HSv5 extensions).
|
|
reqpkt.setLength(m_iMaxSRTPayloadSize);
|
|
|
|
HLOGC(mglog.Debug, log << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength());
|
|
|
|
// NOTE: BUGFIX: SERIALIZE AGAIN.
|
|
// The original UDT code didn't do it, so it was theoretically
|
|
// turned into conclusion, but was sending still the original
|
|
// induction handshake challenge message. It was working only
|
|
// thanks to that simultaneously there were being sent handshake
|
|
// messages from a separate thread (CSndQueue::worker) from
|
|
// RendezvousQueue, this time serialized properly, which caused
|
|
// that with blocking mode there was a kinda initial "drunk
|
|
// passenger with taxi driver talk" until the RendezvousQueue sends
|
|
// (when "the time comes") the right CONCLUSION handshake
|
|
// challenge message.
|
|
//
|
|
// Now that this is fixed, the handshake messages from RendezvousQueue
|
|
// are sent only when there is a rendezvous mode or non-blocking mode.
|
|
if (!createSrtHandshake(Ref(reqpkt), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0))
|
|
{
|
|
LOGC(mglog.Error, log << "createSrtHandshake failed - REJECTING.");
|
|
cst = CONN_REJECT;
|
|
break;
|
|
}
|
|
// These last 2 parameters designate the buffer, which is in use only for SRT_CMD_KMRSP.
|
|
// If m_ConnReq.m_iVersion == HS_VERSION_UDT4, this function will do nothing,
|
|
// except just serializing the UDT handshake.
|
|
// The trick is that the HS challenge is with version HS_VERSION_UDT4, but the
|
|
// listener should respond with HS_VERSION_SRT1, if it is HSv5 capable.
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "startConnect: timeout from Q:recvfrom, looping again; cst=" << ConnectStatusStr(cst));
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
// Non-fatal assertion
|
|
if (cst == CONN_REJECT) // Might be returned by processRendezvous
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!");
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
if (CTimer::getTime() > ttl)
|
|
{
|
|
// timeout
|
|
e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// <--- OUTSIDE-LOOP
|
|
// Here will fall the break when not CONN_CONTINUE.
|
|
// CONN_RENDEZVOUS is handled by processRendezvous.
|
|
// CONN_ACCEPT will skip this and pass on.
|
|
if (cst == CONN_REJECT)
|
|
{
|
|
e = CUDTException(MJ_SETUP, MN_REJECTED, 0);
|
|
}
|
|
|
|
if (e.getErrorCode() == 0)
|
|
{
|
|
if (m_bClosing) // if the socket is closed before connection...
|
|
e = CUDTException(MJ_SETUP); // XXX NO MN ?
|
|
else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected
|
|
{
|
|
m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType);
|
|
e = CUDTException(MJ_SETUP, MN_REJECTED, 0);
|
|
}
|
|
else if ((!m_bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check
|
|
e = CUDTException(MJ_SETUP, MN_SECURITY, 0);
|
|
}
|
|
|
|
if (e.getErrorCode() != 0)
|
|
{
|
|
m_bConnecting = false;
|
|
// The process is to be abnormally terminated, remove the connector
|
|
// now because most likely no other processing part has done anything with it.
|
|
m_pRcvQueue->removeConnector(m_SocketID);
|
|
throw e;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << CONID() << "startConnect: handshake exchange succeeded");
|
|
|
|
// Parameters at the end.
|
|
HLOGC(mglog.Debug,
|
|
log << "startConnect: END. Parameters:"
|
|
" mss="
|
|
<< m_iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize()
|
|
<< " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iRTT << " bw=" << m_iBandwidth);
|
|
}
|
|
|
|
// Asynchronous connection
|
|
EConnectStatus CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT
|
|
{
|
|
EConnectStatus cst = CONN_CONTINUE;
|
|
CUDTException e;
|
|
|
|
CGuard cg(m_ConnectionLock); // FIX
|
|
HLOGC(mglog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing");
|
|
cst = processConnectResponse(pkt, &e, false);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "processAsyncConnectResponse: response processing result: " << ConnectStatusStr(cst)
|
|
<< "REQ-TIME LOW to enforce immediate response");
|
|
m_llLastReqTime = 0;
|
|
|
|
return cst;
|
|
}
|
|
|
|
bool CUDT::processAsyncConnectRequest(EReadStatus rst,
|
|
EConnectStatus cst,
|
|
const CPacket & response,
|
|
const sockaddr *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 request;
|
|
request.setControl(UMSG_HANDSHAKE);
|
|
request.allocate(m_iMaxSRTPayloadSize);
|
|
uint64_t now = CTimer::getTime();
|
|
request.m_iTimeStamp = int(now - m_stats.startTime);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "processAsyncConnectRequest: REQ-TIME: HIGH (" << now << "). Should prevent too quick responses.");
|
|
m_llLastReqTime = now;
|
|
// ID = 0, connection request
|
|
request.m_iID = !m_bRendezvous ? 0 : m_ConnRes.m_iID;
|
|
|
|
bool status = true;
|
|
|
|
if (cst == CONN_RENDEZVOUS)
|
|
{
|
|
HLOGC(mglog.Debug, log << "processAsyncConnectRequest: passing to processRendezvous");
|
|
cst = processRendezvous(Ref(request), response, serv_addr, false /*asynchro*/, rst);
|
|
if (cst == CONN_ACCEPT)
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. "
|
|
"Done.");
|
|
return true;
|
|
}
|
|
|
|
if (cst != CONN_CONTINUE)
|
|
{
|
|
// processRendezvous already set the reject reason
|
|
LOGC(mglog.Error,
|
|
log << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further.");
|
|
status = false;
|
|
}
|
|
}
|
|
else if (cst == CONN_REJECT)
|
|
{
|
|
// m_RejectReason already set at worker_ProcessAddressedPacket.
|
|
LOGC(mglog.Error,
|
|
log << "processAsyncConnectRequest: REJECT reported from HS processing, not processing further.");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// (this procedure will be also run for HSv4 rendezvous)
|
|
HLOGC(mglog.Debug, log << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength());
|
|
if (!createSrtHandshake(Ref(request), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0))
|
|
{
|
|
// All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired".
|
|
LOGC(mglog.Error, log << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing.");
|
|
status = false;
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType)
|
|
<< " to socket " << request.m_iID << " size=" << request.getLength());
|
|
}
|
|
}
|
|
|
|
if (!status)
|
|
{
|
|
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 = request.getLength();
|
|
zhs.store_to(request.m_pcData, Ref(size));
|
|
request.setLength(size);
|
|
*/
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "processAsyncConnectRequest: sending request packet, setting REQ-TIME HIGH.");
|
|
m_llLastReqTime = CTimer::getTime();
|
|
m_pSndQueue->sendto(serv_addr, request);
|
|
return status;
|
|
}
|
|
|
|
void CUDT::cookieContest()
|
|
{
|
|
if (m_SrtHsSide != HSD_DRAW)
|
|
return;
|
|
|
|
HLOGC(mglog.Debug, log << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie);
|
|
|
|
if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0)
|
|
{
|
|
// Note that it's virtually impossible that Agent's cookie is not ready, this
|
|
// shall be considered IPE.
|
|
// Not all cookies are ready, don't start the contest.
|
|
return;
|
|
}
|
|
|
|
// INITIATOR/RESPONDER role is resolved by COOKIE CONTEST.
|
|
//
|
|
// The cookie contest must be repeated every time because it
|
|
// may change the state at some point.
|
|
int better_cookie = m_ConnReq.m_iCookie - m_ConnRes.m_iCookie;
|
|
|
|
if (better_cookie > 0)
|
|
{
|
|
m_SrtHsSide = HSD_INITIATOR;
|
|
return;
|
|
}
|
|
|
|
if (better_cookie < 0)
|
|
{
|
|
m_SrtHsSide = HSD_RESPONDER;
|
|
return;
|
|
}
|
|
|
|
// DRAW! The only way to continue would be to force the
|
|
// cookies to be regenerated and to start over. But it's
|
|
// not worth a shot - this is an extremely rare case.
|
|
// This can simply do reject so that it can be started again.
|
|
|
|
// Pretend then that the cookie contest wasn't done so that
|
|
// it's done again. Cookies are baked every time anew, however
|
|
// the successful initial contest remains valid no matter how
|
|
// cookies will change.
|
|
|
|
m_SrtHsSide = HSD_DRAW;
|
|
}
|
|
|
|
EConnectStatus CUDT::processRendezvous(
|
|
ref_t<CPacket> reqpkt, const CPacket &response, const sockaddr *serv_addr, bool synchro, EReadStatus rst)
|
|
{
|
|
if (m_RdvState == CHandShake::RDV_CONNECTED)
|
|
{
|
|
HLOGC(mglog.Debug, log << "processRendezvous: already in CONNECTED state.");
|
|
return CONN_ACCEPT;
|
|
}
|
|
|
|
uint32_t kmdata[SRTDATA_MAXSIZE];
|
|
size_t kmdatasize = SRTDATA_MAXSIZE;
|
|
CPacket &rpkt = *reqpkt;
|
|
|
|
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(mglog.Error,
|
|
log << "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);
|
|
bool needs_extension = ext_flags != 0; // Initial value: received HS has extensions.
|
|
bool needs_hsrsp;
|
|
rendezvousSwitchState(Ref(rsp_type), Ref(needs_extension), Ref(needs_hsrsp));
|
|
if (rsp_type > URQ_FAILURE_TYPES)
|
|
{
|
|
m_RejectReason = RejectReasonForURQ(rsp_type);
|
|
HLOGC(mglog.Debug,
|
|
log << "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_extension = needs_extension;
|
|
|
|
// This must be done before prepareConnectionObjects().
|
|
applyResponseSettings();
|
|
|
|
// This must be done before interpreting and creating HSv5 extensions.
|
|
if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, 0))
|
|
{
|
|
// m_RejectReason already handled
|
|
HLOGC(mglog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects.");
|
|
return CONN_REJECT;
|
|
}
|
|
|
|
// Case 2.
|
|
if (needs_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_llLastReqTime = 0;
|
|
if (response.getLength() == size_t(-1))
|
|
{
|
|
m_RejectReason = SRT_REJ_IPE;
|
|
LOGC(mglog.Fatal,
|
|
log << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)");
|
|
return CONN_REJECT;
|
|
}
|
|
|
|
if (!interpretSrtHandshake(m_ConnRes, response, kmdata, &kmdatasize))
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW.");
|
|
return CONN_REJECT;
|
|
}
|
|
|
|
// Pass on, inform about the shortened response-waiting period.
|
|
HLOGC(mglog.Debug, log << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately.");
|
|
}
|
|
else
|
|
{
|
|
// 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 kmdatasize empty though.
|
|
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
|
|
if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ))
|
|
{
|
|
// 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_pCryptoControl->getKmMsg_size(0);
|
|
if (msgsize == 0)
|
|
{
|
|
switch (m_pCryptoControl->m_RcvKmState)
|
|
{
|
|
// 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(mglog.Debug,
|
|
log << "processRendezvous: No KMX recorded, status = NOSECRET. Respond with NOSECRET.");
|
|
|
|
// Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case,
|
|
// that is, copy the NOSECRET code into KMX message.
|
|
memcpy(kmdata, &m_pCryptoControl->m_RcvKmState, sizeof(int32_t));
|
|
kmdatasize = 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Remaining values:
|
|
// UNSECURED: should not fall here at alll
|
|
// 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(mglog.Error,
|
|
log << "processRendezvous: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV="
|
|
<< KmStateStr(m_pCryptoControl->m_RcvKmState)
|
|
<< " SND=" << KmStateStr(m_pCryptoControl->m_SndKmState));
|
|
return CONN_REJECT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
kmdatasize = msgsize / 4;
|
|
if (msgsize > kmdatasize * 4)
|
|
{
|
|
// Sanity check
|
|
LOGC(mglog.Error, log << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize);
|
|
memset(kmdata + (kmdatasize * 4), 0, msgsize - (kmdatasize * 4));
|
|
++kmdatasize;
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: getting KM DATA from the fore-recorded KMX from KMREQ, size="
|
|
<< kmdatasize);
|
|
memcpy(kmdata, m_pCryptoControl->getKmMsg_data(0), msgsize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "processRendezvous: no KMX flag - not extracting KM data for KMRSP");
|
|
kmdatasize = 0;
|
|
}
|
|
}
|
|
|
|
// No matter the value of needs_extension, the extension is always needed
|
|
// when HSREQ was interpreted (to store HSRSP extension).
|
|
m_ConnReq.m_extension = true;
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize);
|
|
|
|
rpkt.setLength(m_iMaxSRTPayloadSize);
|
|
if (!createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize))
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW");
|
|
m_llLastReqTime = 0;
|
|
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 || response.getLength() == size_t(-1))
|
|
{
|
|
// Actually the -1 length would be an IPE, but it's likely that this was reported already.
|
|
HLOGC(
|
|
mglog.Debug,
|
|
log << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension");
|
|
if (!interpretSrtHandshake(m_ConnRes, response, 0, 0))
|
|
{
|
|
// m_RejectReason is already set, so set the reqtype accordingly
|
|
m_ConnReq.m_iReqType = URQFailure(m_RejectReason);
|
|
}
|
|
}
|
|
// This should be false, make a kinda assert here.
|
|
if (needs_extension)
|
|
{
|
|
LOGC(mglog.Fatal, log << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS");
|
|
m_ConnReq.m_extension = false;
|
|
}
|
|
}
|
|
|
|
HLOGC(mglog.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(mglog.Debug, log << "... WON'T SEND any response, both sides considered connected");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "... WILL SEND " << RequestTypeStr(rsp_type) << " " << (m_ConnReq.m_extension ? "with" : "without")
|
|
<< " 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_extension = needs_extension;
|
|
|
|
rpkt.setLength(m_iMaxSRTPayloadSize);
|
|
if (m_RdvState == CHandShake::RDV_CONNECTED)
|
|
{
|
|
// When synchro=false, don't lock a mutex for rendezvous queue.
|
|
// This is required when this function is called in the
|
|
// receive queue worker thread - it would lock itself.
|
|
int cst = postConnect(response, true, 0, synchro);
|
|
if (cst == CONN_REJECT)
|
|
{
|
|
// m_RejectReason already set
|
|
HLOGC(mglog.Debug, log << "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(mglog.Debug, log << "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(reqpkt, Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0))
|
|
{
|
|
// m_RejectReason already set
|
|
LOGC(mglog.Error, log << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW");
|
|
m_llLastReqTime = 0;
|
|
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 uint64_t now = CTimer::getTime();
|
|
m_llLastReqTime = now;
|
|
rpkt.m_iTimeStamp = int32_t(now - m_stats.startTime);
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH ("
|
|
<< now << ").");
|
|
|
|
m_pSndQueue->sendto(serv_addr, rpkt);
|
|
|
|
return CONN_ACCEPT;
|
|
}
|
|
|
|
if (rst == RST_OK)
|
|
{
|
|
// the request time must be updated so that the next handshake can be sent out immediately
|
|
HLOGC(mglog.Debug,
|
|
log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType)
|
|
<< " REQ-TIME: LOW to send immediately, consider yourself conencted");
|
|
m_llLastReqTime = 0;
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected");
|
|
}
|
|
return CONN_CONTINUE;
|
|
}
|
|
|
|
EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTException *eout, bool synchro) 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(mglog.Debug,
|
|
log << "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.
|
|
bool bidirectional = false;
|
|
HandshakeSide hsd = m_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_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(mglog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in");
|
|
if (hsv5)
|
|
{
|
|
m_RdvState = CHandShake::RDV_CONNECTED;
|
|
}
|
|
|
|
return postConnect(response, hsv5, eout, synchro);
|
|
}
|
|
|
|
if (!response.isControl(UMSG_HANDSHAKE))
|
|
{
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
if (!response.isControl())
|
|
{
|
|
LOGC(mglog.Error, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected");
|
|
}
|
|
else
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << CONID()
|
|
<< "processConnectResponse: CONFUSED: expected UMSG_HANDSHAKE as connection not yet established, "
|
|
"got: "
|
|
<< MessageTypeStr(response.getType(), response.getExtendedType()));
|
|
}
|
|
return CONN_CONFUSED;
|
|
}
|
|
|
|
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(mglog.Error,
|
|
log << CONID()
|
|
<< "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting.");
|
|
return CONN_REJECT;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show());
|
|
if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES)
|
|
{
|
|
m_RejectReason = RejectReasonForURQ(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(mglog.Fatal, log << "MSS size " << m_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_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(mglog.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(mglog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED.");
|
|
return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous().
|
|
}
|
|
|
|
HLOGC(mglog.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(mglog.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, false /* unidirectional */))
|
|
{
|
|
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_llLastReqTime = 0;
|
|
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_llLastReqTime = 0;
|
|
return CONN_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// set cookie
|
|
if (m_ConnRes.m_iReqType == URQ_INDUCTION)
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" << hex
|
|
<< m_ConnRes.m_iCookie << " version:" << dec << 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)
|
|
{
|
|
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
|
|
|
|
if (hs_flags != SrtHSRequest::SRT_MAGIC_CODE)
|
|
{
|
|
LOGC(mglog.Warn, log << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE");
|
|
}
|
|
|
|
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 sould
|
|
// 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_extension = true;
|
|
|
|
// For HSv5, the caller is INITIATOR and the listener is RESPONDER.
|
|
// The m_bDataSender value should be completely ignored and the
|
|
// connection is always bidirectional.
|
|
bidirectional = true;
|
|
hsd = HSD_INITIATOR;
|
|
}
|
|
m_llLastReqTime = 0;
|
|
if (!createCrypter(hsd, bidirectional))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
return postConnect(response, false, eout, synchro);
|
|
}
|
|
|
|
void CUDT::applyResponseSettings()
|
|
{
|
|
// Re-configure according to the negotiated values.
|
|
m_iMSS = m_ConnRes.m_iMSS;
|
|
m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize;
|
|
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
|
|
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
|
|
m_iPeerISN = m_ConnRes.m_iISN;
|
|
m_iRcvLastAck = m_ConnRes.m_iISN;
|
|
#ifdef ENABLE_LOGGING
|
|
m_iDebugPrevLastAck = m_iRcvLastAck;
|
|
#endif
|
|
m_iRcvLastSkipAck = m_iRcvLastAck;
|
|
m_iRcvLastAckAck = m_ConnRes.m_iISN;
|
|
m_iRcvCurrSeqNo = m_ConnRes.m_iISN - 1;
|
|
m_iRcvCurrPhySeqNo = m_ConnRes.m_iISN - 1;
|
|
m_PeerID = m_ConnRes.m_iID;
|
|
memcpy(m_piSelfIP, m_ConnRes.m_piPeerIP, 16);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize
|
|
<< " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " isn=" << m_ConnRes.m_iISN
|
|
<< " peerID=" << m_ConnRes.m_iID);
|
|
}
|
|
|
|
EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTException *eout, bool synchro)
|
|
{
|
|
if (m_ConnRes.m_iVersion < HS_VERSION_SRT1)
|
|
m_ullRcvPeerStartTime = 0; // will be set correctly in SRT HS.
|
|
|
|
// This procedure isn't being executed in rendezvous because
|
|
// in rendezvous it's completed before calling this function.
|
|
if (!rendezvous)
|
|
{
|
|
// 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().
|
|
applyResponseSettings();
|
|
|
|
// 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".
|
|
bool 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.
|
|
if (ok && response.isControl())
|
|
{
|
|
ok = interpretSrtHandshake(m_ConnRes, response, 0, 0);
|
|
if (!ok && eout)
|
|
{
|
|
*eout = CUDTException(MJ_SETUP, MN_REJECTED, 0);
|
|
}
|
|
}
|
|
if (!ok) // m_RejectReason already set
|
|
return CONN_REJECT;
|
|
}
|
|
|
|
CInfoBlock ib;
|
|
ib.m_iIPversion = m_iIPversion;
|
|
CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP);
|
|
if (m_pCache->lookup(&ib) >= 0)
|
|
{
|
|
m_iRTT = ib.m_iRTT;
|
|
m_iBandwidth = ib.m_iBandwidth;
|
|
}
|
|
|
|
SRT_REJECT_REASON rr = setupCC();
|
|
if (rr != SRT_REJ_UNKNOWN)
|
|
{
|
|
m_RejectReason = rr;
|
|
return CONN_REJECT;
|
|
}
|
|
|
|
// And, I am connected too.
|
|
m_bConnecting = false;
|
|
m_bConnected = true;
|
|
|
|
// register this socket for receiving data packets
|
|
m_pRNode->m_bOnList = true;
|
|
m_pRcvQueue->setNewEntry(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::storePkt. 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 setNewEntry 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 setNewEntry 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_pRcvQueue->removeConnector(m_SocketID, synchro);
|
|
|
|
// acknowledge the management module.
|
|
CUDTSocket* s = s_UDTUnited.locate(m_SocketID);
|
|
if (!s)
|
|
{
|
|
if (eout)
|
|
{
|
|
*eout = CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
|
|
}
|
|
|
|
m_RejectReason = SRT_REJ_CLOSE;
|
|
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_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr);
|
|
CIPAddress::pton(s->m_pSelfAddr, s->m_pUDT->m_piSelfIP, s->m_iIPversion);
|
|
|
|
s->m_Status = SRTS_CONNECTED;
|
|
|
|
// acknowledde any waiting epolls to write
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
|
|
|
|
LOGC(mglog.Note, log << "Connection established to: " << SockaddrToString(m_pPeerAddr));
|
|
|
|
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_iSndCryptoKeyLen == 0)
|
|
{
|
|
m_iSndCryptoKeyLen = rcv_pbkeylen;
|
|
HLOGC(mglog.Debug, log << loghdr << ": PBKEYLEN adopted from advertised value: " << m_iSndCryptoKeyLen);
|
|
}
|
|
else if (m_iSndCryptoKeyLen != rcv_pbkeylen)
|
|
{
|
|
// Conflict. Use SRTO_SENDER flag to check if this side should accept
|
|
// the enforcement, otherwise simply let it win.
|
|
if (!m_bDataSender)
|
|
{
|
|
LOGC(mglog.Warn,
|
|
log << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_iSndCryptoKeyLen << " by "
|
|
<< rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)");
|
|
m_iSndCryptoKeyLen = rcv_pbkeylen;
|
|
}
|
|
else
|
|
{
|
|
LOGC(mglog.Warn,
|
|
log << loghdr << ": PBKEYLEN conflict - keep " << m_iSndCryptoKeyLen
|
|
<< "; peer-advertised PBKEYLEN " << rcv_pbkeylen << " rejected because Agent is SRTO_SENDER");
|
|
}
|
|
}
|
|
}
|
|
else if (enc_flags != 0)
|
|
{
|
|
LOGC(mglog.Error, log << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << loghdr << ": No encryption flags found in type field: " << typefield);
|
|
}
|
|
}
|
|
|
|
// Rendezvous
|
|
void CUDT::rendezvousSwitchState(ref_t<UDTRequestType> rsptype, ref_t<bool> needs_extension, ref_t<bool> needs_hsrsp)
|
|
{
|
|
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.
|
|
*needs_extension = false;
|
|
*needs_hsrsp = false;
|
|
|
|
string reason;
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
|
|
HLOGC(mglog.Debug, log << "rendezvousSwitchState: HS: " << m_ConnRes.show());
|
|
|
|
struct LogAtTheEnd
|
|
{
|
|
CHandShake::RendezvousState ost;
|
|
UDTRequestType orq;
|
|
const CHandShake::RendezvousState &nst;
|
|
const UDTRequestType & nrq;
|
|
bool & needext;
|
|
bool & needrsp;
|
|
string & reason;
|
|
|
|
~LogAtTheEnd()
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "rendezvousSwitchState: STATE[" << CHandShake::RdvStateStr(ost) << "->"
|
|
<< CHandShake::RdvStateStr(nst) << "] REQTYPE[" << RequestTypeStr(orq) << "->"
|
|
<< RequestTypeStr(nrq) << "] "
|
|
<< "ext:" << (needext ? (needrsp ? "HSRSP" : "HSREQ") : "NONE")
|
|
<< (reason == "" ? string() : "reason:" + reason));
|
|
}
|
|
} l_logend = {m_RdvState, req, m_RdvState, *rsptype, *needs_extension, *needs_hsrsp, reason};
|
|
|
|
#endif
|
|
|
|
switch (m_RdvState)
|
|
{
|
|
case CHandShake::RDV_INVALID:
|
|
return;
|
|
|
|
case CHandShake::RDV_WAVING:
|
|
{
|
|
if (req == URQ_WAVEAHAND)
|
|
{
|
|
m_RdvState = CHandShake::RDV_ATTENTION;
|
|
|
|
// NOTE: if this->isWinner(), attach HSREQ
|
|
*rsptype = URQ_CONCLUSION;
|
|
if (hsd == HSD_INITIATOR)
|
|
*needs_extension = true;
|
|
return;
|
|
}
|
|
|
|
if (req == URQ_CONCLUSION)
|
|
{
|
|
m_RdvState = CHandShake::RDV_FINE;
|
|
*rsptype = URQ_CONCLUSION;
|
|
|
|
*needs_extension = true; // (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.
|
|
if (hsd == HSD_RESPONDER)
|
|
*needs_hsrsp = true;
|
|
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.
|
|
*rsptype = URQ_CONCLUSION;
|
|
if (hsd == HSD_INITIATOR)
|
|
*needs_extension = true;
|
|
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(
|
|
mglog.Debug,
|
|
log << "rendezvousSwitchState: "
|
|
"{INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got CONCLUSION, remain in [ATTENTION]");
|
|
*rsptype = URQ_CONCLUSION;
|
|
*needs_extension = true; // If you expect to receive HSRSP, continue sending HSREQ
|
|
return;
|
|
}
|
|
m_RdvState = CHandShake::RDV_CONNECTED;
|
|
*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(
|
|
mglog.Warn,
|
|
log << "rendezvousSwitchState: (IPE!)"
|
|
"{RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got CONCLUSION, remain in [ATTENTION]");
|
|
*rsptype = URQ_CONCLUSION;
|
|
*needs_extension = false; // If you received WITHOUT extensions, respond WITHOUT extensions (wait
|
|
// for the right message)
|
|
return;
|
|
}
|
|
m_RdvState = CHandShake::RDV_INITIATED;
|
|
*rsptype = URQ_CONCLUSION;
|
|
*needs_extension = true;
|
|
*needs_hsrsp = true;
|
|
return;
|
|
}
|
|
|
|
LOGC(mglog.Error, log << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state.");
|
|
// Fallback for cookie draw
|
|
m_RdvState = CHandShake::RDV_INVALID;
|
|
*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.
|
|
*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.
|
|
*rsptype = URQ_CONCLUSION;
|
|
*needs_extension = true;
|
|
*needs_hsrsp = true;
|
|
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(mglog.Debug,
|
|
log << "rendezvousSwitchState: {INITIATOR}[FINE] <CONCLUSION without HSRSP. Stay in [FINE], "
|
|
"await CONCLUSION+HSRSP");
|
|
}
|
|
else if (hsd == HSD_RESPONDER)
|
|
{
|
|
// In FINE state the RESPONDER expects only to be sent AGREEMENT.
|
|
// It has previously received CONCLUSION in WAVING state and this has switched
|
|
// it to FINE state. That CONCLUSION message should have contained extension,
|
|
// so if this is a repeated CONCLUSION+HSREQ, it should be responded with
|
|
// CONCLUSION+HSRSP.
|
|
HLOGC(mglog.Debug,
|
|
log << "rendezvousSwitchState: {RESPONDER}[FINE] <CONCLUSION. Stay in [FINE], await AGREEMENT");
|
|
}
|
|
else
|
|
{
|
|
correct_switch = true;
|
|
}
|
|
|
|
if (!correct_switch)
|
|
{
|
|
*rsptype = URQ_CONCLUSION;
|
|
// initiator should send HSREQ, responder HSRSP,
|
|
// in both cases extension is needed
|
|
*needs_extension = true;
|
|
*needs_hsrsp = hsd == HSD_RESPONDER;
|
|
return;
|
|
}
|
|
|
|
m_RdvState = CHandShake::RDV_CONNECTED;
|
|
*rsptype = URQ_AGREEMENT;
|
|
return;
|
|
}
|
|
|
|
if (req == URQ_AGREEMENT)
|
|
{
|
|
// The loser case, the agreement was sent in response to conclusion that
|
|
// already carried over the HSRSP extension.
|
|
|
|
// There's a theoretical case when URQ_AGREEMENT can be received in case of
|
|
// parallel arrangement, while the agent is already in CHandShake::RDV_CONNECTED state.
|
|
// This will be dispatched in the main loop and discarded.
|
|
|
|
m_RdvState = CHandShake::RDV_CONNECTED;
|
|
*rsptype = URQ_DONE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
reason = "FINE -> CONCLUSION(agreement), AGREEMENT(done)";
|
|
break;
|
|
case CHandShake::RDV_INITIATED:
|
|
{
|
|
// In this state we just wait for URQ_AGREEMENT, which should cause it to
|
|
// switch to CONNECTED. No response required.
|
|
if (req == URQ_AGREEMENT)
|
|
{
|
|
// No matter in which state we'd be, just switch to connected.
|
|
if (m_RdvState == CHandShake::RDV_CONNECTED)
|
|
{
|
|
HLOGC(mglog.Debug, log << "<-- AGREEMENT: already connected");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "<-- AGREEMENT: switched to connected");
|
|
}
|
|
m_RdvState = CHandShake::RDV_CONNECTED;
|
|
*rsptype = URQ_DONE;
|
|
return;
|
|
}
|
|
|
|
if (req == URQ_CONCLUSION)
|
|
{
|
|
// Receiving conclusion in this state means that the other party
|
|
// didn't get our conclusion, so send it again, the same as when
|
|
// exiting the ATTENTION state.
|
|
*rsptype = URQ_CONCLUSION;
|
|
if (hsd == HSD_RESPONDER)
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "rendezvousSwitchState: "
|
|
"{RESPONDER}[INITIATED] awaits AGREEMENT, "
|
|
"got CONCLUSION, sending CONCLUSION+HSRSP");
|
|
*needs_extension = true;
|
|
*needs_hsrsp = true;
|
|
return;
|
|
}
|
|
|
|
// Loser, initiated? This may only happen in parallel arrangement, where
|
|
// the agent exchanges empty conclusion messages with the peer, simultaneously
|
|
// exchanging HSREQ-HSRSP conclusion messages. Check if THIS message contained
|
|
// HSREQ, and set responding HSRSP in that case.
|
|
if (hs_flags == 0)
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "rendezvousSwitchState: "
|
|
"{INITIATOR}[INITIATED] awaits AGREEMENT, "
|
|
"got empty CONCLUSION, STILL RESPONDING CONCLUSION+HSRSP");
|
|
}
|
|
else
|
|
{
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "rendezvousSwitchState: "
|
|
"{INITIATOR}[INITIATED] awaits AGREEMENT, "
|
|
"got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP");
|
|
}
|
|
*needs_extension = true;
|
|
*needs_hsrsp = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
reason = "INITIATED -> AGREEMENT(done)";
|
|
break;
|
|
|
|
case CHandShake::RDV_CONNECTED:
|
|
// Do nothing. This theoretically should never happen.
|
|
*rsptype = URQ_DONE;
|
|
return;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID");
|
|
// All others are treated as errors
|
|
m_RdvState = CHandShake::RDV_WAVING;
|
|
*rsptype = URQFailure(SRT_REJ_ROGUE);
|
|
}
|
|
|
|
/*
|
|
* Timestamp-based Packet Delivery (TsbPd) thread
|
|
* This thread runs only if TsbPd mode is enabled
|
|
* Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay.
|
|
*/
|
|
void *CUDT::tsbpd(void *param)
|
|
{
|
|
CUDT *self = (CUDT *)param;
|
|
|
|
THREAD_STATE_INIT("SRT:TsbPd");
|
|
|
|
CGuard::enterCS(self->m_RecvLock);
|
|
self->m_bTsbPdAckWakeup = true;
|
|
while (!self->m_bClosing)
|
|
{
|
|
int32_t current_pkt_seq = 0;
|
|
uint64_t tsbpdtime = 0;
|
|
bool rxready = false;
|
|
|
|
CGuard::enterCS(self->m_RcvBufferLock);
|
|
|
|
#ifdef SRT_ENABLE_RCVBUFSZ_MAVG
|
|
self->m_pRcvBuffer->updRcvAvgDataSize(CTimer::getTime());
|
|
#endif
|
|
|
|
if (self->m_bTLPktDrop)
|
|
{
|
|
int32_t skiptoseqno = -1;
|
|
bool passack = true; // Get next packet to wait for even if not acked
|
|
|
|
rxready = self->m_pRcvBuffer->getRcvFirstMsg(
|
|
Ref(tsbpdtime), Ref(passack), Ref(skiptoseqno), Ref(current_pkt_seq));
|
|
|
|
HLOGC(tslog.Debug,
|
|
log << boolalpha << "NEXT PKT CHECK: rdy=" << rxready << " passack=" << passack << " skipto=%"
|
|
<< skiptoseqno << " current=%" << current_pkt_seq << " buf-base=%" << self->m_iRcvLastSkipAck);
|
|
/*
|
|
* VALUES RETURNED:
|
|
*
|
|
* rxready: if true, packet at head of queue ready to play
|
|
* tsbpdtime: timestamp of packet at head of queue, ready or not. 0 if none.
|
|
* passack: if true, ready head of queue not yet acknowledged
|
|
* skiptoseqno: sequence number of packet at head of queue if ready to play but
|
|
* some preceeding packets are missing (need to be skipped). -1 if none.
|
|
*/
|
|
if (rxready)
|
|
{
|
|
/* Packet ready to play according to time stamp but... */
|
|
int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno);
|
|
|
|
if (skiptoseqno != -1 && seqlen > 0)
|
|
{
|
|
/*
|
|
* skiptoseqno != -1,
|
|
* packet ready to play but preceeded by missing packets (hole).
|
|
*/
|
|
|
|
/* Update drop/skip stats */
|
|
CGuard::enterCS(self->m_StatsLock);
|
|
self->m_stats.rcvDropTotal += seqlen;
|
|
self->m_stats.traceRcvDrop += seqlen;
|
|
/* Estimate dropped/skipped bytes from average payload */
|
|
int avgpayloadsz = self->m_pRcvBuffer->getRcvAvgPayloadSize();
|
|
self->m_stats.rcvBytesDropTotal += seqlen * avgpayloadsz;
|
|
self->m_stats.traceRcvBytesDrop += seqlen * avgpayloadsz;
|
|
CGuard::leaveCS(self->m_StatsLock);
|
|
|
|
self->dropFromLossLists(self->m_iRcvLastSkipAck,
|
|
CSeqNo::decseq(skiptoseqno)); // remove(from,to-inclusive)
|
|
self->m_pRcvBuffer->skipData(seqlen);
|
|
|
|
self->m_iRcvLastSkipAck = skiptoseqno;
|
|
|
|
#if ENABLE_LOGGING
|
|
int64_t timediff = 0;
|
|
if (tsbpdtime)
|
|
timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime());
|
|
#if ENABLE_HEAVY_LOGGING
|
|
HLOGC(tslog.Debug,
|
|
log << self->CONID() << "tsbpd: DROPSEQ: up to seq=" << CSeqNo::decseq(skiptoseqno) << " ("
|
|
<< seqlen << " packets) playable at " << FormatTime(tsbpdtime) << " delayed "
|
|
<< (timediff / 1000) << "." << (timediff % 1000) << " ms");
|
|
#endif
|
|
LOGC(dlog.Debug, log << "RCV-DROPPED packet delay=" << (timediff / 1000) << "ms");
|
|
#endif
|
|
|
|
tsbpdtime = 0; // Next sent ack will unblock
|
|
rxready = false;
|
|
}
|
|
else if (passack)
|
|
{
|
|
/* Packets ready to play but not yet acknowledged (should happen within 10ms) */
|
|
rxready = false;
|
|
tsbpdtime = 0; // Next sent ack will unblock
|
|
} /* else packet ready to play */
|
|
} /* else packets not ready to play */
|
|
}
|
|
else
|
|
{
|
|
rxready = self->m_pRcvBuffer->isRcvDataReady(Ref(tsbpdtime), Ref(current_pkt_seq));
|
|
}
|
|
CGuard::leaveCS(self->m_RcvBufferLock);
|
|
|
|
if (rxready)
|
|
{
|
|
HLOGC(tslog.Debug,
|
|
log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << current_pkt_seq << " (belated "
|
|
<< ((CTimer::getTime() - tsbpdtime) / 1000.0) << "ms)");
|
|
/*
|
|
* There are packets ready to be delivered
|
|
* signal a waiting "recv" call if there is any data available
|
|
*/
|
|
if (self->m_bSynRecving)
|
|
{
|
|
pthread_cond_signal(&self->m_RecvDataCond);
|
|
}
|
|
/*
|
|
* Set EPOLL_IN to wakeup any thread waiting on epoll
|
|
*/
|
|
self->s_UDTUnited.m_EPoll.update_events(self->m_SocketID, self->m_sPollID, UDT_EPOLL_IN, true);
|
|
CTimer::triggerEvent();
|
|
tsbpdtime = 0;
|
|
}
|
|
|
|
if (tsbpdtime != 0)
|
|
{
|
|
int64_t timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime());
|
|
/*
|
|
* Buffer at head of queue is not ready to play.
|
|
* Schedule wakeup when it will be.
|
|
*/
|
|
self->m_bTsbPdAckWakeup = false;
|
|
THREAD_PAUSED();
|
|
HLOGC(tslog.Debug,
|
|
log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << current_pkt_seq
|
|
<< " T=" << FormatTime(tsbpdtime) << " - waiting " << (timediff / 1000.0) << "ms");
|
|
CTimer::condTimedWaitUS(&self->m_RcvTsbPdCond, &self->m_RecvLock, timediff);
|
|
THREAD_RESUMED();
|
|
}
|
|
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_bTsbPdAckWakeup = true;
|
|
THREAD_PAUSED();
|
|
pthread_cond_wait(&self->m_RcvTsbPdCond, &self->m_RecvLock);
|
|
THREAD_RESUMED();
|
|
}
|
|
}
|
|
CGuard::leaveCS(self->m_RecvLock);
|
|
THREAD_EXIT();
|
|
HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING");
|
|
return NULL;
|
|
}
|
|
|
|
bool CUDT::prepareConnectionObjects(const CHandShake &hs, 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_pSndBuffer)
|
|
{
|
|
HLOGC(mglog.Debug, log << "prepareConnectionObjects: (lazy) already created.");
|
|
return true;
|
|
}
|
|
|
|
bool bidirectional = false;
|
|
if (hs.m_iVersion > HS_VERSION_UDT4)
|
|
{
|
|
bidirectional = true; // HSv5 is always bidirectional
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
if (bidirectional)
|
|
{
|
|
hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR.
|
|
}
|
|
else
|
|
{
|
|
hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize);
|
|
m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_iRcvBufSize);
|
|
// after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space.
|
|
m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2);
|
|
m_pRcvLossList = new CRcvLossList(m_iFlightFlagSize);
|
|
}
|
|
catch (...)
|
|
{
|
|
// Simply reject.
|
|
if (eout)
|
|
{
|
|
*eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
|
|
}
|
|
m_RejectReason = SRT_REJ_RESOURCE;
|
|
return false;
|
|
}
|
|
|
|
if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy)
|
|
{
|
|
m_RejectReason = SRT_REJ_RESOURCE;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket &hspkt)
|
|
{
|
|
HLOGC(mglog.Debug, log << "acceptAndRespond: setting up data according to handshake");
|
|
|
|
CGuard cg(m_ConnectionLock);
|
|
|
|
m_ullRcvPeerStartTime = 0; // will be set correctly at SRT HS
|
|
|
|
// Uses the smaller MSS between the peers
|
|
if (hs->m_iMSS > m_iMSS)
|
|
hs->m_iMSS = m_iMSS;
|
|
else
|
|
m_iMSS = hs->m_iMSS;
|
|
|
|
// exchange info for maximum flow window size
|
|
m_iFlowWindowSize = hs->m_iFlightFlagSize;
|
|
hs->m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
|
|
|
|
m_iPeerISN = hs->m_iISN;
|
|
|
|
m_iRcvLastAck = hs->m_iISN;
|
|
#ifdef ENABLE_LOGGING
|
|
m_iDebugPrevLastAck = m_iRcvLastAck;
|
|
#endif
|
|
m_iRcvLastSkipAck = m_iRcvLastAck;
|
|
m_iRcvLastAckAck = hs->m_iISN;
|
|
m_iRcvCurrSeqNo = hs->m_iISN - 1;
|
|
m_iRcvCurrPhySeqNo = hs->m_iISN - 1;
|
|
|
|
m_PeerID = hs->m_iID;
|
|
hs->m_iID = m_SocketID;
|
|
|
|
// use peer's ISN and send it back for security check
|
|
m_iISN = hs->m_iISN;
|
|
|
|
m_iLastDecSeq = m_iISN - 1;
|
|
m_iSndLastAck = m_iISN;
|
|
m_iSndLastDataAck = m_iISN;
|
|
m_iSndLastFullAck = m_iISN;
|
|
m_iSndCurrSeqNo = m_iISN - 1;
|
|
m_iSndLastAck2 = m_iISN;
|
|
m_ullSndLastAck2Time = CTimer::getTime();
|
|
|
|
// this is a reponse handshake
|
|
hs->m_iReqType = URQ_CONCLUSION;
|
|
|
|
if (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.
|
|
hs->m_extension = true;
|
|
}
|
|
|
|
// get local IP address and send the peer its IP address (because UDP cannot get local IP address)
|
|
memcpy(m_piSelfIP, hs->m_piPeerIP, 16);
|
|
CIPAddress::ntop(peer, hs->m_piPeerIP, m_iIPversion);
|
|
|
|
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
|
|
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
|
|
HLOGC(mglog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
|
|
|
|
// Prepare all structures
|
|
if (!prepareConnectionObjects(*hs, HSD_DRAW, 0))
|
|
{
|
|
HLOGC(mglog.Debug, log << "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.
|
|
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 = m_iIPversion;
|
|
CInfoBlock::convert(peer, m_iIPversion, ib.m_piIP);
|
|
if (m_pCache->lookup(&ib) >= 0)
|
|
{
|
|
m_iRTT = ib.m_iRTT;
|
|
m_iBandwidth = ib.m_iBandwidth;
|
|
}
|
|
|
|
// 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(*hs, hspkt, kmdata, &kmdatasize))
|
|
{
|
|
HLOGC(mglog.Debug, log << "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.
|
|
hs->m_iReqType = URQFailure(m_RejectReason);
|
|
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
|
|
}
|
|
|
|
SRT_REJECT_REASON rr = setupCC();
|
|
// UNKNOWN used as a "no error" value
|
|
if (rr != SRT_REJ_UNKNOWN)
|
|
{
|
|
hs->m_iReqType = URQFailure(rr);
|
|
m_RejectReason = rr;
|
|
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
|
|
}
|
|
|
|
m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6;
|
|
memcpy(m_pPeerAddr, peer, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
|
|
|
|
// And of course, it is connected.
|
|
m_bConnected = true;
|
|
|
|
// register this socket for receiving data packets
|
|
m_pRNode->m_bOnList = true;
|
|
m_pRcvQueue->setNewEntry(this);
|
|
|
|
// send the response to the peer, see listen() for more discussions about this
|
|
// XXX 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 response;
|
|
response.setControl(UMSG_HANDSHAKE);
|
|
response.allocate(size);
|
|
|
|
// This will serialize the handshake according to its current form.
|
|
HLOGC(mglog.Debug,
|
|
log << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size);
|
|
if (!createSrtHandshake(Ref(response), Ref(*hs), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize))
|
|
{
|
|
LOGC(mglog.Error, log << "acceptAndRespond: error creating handshake response");
|
|
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
|
|
}
|
|
|
|
// Set target socket ID to the value from received handshake's source ID.
|
|
response.m_iID = m_PeerID;
|
|
|
|
#if 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(response.m_pcData, response.getLength());
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "acceptAndRespond: sending HS to peer, reqtype=" << RequestTypeStr(debughs.m_iReqType)
|
|
<< " version=" << debughs.m_iVersion << " (connreq:" << RequestTypeStr(m_ConnReq.m_iReqType)
|
|
<< "), target_socket=" << response.m_iID << ", my_socket=" << debughs.m_iID);
|
|
}
|
|
#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.
|
|
m_pSndQueue->sendto(peer, response);
|
|
}
|
|
|
|
// 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, bool bidirectional)
|
|
{
|
|
// Lazy initialization
|
|
if (m_pCryptoControl)
|
|
return true;
|
|
|
|
// Write back this value, when it was just determined.
|
|
m_SrtHsSide = side;
|
|
|
|
m_pCryptoControl.reset(new CCryptoControl(this, m_SocketID));
|
|
|
|
// 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_pCryptoControl->setCryptoSecret(m_CryptoSecret);
|
|
|
|
if (bidirectional || m_bDataSender)
|
|
{
|
|
HLOGC(mglog.Debug, log << "createCrypter: setting RCV/SND KeyLen=" << m_iSndCryptoKeyLen);
|
|
m_pCryptoControl->setCryptoKeylen(m_iSndCryptoKeyLen);
|
|
}
|
|
|
|
return m_pCryptoControl->init(side, bidirectional);
|
|
}
|
|
|
|
SRT_REJECT_REASON CUDT::setupCC()
|
|
{
|
|
// Prepare configuration object,
|
|
// Create the CCC object and configure it.
|
|
|
|
// UDT also sets back the congestion window: ???
|
|
// m_dCongestionWindow = 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 precedense.
|
|
// if (bidirectional || m_bDataSender || m_bTwoWayData)
|
|
// m_bPeerTsbPd = m_bOPT_TsbPd;
|
|
|
|
// SrtCongestion will retrieve whatever parameters it needs
|
|
// from *this.
|
|
if (!m_CongCtl.configure(this))
|
|
{
|
|
return SRT_REJ_CONGESTION;
|
|
}
|
|
|
|
// Configure filter module
|
|
if (m_OPT_PktFilterConfigString != "")
|
|
{
|
|
// 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(mglog.Debug, log << "filter: Configuring Corrector: " << m_OPT_PktFilterConfigString);
|
|
if (!m_PacketFilter.configure(this, m_pRcvBuffer->getUnitQueue(), m_OPT_PktFilterConfigString))
|
|
{
|
|
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.
|
|
uint64_t min_nak_tk = m_CongCtl->minNAKInterval();
|
|
if (min_nak_tk)
|
|
m_ullMinNakInt_tk = min_nak_tk;
|
|
|
|
// Update timers
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
m_ullLastRspTime_tk = currtime_tk;
|
|
m_ullNextACKTime_tk = currtime_tk + m_ullACKInt_tk;
|
|
m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk;
|
|
m_ullLastRspAckTime_tk = currtime_tk;
|
|
m_ullLastSndTime_tk = currtime_tk;
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "setupCC: setting parameters: mss=" << m_iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize
|
|
<< " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)"
|
|
<< " rtt=" << m_iRTT << " bw=" << m_iBandwidth);
|
|
|
|
updateCC(TEV_INIT, TEV_INIT_RESET);
|
|
return SRT_REJ_UNKNOWN;
|
|
}
|
|
|
|
void CUDT::considerLegacySrtHandshake(uint64_t 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 (!isTsbPd() || !m_bDataSender)
|
|
return;
|
|
|
|
if (m_iSndHsRetryCnt <= 0)
|
|
{
|
|
HLOGC(mglog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt);
|
|
return;
|
|
}
|
|
|
|
uint64_t now = CTimer::getTime();
|
|
if (timebase != 0)
|
|
{
|
|
// 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(mglog.Debug, log << "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(mglog.Debug,
|
|
log << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt
|
|
<< " times");
|
|
return;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response");
|
|
m_iSndHsRetryCnt--;
|
|
m_ullSndHsLastTime_us = now;
|
|
sendSrtMsg(SRT_CMD_HSREQ);
|
|
}
|
|
|
|
void CUDT::checkSndTimers(Whether2RegenKm regen)
|
|
{
|
|
if (m_SrtHsSide == HSD_INITIATOR)
|
|
{
|
|
HLOGC(mglog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase");
|
|
// Legacy method for HSREQ, only if initiator.
|
|
considerLegacySrtHandshake(m_ullSndHsLastTime_us + m_iRTT * 3 / 2);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)")
|
|
<< " - not considering legacy handshake");
|
|
}
|
|
|
|
// This must be done always on sender, regardless of HS side.
|
|
// When regen == DONT_REGEN_KM, it's a handshake call, so do
|
|
// it only for initiator.
|
|
if (regen || m_SrtHsSide == HSD_INITIATOR)
|
|
{
|
|
// Don't call this function in "non-regen mode" (sending only),
|
|
// if this side is RESPONDER. This shall be called only with
|
|
// regeneration request, which is required by the sender.
|
|
if (m_pCryptoControl)
|
|
m_pCryptoControl->sendKeysToPeer(regen);
|
|
}
|
|
}
|
|
|
|
void CUDT::addressAndSend(CPacket &pkt)
|
|
{
|
|
pkt.m_iID = m_PeerID;
|
|
pkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
|
|
m_pSndQueue->sendto(m_pPeerAddr, pkt);
|
|
}
|
|
|
|
bool CUDT::close()
|
|
{
|
|
// NOTE: this function is called from within the garbage collector thread.
|
|
|
|
if (!m_bOpened)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << CONID() << " - closing socket:");
|
|
|
|
if (m_Linger.l_onoff != 0)
|
|
{
|
|
uint64_t entertime = CTimer::getTime();
|
|
|
|
HLOGC(mglog.Debug, log << CONID() << " ... (linger)");
|
|
while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) &&
|
|
(CTimer::getTime() - entertime < m_Linger.l_linger * uint64_t(1000000)))
|
|
{
|
|
// linger has been checked by previous close() call and has expired
|
|
if (m_ullLingerExpiration >= entertime)
|
|
break;
|
|
|
|
if (!m_bSynSending)
|
|
{
|
|
// if this socket enables asynchronous sending, return immediately and let GC to close it later
|
|
if (m_ullLingerExpiration == 0)
|
|
m_ullLingerExpiration = entertime + m_Linger.l_linger * uint64_t(1000000);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "CUDT::close: linger-nonblocking, setting expire time T="
|
|
<< FormatTime(m_ullLingerExpiration));
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
timespec ts;
|
|
ts.tv_sec = 0;
|
|
ts.tv_nsec = 1000000;
|
|
nanosleep(&ts, NULL);
|
|
#else
|
|
Sleep(1);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// remove this socket from the snd queue
|
|
if (m_bConnected)
|
|
m_pSndQueue->m_pSndUList->remove(this);
|
|
|
|
/*
|
|
* update_events below useless
|
|
* removing usock for EPolls right after (remove_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.
|
|
*/
|
|
// trigger any pending IO events.
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true);
|
|
// then remove itself from all epoll monitoring
|
|
try
|
|
{
|
|
for (set<int>::iterator i = m_sPollID.begin(); i != m_sPollID.end(); ++i)
|
|
s_UDTUnited.m_EPoll.remove_usock(*i, m_SocketID);
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
|
|
// 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(mglog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock");
|
|
|
|
CGuard cg(m_ConnectionLock);
|
|
|
|
// Signal the sender and recver if they are waiting for data.
|
|
releaseSynch();
|
|
|
|
HLOGC(mglog.Debug, log << CONID() << "CLOSING, removing from listener/connector");
|
|
|
|
if (m_bListening)
|
|
{
|
|
m_bListening = false;
|
|
m_pRcvQueue->removeListener(this);
|
|
}
|
|
else if (m_bConnecting)
|
|
{
|
|
m_pRcvQueue->removeConnector(m_SocketID);
|
|
}
|
|
|
|
if (m_bConnected)
|
|
{
|
|
if (!m_bShutdown)
|
|
{
|
|
HLOGC(mglog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer");
|
|
sendCtrl(UMSG_SHUTDOWN);
|
|
}
|
|
|
|
m_pCryptoControl->close();
|
|
|
|
// Store current connection information.
|
|
CInfoBlock ib;
|
|
ib.m_iIPversion = m_iIPversion;
|
|
CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP);
|
|
ib.m_iRTT = m_iRTT;
|
|
ib.m_iBandwidth = m_iBandwidth;
|
|
m_pCache->update(&ib);
|
|
|
|
m_bConnected = false;
|
|
}
|
|
|
|
if (m_bTsbPd && !pthread_equal(m_RcvTsbPdThread, pthread_t()))
|
|
{
|
|
HLOGC(mglog.Debug, log << "CLOSING, joining TSBPD thread...");
|
|
void *retval;
|
|
int ret SRT_ATR_UNUSED = pthread_join(m_RcvTsbPdThread, &retval);
|
|
HLOGC(mglog.Debug, log << "... " << (ret == 0 ? "SUCCEEDED" : "FAILED"));
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "CLOSING, joining send/receive threads");
|
|
|
|
// waiting all send and recv calls to stop
|
|
CGuard sendguard(m_SendLock);
|
|
CGuard recvguard(m_RecvLock);
|
|
|
|
// Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt(Ref(packet))
|
|
// from the processData(...) function while resetting Crypto Control.
|
|
CGuard::enterCS(m_RcvBufferLock);
|
|
m_pCryptoControl.reset();
|
|
CGuard::leaveCS(m_RcvBufferLock);
|
|
|
|
m_lSrtVersion = SRT_DEF_VERSION;
|
|
m_lPeerSrtVersion = SRT_VERSION_UNK;
|
|
m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1;
|
|
m_ullRcvPeerStartTime = 0;
|
|
|
|
m_bOpened = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Old, mostly original UDT based version of CUDT::send.
|
|
Left for historical reasons.
|
|
|
|
int CUDT::send(const char* data, int len)
|
|
{
|
|
// 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)
|
|
return 0;
|
|
|
|
// Check if the current congctl accepts the call with given parameters.
|
|
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_SEND, data, len, -1, false))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
|
|
|
|
CGuard sendguard(m_SendLock);
|
|
|
|
if (m_pSndBuffer->getCurrBufSize() == 0)
|
|
{
|
|
// delay the EXP timer to avoid mis-fired timeout
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
// (fix keepalive) m_ullLastRspTime_tk = currtime_tk;
|
|
m_ullLastRspAckTime_tk = currtime_tk;
|
|
m_iReXmitCount = 1;
|
|
}
|
|
if (sndBuffersLeft() <= 0)
|
|
{
|
|
if (!m_bSynSending)
|
|
throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0);
|
|
else
|
|
{
|
|
{
|
|
// wait here during a blocking sending
|
|
CGuard sendblock_lock(m_SendBlockLock);
|
|
if (m_iSndTimeOut < 0)
|
|
{
|
|
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth)
|
|
pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock);
|
|
}
|
|
else
|
|
{
|
|
uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * uint64_t(1000);
|
|
timespec locktime;
|
|
|
|
locktime.tv_sec = exptime / 1000000;
|
|
locktime.tv_nsec = (exptime % 1000000) * 1000;
|
|
|
|
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth && (CTimer::getTime() < exptime))
|
|
pthread_cond_timedwait(&m_SendBlockCond, &m_SendBlockLock, &locktime);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sndBuffersLeft() <= 0)
|
|
{
|
|
if (m_iSndTimeOut >= 0)
|
|
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize);
|
|
|
|
// record total time used for sending
|
|
if (m_pSndBuffer->getCurrBufSize() == 0)
|
|
m_llSndDurationCounter = CTimer::getTime();
|
|
|
|
// insert the user buffer into the sending list
|
|
m_pSndBuffer->addBuffer(data, size); // inorder=false, ttl=-1
|
|
|
|
// insert this socket to snd list if it is not on the list yet
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
|
|
|
|
if (sndBuffersLeft() <= 0)
|
|
{
|
|
// write is not available any more
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
*/
|
|
|
|
int CUDT::receiveBuffer(char *data, int len)
|
|
{
|
|
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, -1, false))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
|
|
|
|
CGuard recvguard(m_RecvLock);
|
|
|
|
if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
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(mglog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF");
|
|
return 0;
|
|
}
|
|
HLOGC(mglog.Debug,
|
|
log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no")
|
|
<< " SHUTDOWN. Reporting as BROKEN.");
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
}
|
|
|
|
if (!m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
if (!m_bSynRecving)
|
|
{
|
|
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
|
|
}
|
|
else
|
|
{
|
|
/* Kick TsbPd thread to schedule next wakeup (if running) */
|
|
if (m_iRcvTimeOut < 0)
|
|
{
|
|
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// Do not block forever, check connection status each 1 sec.
|
|
CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, 1000000);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint64_t exptime = CTimer::getTime() + m_iRcvTimeOut * 1000;
|
|
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, m_iRcvTimeOut * 1000);
|
|
if (CTimer::getTime() >= exptime)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// throw an exception if not connected
|
|
if (!m_bConnected)
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// See at the beginning
|
|
if (!m_bMessageAPI && m_bShutdown)
|
|
{
|
|
HLOGC(mglog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF");
|
|
return 0;
|
|
}
|
|
HLOGC(mglog.Debug,
|
|
log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no")
|
|
<< " SHUTDOWN. Reporting as BROKEN.");
|
|
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
}
|
|
|
|
const int res = m_pRcvBuffer->readBuffer(data, len);
|
|
|
|
/* Kick TsbPd thread to schedule next wakeup (if running) */
|
|
if (m_bTsbPd)
|
|
{
|
|
HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup");
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
}
|
|
|
|
if (!m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// read is not available any more
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
|
|
}
|
|
|
|
if ((res <= 0) && (m_iRcvTimeOut >= 0))
|
|
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
|
|
|
|
return res;
|
|
}
|
|
|
|
void CUDT::checkNeedDrop(ref_t<bool> bCongestion)
|
|
{
|
|
if (!m_bPeerTLPktDrop)
|
|
return;
|
|
|
|
if (!m_bMessageAPI)
|
|
{
|
|
LOGC(dlog.Error, log << "The SRTO_TLPKTDROP flag can only be used with message API.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
|
|
}
|
|
|
|
int bytes, timespan_ms;
|
|
// (returns buffer size in buffer units, ignored)
|
|
m_pSndBuffer->getCurrBufSize(Ref(bytes), Ref(timespan_ms));
|
|
|
|
// high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms)
|
|
// Minimum value must accomodate an I-Frame (~8 x average frame size)
|
|
// >>need picture rate or app to set min treshold
|
|
// >>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
|
|
int threshold_ms = 0;
|
|
if (m_iOPT_SndDropDelay >= 0)
|
|
{
|
|
threshold_ms = std::max(m_iPeerTsbPdDelay_ms + m_iOPT_SndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) +
|
|
(2 * COMM_SYN_INTERVAL_US / 1000);
|
|
}
|
|
|
|
if (threshold_ms && timespan_ms > threshold_ms)
|
|
{
|
|
// protect packet retransmission
|
|
CGuard::enterCS(m_RecvAckLock);
|
|
int dbytes;
|
|
int dpkts = m_pSndBuffer->dropLateData(dbytes, CTimer::getTime() - (threshold_ms * 1000));
|
|
if (dpkts > 0)
|
|
{
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceSndDrop += dpkts;
|
|
m_stats.sndDropTotal += dpkts;
|
|
m_stats.traceSndBytesDrop += dbytes;
|
|
m_stats.sndBytesDropTotal += dbytes;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
int32_t realack = m_iSndLastDataAck;
|
|
#endif
|
|
int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts);
|
|
|
|
m_iSndLastAck = fakeack;
|
|
m_iSndLastDataAck = fakeack;
|
|
|
|
int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck);
|
|
m_pSndLossList->remove(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;
|
|
}
|
|
LOGC(dlog.Error, log << "SND-DROPPED " << dpkts << " packets - lost delaying for " << timespan_ms << "ms");
|
|
|
|
HLOGC(dlog.Debug,
|
|
log << "drop,now " << CTimer::getTime() << "us," << realack << "-" << m_iSndCurrSeqNo << " seqs,"
|
|
<< dpkts << " pkts," << dbytes << " bytes," << timespan_ms << " ms");
|
|
}
|
|
*bCongestion = true;
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
}
|
|
else if (timespan_ms > (m_iPeerTsbPdDelay_ms / 2))
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "cong, NOW: " << CTimer::getTime() << "us, BYTES " << bytes << ", TMSPAN " << timespan_ms << "ms");
|
|
|
|
*bCongestion = true;
|
|
}
|
|
}
|
|
|
|
int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, uint64_t srctime)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
mctrl.msgttl = msttl;
|
|
mctrl.inorder = inorder;
|
|
mctrl.srctime = srctime;
|
|
return this->sendmsg2(data, len, Ref(mctrl));
|
|
}
|
|
|
|
int CUDT::sendmsg2(const char *data, int len, ref_t<SRT_MSGCTRL> r_mctrl)
|
|
{
|
|
SRT_MSGCTRL &mctrl = *r_mctrl;
|
|
bool bCongestion = false;
|
|
|
|
// 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(dlog.Error, log << "INVALID: Data size for sending declared with length: " << len);
|
|
return 0;
|
|
}
|
|
|
|
int msttl = mctrl.msgttl;
|
|
bool inorder = 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_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_bMessageAPI && len > int(m_iSndBufSize * m_iMaxSRTPayloadSize))
|
|
{
|
|
LOGC(dlog.Error,
|
|
log << "Message length (" << len << ") exceeds the size of sending buffer: "
|
|
<< (m_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_pCryptoControl->isSndEncryptionOK())
|
|
{
|
|
LOGC(dlog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending
|
|
rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0);
|
|
}
|
|
*/
|
|
|
|
CGuard sendguard(m_SendLock);
|
|
|
|
if (m_pSndBuffer->getCurrBufSize() == 0)
|
|
{
|
|
// delay the EXP timer to avoid mis-fired timeout
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
|
|
CGuard ack_lock(m_RecvAckLock);
|
|
m_ullLastRspAckTime_tk = currtime_tk; // (fix keepalive)
|
|
m_iReXmitCount = 1; // can be modified in checkRexmitTimer and processCtrlAck (receiver's thread)
|
|
}
|
|
|
|
// checkNeedDrop(...) may lock m_RecvAckLock
|
|
// to modify m_pSndBuffer and m_pSndLossList
|
|
checkNeedDrop(Ref(bCongestion));
|
|
|
|
int minlen = 1; // Minimum sender buffer space required for STREAM API
|
|
if (m_bMessageAPI)
|
|
{
|
|
// For MESSAGE API the minimum outgoing buffer space required is
|
|
// the size that can carry over the whole message as passed here.
|
|
minlen = (len + m_iMaxSRTPayloadSize - 1) / m_iMaxSRTPayloadSize;
|
|
}
|
|
|
|
if (sndBuffersLeft() < minlen)
|
|
{
|
|
//>>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_bSynSending)
|
|
throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0);
|
|
|
|
{
|
|
// wait here during a blocking sending
|
|
CGuard sendblock_lock(m_SendBlockLock);
|
|
|
|
if (m_iSndTimeOut < 0)
|
|
{
|
|
while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth)
|
|
pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock);
|
|
}
|
|
else
|
|
{
|
|
const uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * uint64_t(1000);
|
|
|
|
while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth && exptime > CTimer::getTime())
|
|
CTimer::condTimedWaitUS(&m_SendBlockCond, &m_SendBlockLock, m_iSndTimeOut * uint64_t(1000));
|
|
}
|
|
}
|
|
|
|
// 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 alredy returned. If buffer availaible,
|
|
* we test twice if this code is outside the else section.
|
|
* This fix move it in the else (blocking-mode) section
|
|
*/
|
|
if (sndBuffersLeft() < minlen)
|
|
{
|
|
if (m_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(mglog.Fatal,
|
|
log << "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)
|
|
{
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.sndDurationCounter = CTimer::getTime();
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
|
|
int size = len;
|
|
if (!m_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);
|
|
}
|
|
|
|
{
|
|
CGuard recvAckLock(m_RecvAckLock);
|
|
// insert the user buffer into the sending list
|
|
// This should be protected by a mutex. m_SendLock does this.
|
|
m_pSndBuffer->addBuffer(data, size, mctrl.msgttl, mctrl.inorder, mctrl.srctime, Ref(mctrl.msgno));
|
|
HLOGC(dlog.Debug, log << CONID() << "sock:SENDING srctime: " << mctrl.srctime << "us DATA SIZE: " << size);
|
|
|
|
if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried.
|
|
{
|
|
// write is not available any more
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
|
|
}
|
|
}
|
|
|
|
// insert this socket to the snd list if it is not on the list yet
|
|
// m_pSndUList->pop may lock CSndUList::m_ListLock and then m_RecvAckLock
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::rescheduleIf(bCongestion));
|
|
|
|
#ifdef SRT_ENABLE_ECN
|
|
if (bCongestion)
|
|
throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0);
|
|
#endif /* SRT_ENABLE_ECN */
|
|
return size;
|
|
}
|
|
|
|
int CUDT::recv(char *data, int len)
|
|
{
|
|
if (!m_bConnected || !m_CongCtl.ready())
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
if (len <= 0)
|
|
{
|
|
LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recv.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
if (m_bMessageAPI)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
return receiveMessage(data, len, Ref(mctrl));
|
|
}
|
|
|
|
return receiveBuffer(data, len);
|
|
}
|
|
|
|
int CUDT::recvmsg(char *data, int len, uint64_t &srctime)
|
|
{
|
|
if (!m_bConnected || !m_CongCtl.ready())
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
if (len <= 0)
|
|
{
|
|
LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
if (m_bMessageAPI)
|
|
{
|
|
SRT_MSGCTRL mctrl = srt_msgctrl_default;
|
|
int ret = receiveMessage(data, len, Ref(mctrl));
|
|
srctime = mctrl.srctime;
|
|
return ret;
|
|
}
|
|
|
|
return receiveBuffer(data, len);
|
|
}
|
|
|
|
int CUDT::recvmsg2(char *data, int len, ref_t<SRT_MSGCTRL> mctrl)
|
|
{
|
|
if (!m_bConnected || !m_CongCtl.ready())
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
|
|
if (len <= 0)
|
|
{
|
|
LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg.");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
|
|
}
|
|
|
|
if (m_bMessageAPI)
|
|
return receiveMessage(data, len, mctrl);
|
|
|
|
return receiveBuffer(data, len);
|
|
}
|
|
|
|
int CUDT::receiveMessage(char *data, int len, ref_t<SRT_MSGCTRL> r_mctrl)
|
|
{
|
|
SRT_MSGCTRL &mctrl = *r_mctrl;
|
|
// 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.
|
|
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, -1, false))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0);
|
|
|
|
CGuard recvguard(m_RecvLock);
|
|
|
|
/* 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_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)
|
|
{
|
|
int res = m_pRcvBuffer->readMsg(data, len);
|
|
mctrl.srctime = 0;
|
|
|
|
/* Kick TsbPd thread to schedule next wakeup (if running) */
|
|
if (m_bTsbPd)
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
|
|
if (!m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// read is not available any more
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
|
|
}
|
|
|
|
if (res == 0)
|
|
{
|
|
if (!m_bMessageAPI && m_bShutdown)
|
|
return 0;
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
}
|
|
else
|
|
return res;
|
|
}
|
|
|
|
if (!m_bSynRecving)
|
|
{
|
|
|
|
int res = m_pRcvBuffer->readMsg(data, len, r_mctrl);
|
|
if (res == 0)
|
|
{
|
|
// read is not available any more
|
|
|
|
// Kick TsbPd thread to schedule next wakeup (if running)
|
|
if (m_bTsbPd)
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
|
|
// Shut up EPoll if no more messages in non-blocking mode
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
|
|
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
|
|
}
|
|
else
|
|
{
|
|
if (!m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// Kick TsbPd thread to schedule next wakeup (if running)
|
|
if (m_bTsbPd)
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
|
|
// Shut up EPoll if no more messages in non-blocking mode
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
|
|
|
|
// After signaling the tsbpd for ready data, report the bandwidth.
|
|
double bw SRT_ATR_UNUSED = Bps2Mbps(m_iBandwidth * m_iMaxSRTPayloadSize);
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth
|
|
<< " buffers per second)");
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
int res = 0;
|
|
bool timeout = false;
|
|
// Do not block forever, check connection status each 1 sec.
|
|
uint64_t recvtmo = m_iRcvTimeOut < 0 ? 1000 : m_iRcvTimeOut;
|
|
|
|
do
|
|
{
|
|
if (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady()))
|
|
{
|
|
/* Kick TsbPd thread to schedule next wakeup (if running) */
|
|
if (m_bTsbPd)
|
|
{
|
|
HLOGP(tslog.Debug, "recvmsg: KICK tsbpd()");
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
}
|
|
|
|
do
|
|
{
|
|
if (CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, recvtmo * 1000) == ETIMEDOUT)
|
|
{
|
|
if (!(m_iRcvTimeOut < 0))
|
|
timeout = true;
|
|
HLOGP(tslog.Debug, "recvmsg: DATA COND: EXPIRED -- trying to get data anyway");
|
|
}
|
|
else
|
|
{
|
|
HLOGP(tslog.Debug, "recvmsg: DATA COND: KICKED.");
|
|
}
|
|
} while (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady()));
|
|
}
|
|
|
|
/* XXX DEBUG STUFF - enable when required
|
|
LOGC(dlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected
|
|
<< " CLOSING " << m_bClosing << " TMOUT " << timeout
|
|
<< " NMSG " << m_pRcvBuffer->getRcvMsgNum());
|
|
*/
|
|
|
|
res = m_pRcvBuffer->readMsg(data, len, r_mctrl);
|
|
|
|
if (m_bBroken || m_bClosing)
|
|
{
|
|
if (!m_bMessageAPI && m_bShutdown)
|
|
return 0;
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
}
|
|
else if (!m_bConnected)
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
} while ((res == 0) && !timeout);
|
|
|
|
if (!m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// 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)");
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
}
|
|
|
|
// Shut up EPoll if no more messages in non-blocking mode
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
|
|
}
|
|
|
|
// Unblock when required
|
|
// LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT");
|
|
|
|
if ((res <= 0) && (m_iRcvTimeOut >= 0))
|
|
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, -1, false))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
|
|
|
|
if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK())
|
|
{
|
|
LOGC(dlog.Error,
|
|
log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected.");
|
|
throw CUDTException(MJ_SETUP, MN_SECURITY, 0);
|
|
}
|
|
|
|
CGuard sendguard(m_SendLock);
|
|
|
|
if (m_pSndBuffer->getCurrBufSize() == 0)
|
|
{
|
|
// delay the EXP timer to avoid mis-fired timeout
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
// (fix keepalive) m_ullLastRspTime_tk = currtime_tk;
|
|
m_ullLastRspAckTime_tk = currtime_tk;
|
|
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);
|
|
|
|
{
|
|
CGuard lk(m_SendBlockLock);
|
|
|
|
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth)
|
|
pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock);
|
|
}
|
|
|
|
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)
|
|
{
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.sndDurationCounter = CTimer::getTime();
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
|
|
{
|
|
CGuard 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
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
|
|
}
|
|
}
|
|
|
|
// insert this socket to snd list if it is not on the list yet
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
|
|
}
|
|
|
|
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) && !m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
if (!m_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, -1, false))
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
|
|
|
|
if (m_bTsbPd)
|
|
{
|
|
LOGC(dlog.Error, log << "Reading from file is incompatible with TSBPD mode and would cause a deadlock\n");
|
|
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
|
|
}
|
|
|
|
CGuard 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);
|
|
}
|
|
|
|
pthread_mutex_lock(&m_RecvDataLock);
|
|
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
|
|
pthread_cond_wait(&m_RecvDataCond, &m_RecvDataLock);
|
|
pthread_mutex_unlock(&m_RecvDataLock);
|
|
|
|
if (!m_bConnected)
|
|
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
|
|
else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
|
|
if (!m_bMessageAPI && m_bShutdown)
|
|
return 0;
|
|
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
|
|
}
|
|
|
|
unitsize = int((torecv == -1 || torecv >= block) ? block : torecv);
|
|
recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize);
|
|
|
|
if (recvsize > 0)
|
|
{
|
|
torecv -= recvsize;
|
|
offset += recvsize;
|
|
}
|
|
}
|
|
|
|
if (!m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
// read is not available any more
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_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);
|
|
|
|
CGuard statsguard(m_StatsLock);
|
|
|
|
uint64_t currtime = CTimer::getTime();
|
|
perf->msTimeStamp = (currtime - m_stats.startTime) / 1000;
|
|
|
|
perf->pktSent = m_stats.traceSent;
|
|
perf->pktRecv = m_stats.traceRecv;
|
|
perf->pktSndLoss = m_stats.traceSndLoss;
|
|
perf->pktRcvLoss = m_stats.traceRcvLoss;
|
|
perf->pktRetrans = m_stats.traceRetrans;
|
|
perf->pktRcvRetrans = m_stats.traceRcvRetrans;
|
|
perf->pktSentACK = m_stats.sentACK;
|
|
perf->pktRecvACK = m_stats.recvACK;
|
|
perf->pktSentNAK = m_stats.sentNAK;
|
|
perf->pktRecvNAK = m_stats.recvNAK;
|
|
perf->usSndDuration = m_stats.sndDuration;
|
|
perf->pktReorderDistance = m_stats.traceReorderDistance;
|
|
perf->pktReorderTolerance = m_iReorderTolerance;
|
|
perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime;
|
|
perf->pktRcvBelated = m_stats.traceRcvBelated;
|
|
|
|
perf->pktSndFilterExtra = m_stats.sndFilterExtra;
|
|
perf->pktRcvFilterExtra = m_stats.rcvFilterExtra;
|
|
perf->pktRcvFilterSupply = m_stats.rcvFilterSupply;
|
|
perf->pktRcvFilterLoss = m_stats.rcvFilterLoss;
|
|
|
|
/* perf byte counters include all headers (SRT+UDP+IP) */
|
|
const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE;
|
|
perf->byteSent = m_stats.traceBytesSent + (m_stats.traceSent * pktHdrSize);
|
|
perf->byteRecv = m_stats.traceBytesRecv + (m_stats.traceRecv * pktHdrSize);
|
|
perf->byteRetrans = m_stats.traceBytesRetrans + (m_stats.traceRetrans * pktHdrSize);
|
|
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
|
|
perf->byteRcvLoss = m_stats.traceRcvBytesLoss + (m_stats.traceRcvLoss * pktHdrSize);
|
|
#endif
|
|
|
|
perf->pktSndDrop = m_stats.traceSndDrop;
|
|
perf->pktRcvDrop = m_stats.traceRcvDrop + m_stats.traceRcvUndecrypt;
|
|
perf->byteSndDrop = m_stats.traceSndBytesDrop + (m_stats.traceSndDrop * pktHdrSize);
|
|
perf->byteRcvDrop =
|
|
m_stats.traceRcvBytesDrop + (m_stats.traceRcvDrop * pktHdrSize) + m_stats.traceRcvBytesUndecrypt;
|
|
perf->pktRcvUndecrypt = m_stats.traceRcvUndecrypt;
|
|
perf->byteRcvUndecrypt = m_stats.traceRcvBytesUndecrypt;
|
|
|
|
perf->pktSentTotal = m_stats.sentTotal;
|
|
perf->pktRecvTotal = m_stats.recvTotal;
|
|
perf->pktSndLossTotal = m_stats.sndLossTotal;
|
|
perf->pktRcvLossTotal = m_stats.rcvLossTotal;
|
|
perf->pktRetransTotal = m_stats.retransTotal;
|
|
perf->pktSentACKTotal = m_stats.sentACKTotal;
|
|
perf->pktRecvACKTotal = m_stats.recvACKTotal;
|
|
perf->pktSentNAKTotal = m_stats.sentNAKTotal;
|
|
perf->pktRecvNAKTotal = m_stats.recvNAKTotal;
|
|
perf->usSndDurationTotal = m_stats.m_sndDurationTotal;
|
|
|
|
perf->byteSentTotal = m_stats.bytesSentTotal + (m_stats.sentTotal * pktHdrSize);
|
|
perf->byteRecvTotal = m_stats.bytesRecvTotal + (m_stats.recvTotal * pktHdrSize);
|
|
perf->byteRetransTotal = m_stats.bytesRetransTotal + (m_stats.retransTotal * pktHdrSize);
|
|
perf->pktSndFilterExtraTotal = m_stats.sndFilterExtraTotal;
|
|
perf->pktRcvFilterExtraTotal = m_stats.rcvFilterExtraTotal;
|
|
perf->pktRcvFilterSupplyTotal = m_stats.rcvFilterSupplyTotal;
|
|
perf->pktRcvFilterLossTotal = m_stats.rcvFilterLossTotal;
|
|
|
|
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
|
|
perf->byteRcvLossTotal = m_stats.rcvBytesLossTotal + (m_stats.rcvLossTotal * pktHdrSize);
|
|
#endif
|
|
perf->pktSndDropTotal = m_stats.sndDropTotal;
|
|
perf->pktRcvDropTotal = m_stats.rcvDropTotal + m_stats.m_rcvUndecryptTotal;
|
|
perf->byteSndDropTotal = m_stats.sndBytesDropTotal + (m_stats.sndDropTotal * pktHdrSize);
|
|
perf->byteRcvDropTotal =
|
|
m_stats.rcvBytesDropTotal + (m_stats.rcvDropTotal * pktHdrSize) + m_stats.m_rcvBytesUndecryptTotal;
|
|
perf->pktRcvUndecryptTotal = m_stats.m_rcvUndecryptTotal;
|
|
perf->byteRcvUndecryptTotal = m_stats.m_rcvBytesUndecryptTotal;
|
|
//<
|
|
|
|
double interval = double(currtime - m_stats.lastSampleTime);
|
|
|
|
//>mod
|
|
perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval;
|
|
perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval;
|
|
//<
|
|
|
|
perf->usPktSndPeriod = m_ullInterval_tk / double(m_ullCPUFrequency);
|
|
perf->pktFlowWindow = m_iFlowWindowSize;
|
|
perf->pktCongestionWindow = (int)m_dCongestionWindow;
|
|
perf->pktFlightSize = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) - 1;
|
|
perf->msRTT = (double)m_iRTT / 1000.0;
|
|
//>new
|
|
perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0;
|
|
perf->msRcvTsbPdDelay = m_bTsbPd ? m_iTsbPdDelay_ms : 0;
|
|
perf->byteMSS = m_iMSS;
|
|
|
|
perf->mbpsMaxBW = m_llMaxBW > 0 ? Bps2Mbps(m_llMaxBW) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0;
|
|
|
|
//<
|
|
uint32_t availbw = (uint64_t)(m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth);
|
|
|
|
perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize));
|
|
|
|
if (pthread_mutex_trylock(&m_ConnectionLock) == 0)
|
|
{
|
|
if (m_pSndBuffer)
|
|
{
|
|
#ifdef SRT_ENABLE_SNDBUFSZ_MAVG
|
|
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(Ref(perf->byteSndBuf), Ref(perf->msSndBuf));
|
|
}
|
|
else
|
|
{
|
|
perf->pktSndBuf = m_pSndBuffer->getAvgBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf));
|
|
}
|
|
#else
|
|
perf->pktSndBuf = m_pSndBuffer->getCurrBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf));
|
|
#endif
|
|
perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize);
|
|
//<
|
|
perf->byteAvailSndBuf = (m_iSndBufSize - perf->pktSndBuf) * m_iMSS;
|
|
}
|
|
else
|
|
{
|
|
perf->byteAvailSndBuf = 0;
|
|
// new>
|
|
perf->pktSndBuf = 0;
|
|
perf->byteSndBuf = 0;
|
|
perf->msSndBuf = 0;
|
|
//<
|
|
}
|
|
|
|
if (m_pRcvBuffer)
|
|
{
|
|
perf->byteAvailRcvBuf = m_pRcvBuffer->getAvailBufSize() * m_iMSS;
|
|
// new>
|
|
#ifdef SRT_ENABLE_RCVBUFSZ_MAVG
|
|
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->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf);
|
|
#endif
|
|
//<
|
|
}
|
|
else
|
|
{
|
|
perf->byteAvailRcvBuf = 0;
|
|
// new>
|
|
perf->pktRcvBuf = 0;
|
|
perf->byteRcvBuf = 0;
|
|
perf->msRcvBuf = 0;
|
|
//<
|
|
}
|
|
|
|
pthread_mutex_unlock(&m_ConnectionLock);
|
|
}
|
|
else
|
|
{
|
|
perf->byteAvailSndBuf = 0;
|
|
perf->byteAvailRcvBuf = 0;
|
|
// new>
|
|
perf->pktSndBuf = 0;
|
|
perf->byteSndBuf = 0;
|
|
perf->msSndBuf = 0;
|
|
|
|
perf->byteRcvBuf = 0;
|
|
perf->msRcvBuf = 0;
|
|
//<
|
|
}
|
|
|
|
if (clear)
|
|
{
|
|
m_stats.traceSndDrop = 0;
|
|
m_stats.traceRcvDrop = 0;
|
|
m_stats.traceSndBytesDrop = 0;
|
|
m_stats.traceRcvBytesDrop = 0;
|
|
m_stats.traceRcvUndecrypt = 0;
|
|
m_stats.traceRcvBytesUndecrypt = 0;
|
|
// new>
|
|
m_stats.traceBytesSent = m_stats.traceBytesRecv = m_stats.traceBytesRetrans = 0;
|
|
//<
|
|
m_stats.traceSent = m_stats.traceRecv = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans =
|
|
m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0;
|
|
m_stats.sndDuration = 0;
|
|
m_stats.traceRcvRetrans = 0;
|
|
m_stats.traceRcvBelated = 0;
|
|
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
|
|
m_stats.traceRcvBytesLoss = 0;
|
|
#endif
|
|
|
|
m_stats.sndFilterExtra = 0;
|
|
m_stats.rcvFilterExtra = 0;
|
|
|
|
m_stats.rcvFilterSupply = 0;
|
|
m_stats.rcvFilterLoss = 0;
|
|
|
|
m_stats.lastSampleTime = currtime;
|
|
}
|
|
}
|
|
|
|
void CUDT::updateCC(ETransmissionEvent evt, 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(mglog.Error,
|
|
log << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY")
|
|
<< "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created"));
|
|
|
|
return;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "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<EventVariant::INIT>();
|
|
// false = TEV_INIT_RESET: in the beginning, or when MAXBW was changed.
|
|
|
|
if (only_input && m_llMaxBW)
|
|
{
|
|
HLOGC(mglog.Debug, log << "updateCC/TEV_INIT: non-RESET stage and m_llMaxBW already set to " << m_llMaxBW);
|
|
// Don't change
|
|
}
|
|
else // either m_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 requst in-buffer sampling
|
|
// Bytes/s
|
|
int bw = m_llMaxBW != 0 ? m_llMaxBW : // When used SRTO_MAXBW
|
|
m_llInputBW != 0 ? withOverhead(m_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_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 reate if the bandwidth is set
|
|
const bool disable_in_rate_calc = (bw != 0);
|
|
m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc);
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "updateCC/TEV_INIT: updating BW=" << m_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)
|
|
{
|
|
// Specific part done when MaxBW is set to 0 (auto) and InputBW is 0.
|
|
// This requests internal input rate sampling.
|
|
if (m_llMaxBW == 0 && m_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(inputbw)); // Bytes/sec
|
|
}
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "udpateCC: 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_ullInterval_tk = (uint64_t)(m_CongCtl->pktSndPeriod_us() * m_ullCPUFrequency);
|
|
m_dCongestionWindow = m_CongCtl->cgWindowSize();
|
|
#if ENABLE_HEAVY_LOGGING
|
|
HLOGC(mglog.Debug,
|
|
log << "updateCC: updated values from congctl: interval=" << m_ullInterval_tk << "tk ("
|
|
<< m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << m_dCongestionWindow);
|
|
#endif
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt));
|
|
|
|
#if 0 // debug
|
|
static int callcnt = 0;
|
|
if (!(callcnt++ % 250)) cerr << "SndPeriod=" << (m_ullInterval_tk/m_ullCPUFrequency) << "\n");
|
|
|
|
#endif
|
|
}
|
|
|
|
void CUDT::initSynch()
|
|
{
|
|
pthread_mutex_init(&m_SendBlockLock, NULL);
|
|
pthread_cond_init(&m_SendBlockCond, NULL);
|
|
pthread_mutex_init(&m_RecvDataLock, NULL);
|
|
pthread_cond_init(&m_RecvDataCond, NULL);
|
|
pthread_mutex_init(&m_SendLock, NULL);
|
|
pthread_mutex_init(&m_RecvLock, NULL);
|
|
pthread_mutex_init(&m_RcvLossLock, NULL);
|
|
pthread_mutex_init(&m_RecvAckLock, NULL);
|
|
pthread_mutex_init(&m_RcvBufferLock, NULL);
|
|
pthread_mutex_init(&m_ConnectionLock, NULL);
|
|
pthread_mutex_init(&m_StatsLock, NULL);
|
|
|
|
memset(&m_RcvTsbPdThread, 0, sizeof m_RcvTsbPdThread);
|
|
pthread_cond_init(&m_RcvTsbPdCond, NULL);
|
|
}
|
|
|
|
void CUDT::destroySynch()
|
|
{
|
|
pthread_mutex_destroy(&m_SendBlockLock);
|
|
pthread_cond_destroy(&m_SendBlockCond);
|
|
pthread_mutex_destroy(&m_RecvDataLock);
|
|
pthread_cond_destroy(&m_RecvDataCond);
|
|
pthread_mutex_destroy(&m_SendLock);
|
|
pthread_mutex_destroy(&m_RecvLock);
|
|
pthread_mutex_destroy(&m_RcvLossLock);
|
|
pthread_mutex_destroy(&m_RecvAckLock);
|
|
pthread_mutex_destroy(&m_RcvBufferLock);
|
|
pthread_mutex_destroy(&m_ConnectionLock);
|
|
pthread_mutex_destroy(&m_StatsLock);
|
|
pthread_cond_destroy(&m_RcvTsbPdCond);
|
|
}
|
|
|
|
void CUDT::releaseSynch()
|
|
{
|
|
// wake up user calls
|
|
pthread_mutex_lock(&m_SendBlockLock);
|
|
pthread_cond_signal(&m_SendBlockCond);
|
|
pthread_mutex_unlock(&m_SendBlockLock);
|
|
|
|
pthread_mutex_lock(&m_SendLock);
|
|
pthread_mutex_unlock(&m_SendLock);
|
|
|
|
pthread_mutex_lock(&m_RecvDataLock);
|
|
pthread_cond_signal(&m_RecvDataCond);
|
|
pthread_mutex_unlock(&m_RecvDataLock);
|
|
|
|
pthread_mutex_lock(&m_RecvLock);
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
pthread_mutex_unlock(&m_RecvLock);
|
|
|
|
pthread_mutex_lock(&m_RecvDataLock);
|
|
if (!pthread_equal(m_RcvTsbPdThread, pthread_t()))
|
|
{
|
|
pthread_join(m_RcvTsbPdThread, NULL);
|
|
m_RcvTsbPdThread = pthread_t();
|
|
}
|
|
pthread_mutex_unlock(&m_RecvDataLock);
|
|
|
|
pthread_mutex_lock(&m_RecvLock);
|
|
pthread_mutex_unlock(&m_RecvLock);
|
|
}
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
static void DebugAck(string hdr, int prev, int ack)
|
|
{
|
|
if (!prev)
|
|
{
|
|
HLOGC(mglog.Debug, log << hdr << "ACK " << ack);
|
|
return;
|
|
}
|
|
|
|
prev = CSeqNo::incseq(prev);
|
|
int diff = CSeqNo::seqoff(prev, ack);
|
|
if (diff < 0)
|
|
{
|
|
HLOGC(mglog.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(mglog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack);
|
|
}
|
|
#else
|
|
static inline void DebugAck(string, int, int) {}
|
|
#endif
|
|
|
|
void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, int size)
|
|
{
|
|
CPacket ctrlpkt;
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
|
|
ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
|
|
int nbsent = 0;
|
|
int local_prevack = 0;
|
|
|
|
#if 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
|
|
|
|
switch (pkttype)
|
|
{
|
|
case UMSG_ACK: // 010 - Acknowledgement
|
|
{
|
|
int32_t ack;
|
|
|
|
// If there is no loss, the ACK is the current largest sequence number plus 1;
|
|
// Otherwise it is the smallest sequence number in the receiver loss list.
|
|
if (m_pRcvLossList->getLossLength() == 0)
|
|
ack = CSeqNo::incseq(m_iRcvCurrSeqNo);
|
|
else
|
|
ack = m_pRcvLossList->getFirstLostSeq();
|
|
|
|
if (m_iRcvLastAckAck == ack)
|
|
break;
|
|
|
|
// 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)
|
|
{
|
|
ctrlpkt.pack(pkttype, NULL, &ack, size);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
DebugAck("sendCtrl(lite):" + CONID(), local_prevack, ack);
|
|
break;
|
|
}
|
|
|
|
// There are new received packets to acknowledge, update related information.
|
|
/* tsbpd thread may also call ackData when skipping packet so protect code */
|
|
CGuard::enterCS(m_RcvBufferLock);
|
|
|
|
// IF ack > m_iRcvLastAck
|
|
if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0)
|
|
{
|
|
int acksize = CSeqNo::seqoff(m_iRcvLastSkipAck, ack);
|
|
|
|
IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck);
|
|
m_iRcvLastAck = ack;
|
|
m_iRcvLastSkipAck = ack;
|
|
|
|
// XXX Unknown as to whether it matters.
|
|
// This if (acksize) causes that ackData() won't be called.
|
|
// With size == 0 it wouldn't do anything except calling CTimer::triggerEvent().
|
|
// This, again, signals the condition, CTimer::m_EventCond.
|
|
// This releases CTimer::waitForEvent() call used in CUDTUnited::selectEx().
|
|
// Preventing to call this on zero size makes sense, if it prevents false alerts.
|
|
if (acksize > 0)
|
|
m_pRcvBuffer->ackData(acksize);
|
|
CGuard::leaveCS(m_RcvBufferLock);
|
|
|
|
// If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond,
|
|
// signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which
|
|
// will signal m_RecvDataCond when there's time to play for particular
|
|
// data packet.
|
|
HLOGC(dlog.Debug,
|
|
log << "ACK: clip %" << oldack << "-%" << ack << ", REVOKED " << acksize << " from RCV buffer");
|
|
|
|
if (m_bTsbPd)
|
|
{
|
|
/* Newly acknowledged data, signal TsbPD thread */
|
|
pthread_mutex_lock(&m_RecvLock);
|
|
if (m_bTsbPdAckWakeup)
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
pthread_mutex_unlock(&m_RecvLock);
|
|
}
|
|
else
|
|
{
|
|
if (m_bSynRecving)
|
|
{
|
|
// signal a waiting "recv" call if there is any data available
|
|
pthread_mutex_lock(&m_RecvDataLock);
|
|
pthread_cond_signal(&m_RecvDataCond);
|
|
pthread_mutex_unlock(&m_RecvDataLock);
|
|
}
|
|
// acknowledge any waiting epolls to read
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true);
|
|
CTimer::triggerEvent();
|
|
}
|
|
CGuard::enterCS(m_RcvBufferLock);
|
|
}
|
|
else if (ack == m_iRcvLastAck)
|
|
{
|
|
// If the ACK was just sent already AND elapsed time did not exceed RTT,
|
|
if ((currtime_tk - m_ullLastAckTime_tk) < ((m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency))
|
|
{
|
|
CGuard::leaveCS(m_RcvBufferLock);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not possible (m_iRcvCurrSeqNo+1 < m_iRcvLastAck ?)
|
|
CGuard::leaveCS(m_RcvBufferLock);
|
|
break;
|
|
}
|
|
|
|
// [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]]
|
|
|
|
// Send out the ACK only if has not been received by the sender before
|
|
if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0)
|
|
{
|
|
// 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_iRTT;
|
|
data[ACKD_RTTVAR] = m_iRTTVar;
|
|
data[ACKD_BUFFERLEFT] = m_pRcvBuffer->getAvailBufSize();
|
|
// a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock
|
|
if (data[ACKD_BUFFERLEFT] < 2)
|
|
data[ACKD_BUFFERLEFT] = 2;
|
|
|
|
// NOTE: m_CongCtl->ACKTimeout_us() should be taken into account.
|
|
if (currtime_tk - m_ullLastAckTime_tk > m_ullACKInt_tk)
|
|
{
|
|
int rcvRate;
|
|
int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size
|
|
|
|
data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed(Ref(rcvRate));
|
|
data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth();
|
|
|
|
//>>Patch while incompatible (1.0.2) receiver floating around
|
|
if (m_lPeerSrtVersion == SrtVersion(1, 0, 2))
|
|
{
|
|
data[ACKD_RCVRATE] = rcvRate; // bytes/sec
|
|
data[ACKD_XMRATE] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec
|
|
ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102;
|
|
}
|
|
else if (m_lPeerSrtVersion >= 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(pkttype, &m_iAckSeqNo, data, ctrlsz);
|
|
CTimer::rdtsc(m_ullLastAckTime_tk);
|
|
}
|
|
else
|
|
{
|
|
ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL);
|
|
}
|
|
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
DebugAck("sendCtrl: " + CONID(), local_prevack, ack);
|
|
|
|
m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
++m_stats.sentACK;
|
|
++m_stats.sentACKTotal;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
CGuard::leaveCS(m_RcvBufferLock);
|
|
break;
|
|
}
|
|
|
|
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
|
|
ctrlpkt.pack(pkttype, lparam);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
break;
|
|
|
|
case UMSG_LOSSREPORT: // 011 - Loss Report
|
|
{
|
|
// 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.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
++m_stats.sentNAK;
|
|
++m_stats.sentNAKTotal;
|
|
CGuard::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.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
++m_stats.sentNAK;
|
|
++m_stats.sentNAKTotal;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
|
|
delete[] data;
|
|
}
|
|
|
|
// update next NAK time, which should wait enough time for the retansmission, but not too long
|
|
m_ullNAKInt_tk = (m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency;
|
|
|
|
// Fix the NAKreport period according to the congctl
|
|
m_ullNAKInt_tk = m_CongCtl->updateNAKInterval(
|
|
m_ullNAKInt_tk, 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_ullNAKInt_tk < m_ullMinNakInt_tk)
|
|
m_ullNAKInt_tk = m_ullMinNakInt_tk;
|
|
|
|
break;
|
|
}
|
|
|
|
case UMSG_CGWARNING: // 100 - Congestion Warning
|
|
ctrlpkt.pack(pkttype);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
CTimer::rdtsc(m_ullLastWarningTime);
|
|
|
|
break;
|
|
|
|
case UMSG_KEEPALIVE: // 001 - Keep-alive
|
|
ctrlpkt.pack(pkttype);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
break;
|
|
|
|
case UMSG_HANDSHAKE: // 000 - Handshake
|
|
ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake));
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
break;
|
|
|
|
case UMSG_SHUTDOWN: // 101 - Shutdown
|
|
ctrlpkt.pack(pkttype);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
break;
|
|
|
|
case UMSG_DROPREQ: // 111 - Msg drop request
|
|
ctrlpkt.pack(pkttype, lparam, rparam, 8);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
break;
|
|
|
|
case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error
|
|
ctrlpkt.pack(pkttype, lparam);
|
|
ctrlpkt.m_iID = m_PeerID;
|
|
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
|
|
|
|
break;
|
|
|
|
case UMSG_EXT: // 0x7FFF - Resevered for future use
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Fix keepalive
|
|
if (nbsent)
|
|
m_ullLastSndTime_tk = currtime_tk;
|
|
}
|
|
|
|
void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno)
|
|
{
|
|
// Update sender's loss list and acknowledge packets in the sender's buffer
|
|
{
|
|
// m_RecvAckLock protects sender's loss list and epoll
|
|
CGuard 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;
|
|
|
|
// remove any loss that predates 'ack' (not to be considered loss anymore)
|
|
m_pSndLossList->remove(CSeqNo::decseq(m_iSndLastDataAck));
|
|
|
|
// acknowledge the sending buffer (remove data that predate 'ack')
|
|
m_pSndBuffer->ackData(offset);
|
|
|
|
// acknowledde any waiting epolls to write
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
|
|
}
|
|
|
|
// insert this socket to snd list if it is not on the list yet
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
|
|
|
|
if (m_bSynSending)
|
|
{
|
|
CGuard lk(m_SendBlockLock);
|
|
pthread_cond_signal(&m_SendBlockCond);
|
|
}
|
|
|
|
const int64_t currtime = CTimer::getTime();
|
|
// record total time used for sending
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.sndDuration += currtime - m_stats.sndDurationCounter;
|
|
m_stats.m_sndDurationTotal += currtime - m_stats.sndDurationCounter;
|
|
m_stats.sndDurationCounter = currtime;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
|
|
void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk)
|
|
{
|
|
const int32_t *ackdata = (const int32_t *)ctrlpkt.m_pcData;
|
|
const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK];
|
|
|
|
const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK;
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck
|
|
<< "]" << (isLiteAck ? "[LITE]" : "[FULL]"));
|
|
|
|
updateSndLossListOnACK(ackdata_seqno);
|
|
|
|
// Process a lite ACK
|
|
if (isLiteAck)
|
|
{
|
|
if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
|
|
{
|
|
CGuard ack_lock(m_RecvAckLock);
|
|
m_iFlowWindowSize -= CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno);
|
|
m_iSndLastAck = ackdata_seqno;
|
|
|
|
// TODO: m_ullLastRspAckTime_tk should be protected with m_RecvAckLock
|
|
// because the sendmsg2 may want to change it at the same time.
|
|
m_ullLastRspAckTime_tk = currtime_tk;
|
|
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.
|
|
const uint64_t now = CTimer::getTime();
|
|
if ((now - m_ullSndLastAck2Time > (uint64_t)COMM_SYN_INTERVAL_US) || (ack_seqno == m_iSndLastAck2))
|
|
{
|
|
sendCtrl(UMSG_ACKACK, &ack_seqno);
|
|
m_iSndLastAck2 = ack_seqno;
|
|
m_ullSndLastAck2Time = now;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Begin of the new code with TLPKTDROP.
|
|
//
|
|
|
|
// Protect packet retransmission
|
|
CGuard::enterCS(m_RecvAckLock);
|
|
|
|
// Check the validation of the ack
|
|
if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0)
|
|
{
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
// this should not happen: attack or bug
|
|
LOGC(glog.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;
|
|
return;
|
|
}
|
|
|
|
if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
|
|
{
|
|
// Update Flow Window Size, must update before and together with m_iSndLastAck
|
|
m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT];
|
|
m_iSndLastAck = ackdata_seqno;
|
|
m_ullLastRspAckTime_tk = currtime_tk; // Should be protected with m_RecvAckLock
|
|
m_iReXmitCount = 1; // Reset re-transmit count since last ACK
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
* occures, 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
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
return;
|
|
}
|
|
m_iSndLastFullAck = ackdata_seqno;
|
|
|
|
//
|
|
// END of the new code with TLPKTDROP
|
|
//
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
|
|
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(mglog.Error,
|
|
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(mglog.Error, 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.
|
|
|
|
// Update RTT
|
|
// m_iRTT = ackdata[ACKD_RTT];
|
|
// m_iRTTVar = ackdata[ACKD_RTTVAR];
|
|
// XXX These ^^^ commented-out were blocked in UDT;
|
|
// the current RTT calculations are exactly the same as in UDT4.
|
|
const int rtt = ackdata[ACKD_RTT];
|
|
|
|
m_iRTTVar = avg_iir<4>(m_iRTTVar, abs(rtt - m_iRTT));
|
|
m_iRTT = avg_iir<8>(m_iRTT, rtt);
|
|
|
|
/* 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 version 1.0.2 (bstats):
|
|
* ACKD_RCVRATE
|
|
* SRT extension version 1.0.4:
|
|
* ACKD_XMRATE
|
|
*/
|
|
|
|
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]) 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, bandwidth);
|
|
m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate, pktps);
|
|
m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate, bytesps);
|
|
// XXX not sure if ACKD_XMRATE is of any use. This is simply
|
|
// calculated as ACKD_BANDWIDTH * m_iMaxSRTPayloadSize.
|
|
|
|
// 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.
|
|
}
|
|
|
|
checkSndTimers(REGEN_KM);
|
|
updateCC(TEV_ACK, ackdata_seqno);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
++m_stats.recvACK;
|
|
++m_stats.recvACKTotal;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
|
|
void CUDT::processCtrl(CPacket &ctrlpkt)
|
|
{
|
|
// Just heard from the peer, reset the expiration count.
|
|
m_iEXPCount = 1;
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
m_ullLastRspTime_tk = currtime_tk;
|
|
bool using_rexmit_flag = m_bPeerRexmitFlag;
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " ("
|
|
<< MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID);
|
|
|
|
switch (ctrlpkt.getType())
|
|
{
|
|
case UMSG_ACK: // 010 - Acknowledgement
|
|
processCtrlAck(ctrlpkt, currtime_tk);
|
|
break;
|
|
|
|
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
|
|
{
|
|
int32_t ack = 0;
|
|
int rtt = -1;
|
|
|
|
// update RTT
|
|
rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack);
|
|
if (rtt <= 0)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "IPE: ACK node overwritten when acknowledging " << ctrlpkt.getAckSeqNo()
|
|
<< " (ack extracted: " << ack << ")");
|
|
break;
|
|
}
|
|
|
|
// if increasing delay detected...
|
|
// sendCtrl(UMSG_CGWARNING);
|
|
|
|
// RTT EWMA
|
|
m_iRTTVar = (m_iRTTVar * 3 + abs(rtt - m_iRTT)) >> 2;
|
|
m_iRTT = (m_iRTT * 7 + rtt) >> 3;
|
|
|
|
updateCC(TEV_ACKACK, 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.
|
|
m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), m_RecvLock);
|
|
|
|
// update last ACK that has been received by the sender
|
|
if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0)
|
|
m_iRcvLastAckAck = ack;
|
|
|
|
break;
|
|
}
|
|
|
|
case UMSG_LOSSREPORT: // 011 - Loss Report
|
|
{
|
|
int32_t *losslist = (int32_t *)(ctrlpkt.m_pcData);
|
|
size_t losslist_len = ctrlpkt.getLength() / 4;
|
|
|
|
bool secure = true;
|
|
|
|
// protect packet retransmission
|
|
CGuard::enterCS(m_RecvAckLock);
|
|
|
|
// 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 = CSeqNo::m_iMaxSeqNo;
|
|
|
|
// decode loss list message and insert loss into the sender loss list
|
|
for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++i)
|
|
{
|
|
if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST))
|
|
{
|
|
// Then it's this is a <lo, hi> specification with HI in a consecutive cell.
|
|
int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]);
|
|
int32_t losslist_hi = losslist[i + 1];
|
|
// <lo, hi> specification means that the consecutive cell has been already interpreted.
|
|
++i;
|
|
|
|
HLOGF(mglog.Debug,
|
|
"received UMSG_LOSSREPORT: %d-%d (%d packets)...",
|
|
losslist_lo,
|
|
losslist_hi,
|
|
CSeqNo::seqoff(losslist_lo, losslist_hi) + 1);
|
|
|
|
if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) ||
|
|
(CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0))
|
|
{
|
|
// seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq
|
|
secure = false;
|
|
wrong_loss = losslist_hi;
|
|
// XXX leaveCS: really necessary? 'break' will break the 'for' loop, not the 'switch' statement.
|
|
// and the leaveCS is done again next to the 'for' loop end.
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
break;
|
|
}
|
|
|
|
int num = 0;
|
|
if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0)
|
|
num = m_pSndLossList->insert(losslist_lo, losslist_hi);
|
|
else if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0)
|
|
{
|
|
// 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 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.
|
|
num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi);
|
|
}
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceSndLoss += num;
|
|
m_stats.sndLossTotal += num;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0)
|
|
{
|
|
HLOGF(mglog.Debug, "received UMSG_LOSSREPORT: %d (1 packet)...", losslist[i]);
|
|
|
|
if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0)
|
|
{
|
|
// seq_a must not be greater than the most recent sent seq
|
|
secure = false;
|
|
wrong_loss = losslist[i];
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
break;
|
|
}
|
|
|
|
int num = m_pSndLossList->insert(losslist[i], losslist[i]);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceSndLoss += num;
|
|
m_stats.sndLossTotal += num;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
}
|
|
}
|
|
CGuard::leaveCS(m_RecvAckLock);
|
|
|
|
updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len));
|
|
|
|
if (!secure)
|
|
{
|
|
LOGC(mglog.Warn,
|
|
log << "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;
|
|
break;
|
|
}
|
|
|
|
// the lost packet (retransmission) should be sent out immediately
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
++m_stats.recvNAK;
|
|
++m_stats.recvNAKTotal;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
break;
|
|
}
|
|
|
|
case UMSG_CGWARNING: // 100 - Delay Warning
|
|
// One way packet delay is increasing, so decrease the sending rate
|
|
m_ullInterval_tk = (uint64_t)ceil(m_ullInterval_tk * 1.125);
|
|
m_iLastDecSeq = m_iSndCurrSeqNo;
|
|
// 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
|
|
// The only purpose of keep-alive packet is to tell that the peer is still alive
|
|
// nothing needs to be done.
|
|
|
|
break;
|
|
|
|
case UMSG_HANDSHAKE: // 000 - Handshake
|
|
{
|
|
CHandShake req;
|
|
req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength());
|
|
|
|
HLOGC(mglog.Debug, log << "processCtrl: got HS: " << req.show());
|
|
|
|
if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...???
|
|
|| (m_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 initdata;
|
|
initdata.m_iISN = m_iISN;
|
|
initdata.m_iMSS = m_iMSS;
|
|
initdata.m_iFlightFlagSize = m_iFlightFlagSize;
|
|
|
|
// For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT.
|
|
// For client-server we do URQ_INDUCTION --> URQ_CONCLUSION.
|
|
initdata.m_iReqType = (!m_bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT;
|
|
initdata.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)
|
|
{
|
|
initdata.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener...
|
|
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
|
|
if (hs_flags != 0) // has SRT extensions
|
|
{
|
|
HLOGC(mglog.Debug,
|
|
log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)
|
|
<< " WITH SRT ext");
|
|
have_hsreq = interpretSrtHandshake(req, ctrlpkt, kmdata, &kmdatasize);
|
|
if (!have_hsreq)
|
|
{
|
|
initdata.m_iVersion = 0;
|
|
m_RejectReason = SRT_REJ_ROGUE;
|
|
initdata.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_bRendezvous && m_SrtHsSide == HSD_RESPONDER)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "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 = initdata.m_iReqType == URQ_CONCLUSION;
|
|
HLOGC(mglog.Debug,
|
|
log << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType)
|
|
<< " kmdatasize=" << kmdatasize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
initdata.m_iVersion = HS_VERSION_UDT4;
|
|
}
|
|
|
|
initdata.m_extension = have_hsreq;
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType)
|
|
<< (have_hsreq ? " WITH SRT HS response extensions" : ""));
|
|
|
|
// XXX here interpret SRT handshake extension
|
|
CPacket response;
|
|
response.setControl(UMSG_HANDSHAKE);
|
|
response.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.
|
|
if (createSrtHandshake(Ref(response), Ref(initdata), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize))
|
|
{
|
|
response.m_iID = m_PeerID;
|
|
response.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
int nbsent = m_pSndQueue->sendto(m_pPeerAddr, response);
|
|
if (nbsent)
|
|
{
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
m_ullLastSndTime_tk = currtime_tk;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED.");
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case UMSG_SHUTDOWN: // 101 - Shutdown
|
|
m_bShutdown = true;
|
|
m_bClosing = true;
|
|
m_bBroken = true;
|
|
m_iBrokenCounter = 60;
|
|
|
|
// Signal the sender and recver if they are waiting for data.
|
|
releaseSynch();
|
|
// Unblock any call so they learn the connection_broken error
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true);
|
|
|
|
CTimer::triggerEvent();
|
|
|
|
break;
|
|
|
|
case UMSG_DROPREQ: // 111 - Msg drop request
|
|
CGuard::enterCS(m_RecvLock);
|
|
m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag);
|
|
CGuard::leaveCS(m_RecvLock);
|
|
|
|
dropFromLossLists(*(int32_t *)ctrlpkt.m_pcData, *(int32_t *)(ctrlpkt.m_pcData + 4));
|
|
|
|
// move forward with current recv seq no.
|
|
if ((CSeqNo::seqcmp(*(int32_t *)ctrlpkt.m_pcData, CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) &&
|
|
(CSeqNo::seqcmp(*(int32_t *)(ctrlpkt.m_pcData + 4), m_iRcvCurrSeqNo) > 0))
|
|
{
|
|
m_iRcvCurrSeqNo = *(int32_t *)(ctrlpkt.m_pcData + 4);
|
|
}
|
|
|
|
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() failes (e.g., due to disk fail), blcoked 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
|
|
HLOGF(mglog.Debug, "CONTROL EXT MSG RECEIVED: %08X\n", 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)
|
|
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)
|
|
{
|
|
updateAfterSrtHandshake(ctrlpkt.getExtendedType(), HS_VERSION_UDT4);
|
|
}
|
|
else
|
|
{
|
|
updateCC(TEV_CUSTOM, &ctrlpkt);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CUDT::updateSrtRcvSettings()
|
|
{
|
|
if (m_bTsbPd)
|
|
{
|
|
/* We are TsbPd receiver */
|
|
CGuard::enterCS(m_RecvLock);
|
|
m_pRcvBuffer->setRcvTsbPdMode(m_ullRcvPeerStartTime, m_iTsbPdDelay_ms * 1000);
|
|
CGuard::leaveCS(m_RecvLock);
|
|
|
|
HLOGF(mglog.Debug,
|
|
"AFTER HS: Set Rcv TsbPd mode: delay=%u.%03u secs",
|
|
m_iTsbPdDelay_ms / 1000,
|
|
m_iTsbPdDelay_ms % 1000);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "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_iRTT + (4 * m_iRTTVar)) / 1000);
|
|
/*
|
|
* For sender to apply Too-Late Packet Drop
|
|
* option (m_bTLPktDrop) must be enabled and receiving peer shall support it
|
|
*/
|
|
HLOGF(mglog.Debug,
|
|
"AFTER HS: Set Snd TsbPd mode %s: delay=%d.%03d secs",
|
|
m_bPeerTLPktDrop ? "with TLPktDrop" : "without TLPktDrop",
|
|
m_iPeerTsbPdDelay_ms / 1000,
|
|
m_iPeerTsbPdDelay_ms % 1000);
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "AFTER HS: Snd TsbPd mode not set");
|
|
}
|
|
}
|
|
|
|
void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv)
|
|
{
|
|
|
|
switch (srt_cmd)
|
|
{
|
|
case SRT_CMD_HSREQ:
|
|
case SRT_CMD_HSRSP:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// 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 (hsv > HS_VERSION_UDT4)
|
|
{
|
|
updateSrtRcvSettings();
|
|
updateSrtSndSettings();
|
|
}
|
|
else if (srt_cmd == SRT_CMD_HSRSP)
|
|
{
|
|
// HSv4 INITIATOR is sender
|
|
updateSrtSndSettings();
|
|
}
|
|
else
|
|
{
|
|
// HSv4 RESPONDER is receiver
|
|
updateSrtRcvSettings();
|
|
}
|
|
}
|
|
|
|
int CUDT::packLostData(CPacket &packet, uint64_t &origintime)
|
|
{
|
|
// protect m_iSndLastDataAck from updating by ACK processing
|
|
CGuard ackguard(m_RecvAckLock);
|
|
|
|
while ((packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0)
|
|
{
|
|
const int offset = CSeqNo::seqoff(m_iSndLastDataAck, packet.m_iSeqNo);
|
|
if (offset < 0)
|
|
{
|
|
LOGC(dlog.Error,
|
|
log << "IPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " << packet.m_iSeqNo
|
|
<< ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset << ". Continue");
|
|
continue;
|
|
}
|
|
|
|
int msglen;
|
|
|
|
const int payload = m_pSndBuffer->readData(&(packet.m_pcData), offset, packet.m_iMsgNo, origintime, msglen);
|
|
SRT_ASSERT(payload != 0);
|
|
if (payload == -1)
|
|
{
|
|
int32_t seqpair[2];
|
|
seqpair[0] = packet.m_iSeqNo;
|
|
seqpair[1] = CSeqNo::incseq(seqpair[0], msglen);
|
|
sendCtrl(UMSG_DROPREQ, &packet.m_iMsgNo, seqpair, 8);
|
|
|
|
// only one msg drop request is necessary
|
|
m_pSndLossList->remove(seqpair[1]);
|
|
|
|
// skip all dropped packets
|
|
if (CSeqNo::seqcmp(m_iSndCurrSeqNo, CSeqNo::incseq(seqpair[1])) < 0)
|
|
m_iSndCurrSeqNo = CSeqNo::incseq(seqpair[1]);
|
|
|
|
continue;
|
|
}
|
|
// NOTE: This is just a sanity check. Returning 0 is impossible to happen
|
|
// in case of retransmission. If the offset was a positive value, then the
|
|
// block must exist in the old blocks because it wasn't yet cut off by ACK
|
|
// and has been already recorded as sent (otherwise the peer wouldn't send
|
|
// back the loss report). May something happen here in case when the send
|
|
// loss record has been updated by the FASTREXMIT.
|
|
else if (payload == 0)
|
|
continue;
|
|
|
|
// At this point we no longer need the ACK lock,
|
|
// because we are going to return from the function.
|
|
// Therefore unlocking in order not to block other threads.
|
|
ackguard.forceUnlock();
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
++m_stats.traceRetrans;
|
|
++m_stats.retransTotal;
|
|
m_stats.traceBytesRetrans += payload;
|
|
m_stats.bytesRetransTotal += payload;
|
|
CGuard::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)
|
|
{
|
|
packet.m_iMsgNo |= PACKET_SND_REXMIT;
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CUDT::packData(CPacket &packet, uint64_t &ts_tk)
|
|
{
|
|
int payload = 0;
|
|
bool probe = false;
|
|
uint64_t origintime = 0;
|
|
bool new_packet_packed = false;
|
|
bool filter_ctl_pkt = false;
|
|
|
|
int kflg = EK_NOENC;
|
|
|
|
uint64_t entertime_tk;
|
|
CTimer::rdtsc(entertime_tk);
|
|
|
|
#if 0 // debug: TimeDiff histogram
|
|
static int lldiffhisto[23] = {0};
|
|
static int llnodiff = 0;
|
|
if (m_ullTargetTime_tk != 0)
|
|
{
|
|
int ofs = 11 + ((entertime_tk - m_ullTargetTime_tk)/(int64_t)m_ullCPUFrequency)/1000;
|
|
if (ofs < 0) ofs = 0;
|
|
else if (ofs > 22) ofs = 22;
|
|
lldiffhisto[ofs]++;
|
|
}
|
|
else if(m_ullTargetTime_tk == 0)
|
|
{
|
|
llnodiff++;
|
|
}
|
|
static int callcnt = 0;
|
|
if (!(callcnt++ % 5000)) {
|
|
fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n",
|
|
lldiffhisto[0],lldiffhisto[1],lldiffhisto[2],lldiffhisto[3],lldiffhisto[4],lldiffhisto[5],
|
|
lldiffhisto[6],lldiffhisto[7],lldiffhisto[8],lldiffhisto[9],lldiffhisto[10],lldiffhisto[11]);
|
|
fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n",
|
|
lldiffhisto[12],lldiffhisto[13],lldiffhisto[14],lldiffhisto[15],lldiffhisto[16],lldiffhisto[17],
|
|
lldiffhisto[18],lldiffhisto[19],lldiffhisto[20],lldiffhisto[21],lldiffhisto[21],llnodiff);
|
|
}
|
|
#endif
|
|
if ((0 != m_ullTargetTime_tk) && (entertime_tk > m_ullTargetTime_tk))
|
|
m_ullTimeDiff_tk += entertime_tk - m_ullTargetTime_tk;
|
|
|
|
string reason;
|
|
|
|
payload = packLostData(packet, origintime);
|
|
if (payload > 0)
|
|
{
|
|
reason = "reXmit";
|
|
}
|
|
else if (m_PacketFilter &&
|
|
m_PacketFilter.packControlPacket(Ref(packet), m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags()))
|
|
{
|
|
HLOGC(mglog.Debug, log << "filter: filter/CTL packet ready - packing instead of data.");
|
|
payload = packet.getLength();
|
|
reason = "filter";
|
|
filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set
|
|
|
|
// Stats
|
|
|
|
{
|
|
CGuard lg(m_StatsLock);
|
|
++m_stats.sndFilterExtra;
|
|
++m_stats.sndFilterExtraTotal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If no loss, and no packetfilter control packet, pack a new packet.
|
|
|
|
// check congestion/flow window limit
|
|
int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow));
|
|
int seqdiff = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo));
|
|
if (cwnd >= seqdiff)
|
|
{
|
|
// 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_pCryptoControl->getSndCryptoFlags();
|
|
payload = m_pSndBuffer->readData(&(packet.m_pcData), packet.m_iMsgNo, origintime, kflg);
|
|
if (payload)
|
|
{
|
|
m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo);
|
|
// m_pCryptoControl->m_iSndCurrSeqNo = m_iSndCurrSeqNo;
|
|
|
|
packet.m_iSeqNo = m_iSndCurrSeqNo;
|
|
|
|
// every 16 (0xF) packets, a packet pair is sent
|
|
if ((packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0)
|
|
probe = true;
|
|
|
|
new_packet_packed = true;
|
|
}
|
|
else
|
|
{
|
|
m_ullTargetTime_tk = 0;
|
|
m_ullTimeDiff_tk = 0;
|
|
ts_tk = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGC(dlog.Debug,
|
|
log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow
|
|
<< ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << seqdiff);
|
|
m_ullTargetTime_tk = 0;
|
|
m_ullTimeDiff_tk = 0;
|
|
ts_tk = 0;
|
|
return 0;
|
|
}
|
|
|
|
reason = "normal";
|
|
}
|
|
|
|
// Normally packet.m_iTimeStamp field is set exactly here,
|
|
// usually as taken from m_StartTime and current time, unless live
|
|
// mode in which case it is based on 'origintime' as set during scheduling.
|
|
// In case when this is a filter control packet, the m_iTimeStamp field already
|
|
// contains the exactly needed value, and it's a timestamp clip, not a real
|
|
// timestamp.
|
|
if (!filter_ctl_pkt)
|
|
{
|
|
if (m_bPeerTsbPd)
|
|
{
|
|
/*
|
|
* When timestamp is carried over in this sending stream from a received stream,
|
|
* it may be older than the session start time causing a negative packet time
|
|
* that may block the receiver's Timestamp-based Packet Delivery.
|
|
* XXX Isn't it then better to not decrease it by m_StartTime? As long as it
|
|
* doesn't screw up the start time on the other side.
|
|
*/
|
|
if (origintime >= m_stats.startTime)
|
|
packet.m_iTimeStamp = int(origintime - m_stats.startTime);
|
|
else
|
|
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
}
|
|
else
|
|
{
|
|
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
}
|
|
}
|
|
|
|
packet.m_iID = m_PeerID;
|
|
packet.setLength(payload);
|
|
|
|
/* Encrypt if 1st time this packet is sent and crypto is enabled */
|
|
if (kflg)
|
|
{
|
|
// XXX Encryption flags are already set on the packet before calling this.
|
|
// See readData() above.
|
|
if (m_pCryptoControl->encrypt(Ref(packet)))
|
|
{
|
|
// Encryption failed
|
|
//>>Add stats for crypto failure
|
|
ts_tk = 0;
|
|
LOGC(dlog.Error, log << "ENCRYPT FAILED - packet won't be sent, size=" << payload);
|
|
return -1; // Encryption failed
|
|
}
|
|
payload = packet.getLength(); /* Cipher may change length */
|
|
reason += " (encrypted)";
|
|
}
|
|
|
|
if (new_packet_packed && m_PacketFilter)
|
|
{
|
|
HLOGC(mglog.Debug, log << "filter: Feeding packet for source clip");
|
|
m_PacketFilter.feedSource(Ref(packet));
|
|
}
|
|
|
|
#if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr()
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "packData: " << reason << " packet seq=" << packet.m_iSeqNo << " (ACK=" << m_iSndLastAck
|
|
<< " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << packet.MessageFlagStr() << ")");
|
|
#endif
|
|
|
|
// Fix keepalive
|
|
m_ullLastSndTime_tk = entertime_tk;
|
|
|
|
considerLegacySrtHandshake(0);
|
|
|
|
// 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, &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(packet.m_iTimeStamp);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceBytesSent += payload;
|
|
m_stats.bytesSentTotal += payload;
|
|
++m_stats.traceSent;
|
|
++m_stats.sentTotal;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
if (probe)
|
|
{
|
|
// sends out probing packet pair
|
|
ts_tk = entertime_tk;
|
|
probe = false;
|
|
}
|
|
else
|
|
{
|
|
#if USE_BUSY_WAITING
|
|
ts_tk = entertime_tk + m_ullInterval_tk;
|
|
#else
|
|
if (m_ullTimeDiff_tk >= m_ullInterval_tk)
|
|
{
|
|
ts_tk = entertime_tk;
|
|
m_ullTimeDiff_tk -= m_ullInterval_tk;
|
|
}
|
|
else
|
|
{
|
|
ts_tk = entertime_tk + m_ullInterval_tk - m_ullTimeDiff_tk;
|
|
m_ullTimeDiff_tk = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
m_ullTargetTime_tk = ts_tk;
|
|
|
|
return payload;
|
|
}
|
|
|
|
// This is a close request, but called from the
|
|
void CUDT::processClose()
|
|
{
|
|
sendCtrl(UMSG_SHUTDOWN);
|
|
|
|
m_bShutdown = true;
|
|
m_bClosing = true;
|
|
m_bBroken = true;
|
|
m_iBrokenCounter = 60;
|
|
|
|
HLOGP(mglog.Debug, "processClose: sent message and set flags");
|
|
|
|
if (m_bTsbPd)
|
|
{
|
|
HLOGP(mglog.Debug, "processClose: lock-and-signal TSBPD");
|
|
CGuard rl(m_RecvLock);
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
}
|
|
|
|
// Signal the sender and recver if they are waiting for data.
|
|
releaseSynch();
|
|
// Unblock any call so they learn the connection_broken error
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true);
|
|
|
|
HLOGP(mglog.Debug, "processClose: triggering timer event to spread the bad news");
|
|
CTimer::triggerEvent();
|
|
}
|
|
|
|
void CUDT::sendLossReport(const std::vector<std::pair<int32_t, int32_t> > &loss_seqs)
|
|
{
|
|
typedef vector<pair<int32_t, int32_t> > loss_seqs_t;
|
|
|
|
vector<int32_t> 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);
|
|
HLOGF(mglog.Debug, "lost packet %d: sending LOSSREPORT", i->first);
|
|
}
|
|
else
|
|
{
|
|
seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST);
|
|
seqbuffer.push_back(i->second);
|
|
HLOGF(mglog.Debug,
|
|
"lost packets %d-%d (%d packets): sending LOSSREPORT",
|
|
i->first,
|
|
i->second,
|
|
1 + CSeqNo::seqcmp(i->second, i->first));
|
|
}
|
|
}
|
|
|
|
if (!seqbuffer.empty())
|
|
{
|
|
sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], seqbuffer.size());
|
|
}
|
|
}
|
|
|
|
int CUDT::processData(CUnit *in_unit)
|
|
{
|
|
CPacket &packet = in_unit->m_Packet;
|
|
|
|
// XXX This should be called (exclusively) here:
|
|
// m_pRcvBuffer->addLocalTsbPdDriftSample(packet.getMsgTimeStamp());
|
|
// Just heard from the peer, reset the expiration count.
|
|
m_iEXPCount = 1;
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
m_ullLastRspTime_tk = currtime_tk;
|
|
|
|
// We are receiving data, start tsbpd thread if TsbPd is enabled
|
|
if (m_bTsbPd && pthread_equal(m_RcvTsbPdThread, pthread_t()))
|
|
{
|
|
HLOGP(mglog.Debug, "Spawning TSBPD thread");
|
|
int st = 0;
|
|
{
|
|
ThreadName tn("SRT:TsbPd");
|
|
st = pthread_create(&m_RcvTsbPdThread, NULL, CUDT::tsbpd, this);
|
|
}
|
|
if (st != 0)
|
|
return -1;
|
|
}
|
|
|
|
const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2;
|
|
#if ENABLE_HEAVY_LOGGING
|
|
static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"};
|
|
string rexmit_reason;
|
|
#endif
|
|
|
|
if (pktrexmitflag == 1)
|
|
{
|
|
// This packet was retransmitted
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceRcvRetrans++;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
#if ENABLE_HEAVY_LOGGING
|
|
// Check if packet was retransmitted on request or on ack timeout
|
|
// Search the sequence in the loss record.
|
|
rexmit_reason = " by ";
|
|
if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo))
|
|
rexmit_reason += "REQUEST";
|
|
else
|
|
rexmit_reason += "ACK-TMOUT";
|
|
#endif
|
|
}
|
|
|
|
HLOGC(dlog.Debug,
|
|
log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo());
|
|
|
|
updateCC(TEV_RECEIVE, &packet);
|
|
++m_iPktCount;
|
|
|
|
const int pktsz = 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.m_iSeqNo, m_iRcvCurrSeqNo) <= 0;
|
|
const bool retransmitted = m_bPeerRexmitFlag && packet.getRexmitFlag();
|
|
|
|
// 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);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceBytesRecv += pktsz;
|
|
m_stats.bytesRecvTotal += pktsz;
|
|
++m_stats.traceRecv;
|
|
++m_stats.recvTotal;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
typedef vector<pair<int32_t, int32_t> > loss_seqs_t;
|
|
loss_seqs_t filter_loss_seqs;
|
|
loss_seqs_t srt_loss_seqs;
|
|
vector<CUnit *> incoming;
|
|
bool was_sent_in_order = true;
|
|
bool reorder_prevent_lossreport = false;
|
|
|
|
// 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;
|
|
|
|
// After introduction of packet filtering, the "recordable loss detection"
|
|
// does not exactly match the true loss detection. When a FEC filter is
|
|
// working, for example, then getting one group filled with all packet but
|
|
// the last one and the FEC control packet, in this special case this packet
|
|
// won't be notified at all as lost because it will be recovered by the
|
|
// filter immediately before anyone notices what happened (and the loss
|
|
// detection for the further functionality is checked only afterwards,
|
|
// and in this case the immediate recovery makes the loss to not be noticed
|
|
// at all).
|
|
//
|
|
// Because of that the check for losses must happen BEFORE passing the packet
|
|
// to the filter and before the filter could recover the packet before anyone
|
|
// notices :)
|
|
|
|
if (packet.getMsgSeq() != 0) // disregard filter-control packets, their seq may mean nothing
|
|
{
|
|
int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo);
|
|
if (diff > 1)
|
|
{
|
|
CGuard lg(m_StatsLock);
|
|
int loss = diff - 1; // loss is all that is above diff == 1
|
|
m_stats.traceRcvLoss += loss;
|
|
m_stats.rcvLossTotal += loss;
|
|
uint64_t lossbytes = loss * m_pRcvBuffer->getRcvAvgPayloadSize();
|
|
m_stats.traceRcvBytesLoss += lossbytes;
|
|
m_stats.rcvBytesLossTotal += lossbytes;
|
|
HLOGC(mglog.Debug,
|
|
log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " "
|
|
<< CSeqNo::decseq(packet.m_iSeqNo) << "]");
|
|
}
|
|
|
|
if (diff > 0)
|
|
{
|
|
// Record if it was further than latest
|
|
m_iRcvCurrPhySeqNo = packet.m_iSeqNo;
|
|
}
|
|
}
|
|
|
|
{
|
|
// 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()
|
|
CGuard recvbuf_acklock(m_RcvBufferLock);
|
|
|
|
// vector<CUnit*> undec_units;
|
|
if (m_PacketFilter)
|
|
{
|
|
// Stuff this data into the filter
|
|
m_PacketFilter.receive(in_unit, Ref(incoming), Ref(filter_loss_seqs));
|
|
HLOGC(mglog.Debug,
|
|
log << "(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);
|
|
}
|
|
|
|
bool excessive = true; // stays true unless it was successfully added
|
|
|
|
// Needed for possibly check for needsQuickACK.
|
|
bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0);
|
|
|
|
// 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<CUnit *>::iterator i = incoming.begin(); i != incoming.end(); ++i)
|
|
{
|
|
CUnit * u = *i;
|
|
CPacket &rpkt = u->m_Packet;
|
|
|
|
// m_iRcvLastSkipAck is the base sequence number for the receiver buffer.
|
|
// This is the offset in the buffer; if this is negative, it means that
|
|
// this sequence is already in the past and the buffer is not interested.
|
|
// Meaning, this packet will be rejected, even if it could potentially be
|
|
// one of missing packets in the transmission.
|
|
int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo);
|
|
|
|
IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED");
|
|
|
|
if (offset < 0)
|
|
{
|
|
IF_HEAVY_LOGGING(exc_type = "BELATED");
|
|
uint64_t tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp());
|
|
uint64_t bltime =
|
|
CountIIR(uint64_t(m_stats.traceBelatedTime) * 1000, CTimer::getTime() - tsbpdtime, 0.2);
|
|
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceBelatedTime = double(bltime) / 1000.0;
|
|
m_stats.traceRcvBelated++;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/"
|
|
<< rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr());
|
|
continue;
|
|
}
|
|
|
|
const int avail_bufsize = m_pRcvBuffer->getAvailBufSize();
|
|
if (offset >= avail_bufsize)
|
|
{
|
|
// 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(mglog.Error,
|
|
log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION. offset=" << offset
|
|
<< " avail=" << avail_bufsize << " ack.seq=" << m_iRcvLastSkipAck
|
|
<< " pkt.seq=" << rpkt.m_iSeqNo << " rcv-remain=" << m_pRcvBuffer->debugGetSize());
|
|
|
|
// 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.forceUnlock();
|
|
processClose();
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << CONID() << "No room to store incoming packet: offset=" << offset
|
|
<< " avail=" << avail_bufsize << " ack.seq=" << m_iRcvLastSkipAck
|
|
<< " pkt.seq=" << rpkt.m_iSeqNo << " rcv-remain=" << m_pRcvBuffer->debugGetSize());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool adding_successful = true;
|
|
if (m_pRcvBuffer->addData(*i, offset) < 0)
|
|
{
|
|
// addData returns -1 if at the m_iLastAckPos+offset position 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())
|
|
{
|
|
EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt(Ref(u->m_Packet)) : ENCS_NOTSUP;
|
|
if (rc != ENCS_CLEAR)
|
|
{
|
|
// Could not decrypt
|
|
// Keep packet in received buffer
|
|
// Crypto flags are still set
|
|
// It will be acknowledged
|
|
{
|
|
CGuard lg(m_StatsLock);
|
|
m_stats.traceRcvUndecrypt += 1;
|
|
m_stats.traceRcvBytesUndecrypt += pktsz;
|
|
m_stats.m_rcvUndecryptTotal += 1;
|
|
m_stats.m_rcvBytesUndecryptTotal += pktsz;
|
|
}
|
|
|
|
// Log message degraded to debug because it may happen very often
|
|
HLOGC(dlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data.");
|
|
adding_successful = false;
|
|
IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED");
|
|
}
|
|
}
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " offset=" << offset
|
|
<< " BUFr=" << avail_bufsize
|
|
<< " (" << exc_type << "/" << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: "
|
|
<< packet.MessageFlagStr());
|
|
|
|
// Decryption should have made the crypto flags EK_NOENC.
|
|
// Otherwise it's an error.
|
|
if (adding_successful)
|
|
{
|
|
HLOGC(dlog.Debug,
|
|
log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo));
|
|
if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection.
|
|
{
|
|
int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo);
|
|
int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo);
|
|
|
|
srt_loss_seqs.push_back(make_pair(seqlo, seqhi));
|
|
|
|
if (initial_loss_ttl)
|
|
{
|
|
// pack loss list for (possibly belated) NAK
|
|
// The LOSSREPORT will be sent in a while.
|
|
|
|
for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i)
|
|
{
|
|
m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl));
|
|
}
|
|
HLOGC(mglog.Debug,
|
|
log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs)
|
|
<< " tolerance: " << initial_loss_ttl);
|
|
reorder_prevent_lossreport = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.m_iSeqNo, m_iRcvCurrSeqNo) > 0)
|
|
{
|
|
m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received
|
|
}
|
|
else
|
|
{
|
|
unlose(rpkt); // was BELATED or RETRANSMITTED
|
|
was_sent_in_order &= 0 != pktrexmitflag;
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
{
|
|
CTimer::rdtsc(m_ullNextACKTime_tk);
|
|
}
|
|
}
|
|
|
|
if (excessive)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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())
|
|
{
|
|
// A loss is detected
|
|
{
|
|
// TODO: Can unlock rcvloss after m_pRcvLossList->insert(...)?
|
|
// And probably protect m_FreshLoss as well.
|
|
|
|
HLOGC(mglog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING.");
|
|
// if record_loss == false, nothing will be contained here
|
|
// Insert lost sequence numbers to the receiver loss list
|
|
CGuard lg(m_RcvLossLock);
|
|
for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i)
|
|
{
|
|
// If loss found, insert them to the receiver loss list
|
|
m_pRcvLossList->insert(i->first, i->second);
|
|
}
|
|
}
|
|
|
|
const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS;
|
|
if (!reorder_prevent_lossreport && report_recorded_loss)
|
|
{
|
|
HLOGC(mglog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs));
|
|
sendLossReport(srt_loss_seqs);
|
|
}
|
|
|
|
if (m_bTsbPd)
|
|
{
|
|
pthread_mutex_lock(&m_RecvLock);
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
pthread_mutex_unlock(&m_RecvLock);
|
|
}
|
|
}
|
|
|
|
// 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(mglog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs));
|
|
sendLossReport(filter_loss_seqs);
|
|
|
|
if (m_bTsbPd)
|
|
{
|
|
pthread_mutex_lock(&m_RecvLock);
|
|
pthread_cond_signal(&m_RcvTsbPdCond);
|
|
pthread_mutex_unlock(&m_RecvLock);
|
|
}
|
|
}
|
|
|
|
// 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 unlose() 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 becase the loop to increment one field (without any condition check)
|
|
// can be quite well optimized.
|
|
|
|
vector<int32_t> lossdata;
|
|
{
|
|
CGuard 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<CRcvFreshLoss>::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 unlose) sequence that has split one detected loss
|
|
// into two records.
|
|
for (; i != m_FreshLoss.end() && i->ttl <= 0; ++i)
|
|
{
|
|
HLOGF(mglog.Debug,
|
|
"Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT",
|
|
i->seq[0],
|
|
i->seq[1],
|
|
CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1);
|
|
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(mglog.Debug, "NO MORE FRESH LOSS RECORDS.");
|
|
}
|
|
else
|
|
{
|
|
HLOGF(mglog.Debug,
|
|
"STILL %" PRIzu " FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d",
|
|
m_FreshLoss.size(),
|
|
i->seq[0],
|
|
i->seq[1],
|
|
1 + CSeqNo::seqoff(i->seq[0], i->seq[1]),
|
|
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], 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--;
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceReorderDistance--;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
HLOGF(mglog.Debug,
|
|
"ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d",
|
|
m_iReorderTolerance);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// 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 m_iMaxReorderTolerance.
|
|
/// This value can be set in options - SRT_LOSSMAXTTL.
|
|
void CUDT::unlose(const CPacket &packet)
|
|
{
|
|
CGuard lg(m_RcvLossLock);
|
|
int32_t sequence = packet.m_iSeqNo;
|
|
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)
|
|
{
|
|
HLOGF(mglog.Debug, "received out-of-band packet seq %d", sequence);
|
|
|
|
const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo));
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance);
|
|
CGuard::leaveCS(m_StatsLock);
|
|
if (seqdiff > m_iReorderTolerance)
|
|
{
|
|
const int new_tolerance = min(seqdiff, m_iMaxReorderTolerance);
|
|
HLOGF(mglog.Debug,
|
|
"Belated by %d seqs - Reorder tolerance %s %d",
|
|
seqdiff,
|
|
(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(mglog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HLOGF(mglog.Debug, "received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence);
|
|
}
|
|
|
|
// 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;
|
|
|
|
size_t i = 0;
|
|
int had_ttl = 0;
|
|
for (i = 0; i < m_FreshLoss.size(); ++i)
|
|
{
|
|
had_ttl = m_FreshLoss[i].ttl;
|
|
switch (m_FreshLoss[i].revoke(sequence))
|
|
{
|
|
case CRcvFreshLoss::NONE:
|
|
continue; // Not found. Search again.
|
|
|
|
case CRcvFreshLoss::STRIPPED:
|
|
goto breakbreak; // Found and the modification is applied. We're done here.
|
|
|
|
case CRcvFreshLoss::DELETE:
|
|
// No more elements. Kill it.
|
|
m_FreshLoss.erase(m_FreshLoss.begin() + i);
|
|
// Every loss is unique. We're done here.
|
|
goto breakbreak;
|
|
|
|
case CRcvFreshLoss::SPLIT:
|
|
// Oh, this will be more complicated. This means that it was in between.
|
|
{
|
|
// So create a new element that will hold the upper part of the range,
|
|
// and this one modify to be the lower part of the range.
|
|
|
|
// Keep the current end-of-sequence value for the second element
|
|
int32_t next_end = m_FreshLoss[i].seq[1];
|
|
|
|
// seq-1 set to the end of this element
|
|
m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence);
|
|
// seq+1 set to the begin of the next element
|
|
int32_t next_begin = CSeqNo::incseq(sequence);
|
|
|
|
// Use position of the NEXT element because insertion happens BEFORE pointed element.
|
|
// Use the same TTL (will stay the same in the other one).
|
|
m_FreshLoss.insert(m_FreshLoss.begin() + i + 1,
|
|
CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl));
|
|
}
|
|
goto breakbreak;
|
|
}
|
|
}
|
|
|
|
// Could have made the "return" instruction instead of goto, but maybe there will be something
|
|
// to add in future, so keeping that.
|
|
breakbreak:;
|
|
|
|
if (i != m_FreshLoss.size())
|
|
{
|
|
HLOGF(mglog.Debug, "sequence %d removed from belated lossreport record", sequence);
|
|
}
|
|
|
|
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
|
|
HLOGF(mglog.Debug, "... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery);
|
|
|
|
// After 10 consecutive
|
|
if (m_iConsecEarlyDelivery >= 10)
|
|
{
|
|
m_iConsecEarlyDelivery = 0;
|
|
if (m_iReorderTolerance > 0)
|
|
{
|
|
m_iReorderTolerance--;
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceReorderDistance--;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
HLOGF(mglog.Debug,
|
|
"... reached %d times - decreasing tolerance to %d",
|
|
m_iConsecEarlyDelivery,
|
|
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)
|
|
{
|
|
CGuard lg(m_RcvLossLock);
|
|
m_pRcvLossList->remove(from, to);
|
|
|
|
HLOGF(mglog.Debug, "TLPKTDROP seq %d-%d (%d packets)", from, to, CSeqNo::seqoff(from, to));
|
|
|
|
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 *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,
|
|
(m_iIPversion == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6),
|
|
clienthost,
|
|
sizeof(clienthost),
|
|
clientport,
|
|
sizeof(clientport),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
int64_t timestamp = ((CTimer::getTime() - m_stats.startTime) / 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.
|
|
SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &packet)
|
|
{
|
|
// XXX ASSUMPTIONS:
|
|
// [[using assert(packet.m_iID == 0)]]
|
|
|
|
HLOGC(mglog.Debug, log << "processConnectRequest: received a connection request");
|
|
|
|
if (m_bClosing)
|
|
{
|
|
m_RejectReason = SRT_REJ_CLOSE;
|
|
HLOGC(mglog.Debug, log << "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(mglog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken.");
|
|
return m_RejectReason;
|
|
}
|
|
size_t exp_len =
|
|
CHandShake::m_iContentSize; // When CHandShake::m_iContentSize is used in log, the file fails to link!
|
|
|
|
// 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(mglog.Debug,
|
|
log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len
|
|
<< ")");
|
|
return m_RejectReason;
|
|
}
|
|
|
|
// Dunno 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(mglog.Error, log << "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(mglog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val);
|
|
|
|
// REQUEST:INDUCTION.
|
|
// Set a cookie, a target ID, and send back the same as
|
|
// RESPONSE:INDUCTION.
|
|
if (hs.m_iReqType == URQ_INDUCTION)
|
|
{
|
|
HLOGC(mglog.Debug, log << "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.m_iID = 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_iSndCryptoKeyLen);
|
|
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
|
|
HLOGC(mglog.Debug,
|
|
log << "processConnectRequest: " << (whether ? "" : "NOT ")
|
|
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
|
|
|
|
size_t size = packet.getLength();
|
|
hs.store_to(packet.m_pcData, Ref(size));
|
|
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
m_pSndQueue->sendto(addr, packet);
|
|
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.
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "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(mglog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring.");
|
|
return m_RejectReason;
|
|
}
|
|
|
|
HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding.");
|
|
}
|
|
else
|
|
{
|
|
HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding.");
|
|
}
|
|
|
|
int32_t 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(mglog.Debug,
|
|
log << "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, Ref(size));
|
|
packet.m_iID = id;
|
|
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
m_pSndQueue->sendto(addr, packet);
|
|
}
|
|
else
|
|
{
|
|
SRT_REJECT_REASON error = SRT_REJ_UNKNOWN;
|
|
int result = s_UDTUnited.newConnection(m_SocketID, addr, &hs, packet, Ref(error));
|
|
|
|
// 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);
|
|
LOGF(mglog.Error, "UU:newConnection: rsp(REJECT): %d - %s", hs.m_iReqType, srt_rejectreason_str(error));
|
|
}
|
|
|
|
// CONFUSION WARNING!
|
|
//
|
|
// The newConnection() will call acceptAndRespond() if the processing
|
|
// was successful - IN WHICH CASE THIS PROCEDURE SHOULD DO NOTHING.
|
|
// Ok, almost nothing - see update_events below.
|
|
//
|
|
// If newConnection() failed, acceptAndRespond() will not be called.
|
|
// Ok, more precisely, the thing that acceptAndRespond() is expected to do
|
|
// will not be done (this includes sending any response to the peer).
|
|
//
|
|
// Now read CAREFULLY. The newConnection() will return:
|
|
//
|
|
// - -1: The connection processing failed due to errors like:
|
|
// - memory alloation error
|
|
// - listen backlog exceeded
|
|
// - any error propagated from CUDT::open and CUDT::acceptAndRespond
|
|
// - 0: The connection already exists
|
|
// - 1: Connection accepted.
|
|
//
|
|
// So, update_events is called only if the connection is established.
|
|
// Both 0 (repeated) and -1 (error) require that a response be sent.
|
|
// The CPacket object that has arrived as a connection request is here
|
|
// reused for the connection rejection response (see URQ_ERROR_REJECT set
|
|
// as m_iReqType).
|
|
|
|
// send back a response if connection failed or connection already existed
|
|
// new connection response should be sent in acceptAndRespond()
|
|
if (result != 1)
|
|
{
|
|
HLOGC(mglog.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, Ref(size));
|
|
packet.m_iID = id;
|
|
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
|
|
m_pSndQueue->sendto(addr, packet);
|
|
}
|
|
else
|
|
{
|
|
// a new connection has been created, enable epoll for write
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
|
|
}
|
|
}
|
|
LOGC(mglog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType));
|
|
|
|
return RejectReasonForURQ(hs.m_iReqType);
|
|
}
|
|
|
|
void CUDT::addLossRecord(std::vector<int32_t> &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);
|
|
}
|
|
}
|
|
|
|
void CUDT::checkACKTimer(uint64_t currtime_tk)
|
|
{
|
|
if (currtime_tk > m_ullNextACKTime_tk // 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);
|
|
CTimer::rdtsc(currtime_tk);
|
|
|
|
const int ack_interval_tk =
|
|
m_CongCtl->ACKTimeout_us() > 0 ? m_CongCtl->ACKTimeout_us() * m_ullCPUFrequency : m_ullACKInt_tk;
|
|
m_ullNextACKTime_tk = currtime_tk + ack_interval_tk;
|
|
|
|
m_iPktCount = 0;
|
|
m_iLightACKCount = 1;
|
|
}
|
|
// 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_ullNextACKTime_tk. 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;
|
|
}
|
|
}
|
|
|
|
void CUDT::checkNAKTimer(uint64_t currtime_tk)
|
|
{
|
|
// 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_bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS)
|
|
return;
|
|
|
|
/*
|
|
* m_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).
|
|
*/
|
|
const int loss_len = m_pRcvLossList->getLossLength();
|
|
SRT_ASSERT(loss_len >= 0);
|
|
|
|
if (loss_len > 0)
|
|
{
|
|
if (currtime_tk <= m_ullNextNAKTime_tk)
|
|
return; // wait for next NAK time
|
|
|
|
sendCtrl(UMSG_LOSSREPORT);
|
|
}
|
|
|
|
m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk;
|
|
}
|
|
|
|
bool CUDT::checkExpTimer(uint64_t currtime_tk)
|
|
{
|
|
// In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class.
|
|
// There's nothing in the original code that alters these values.
|
|
|
|
uint64_t next_exp_time_tk;
|
|
if (m_CongCtl->RTO())
|
|
{
|
|
next_exp_time_tk = m_ullLastRspTime_tk + m_CongCtl->RTO() * m_ullCPUFrequency;
|
|
}
|
|
else
|
|
{
|
|
uint64_t exp_int_tk = (m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency;
|
|
if (exp_int_tk < m_iEXPCount * m_ullMinExpInt_tk)
|
|
exp_int_tk = m_iEXPCount * m_ullMinExpInt_tk;
|
|
next_exp_time_tk = m_ullLastRspTime_tk + exp_int_tk;
|
|
}
|
|
|
|
if (currtime_tk <= next_exp_time_tk)
|
|
return false;
|
|
|
|
// ms -> us
|
|
const int PEER_IDLE_TMO_US = m_iOPT_PeerIdleTimeout * 1000;
|
|
// Haven't received any information from the peer, is it dead?!
|
|
// timeout: at least 16 expirations and must be greater than 5 seconds
|
|
if ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) &&
|
|
(currtime_tk - m_ullLastRspTime_tk > PEER_IDLE_TMO_US * m_ullCPUFrequency))
|
|
{
|
|
//
|
|
// 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(mglog.Debug,
|
|
log << "CONNECTION EXPIRED after " << ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "ms");
|
|
m_bClosing = true;
|
|
m_bBroken = true;
|
|
m_iBrokenCounter = 30;
|
|
|
|
// update snd U list to remove this socket
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
|
|
|
|
releaseSynch();
|
|
|
|
// app can call any UDT API to learn the connection_broken error
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR, true);
|
|
|
|
CTimer::triggerEvent();
|
|
|
|
return true;
|
|
}
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed="
|
|
<< ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "/" << (+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_ullLastRspTime_tk = currtime_tk;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CUDT::checkRexmitTimer(uint64_t currtime_tk)
|
|
{
|
|
/* There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT.
|
|
*
|
|
* LATEREXMIT is only used with FileCC.
|
|
* The mode 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.
|
|
*
|
|
* FASTREXMIT is only used with LiveCC.
|
|
* The mode is triggered if the receiver does not 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 loss list and retransmitted.
|
|
*/
|
|
|
|
const uint64_t rtt_syn = (m_iRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US);
|
|
const uint64_t exp_int = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency;
|
|
|
|
if (currtime_tk <= (m_ullLastRspAckTime_tk + exp_int))
|
|
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;
|
|
const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT;
|
|
|
|
// If the receiver will send periodic NAK reports, then FASTREXMIT is inactive.
|
|
// MIND that probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off.
|
|
if (is_fastrexmit && m_bPeerNakReport)
|
|
return;
|
|
|
|
// We need to retransmit only when the data in the sender's buffer was already sent.
|
|
// Otherwise it might still be sent regulary.
|
|
bool retransmit = false;
|
|
// - the sender loss list is empty (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track)
|
|
if (is_laterexmit && (CSeqNo::incseq(m_iSndCurrSeqNo) != m_iSndLastAck) && m_pSndLossList->getLossLength() == 0)
|
|
retransmit = true;
|
|
|
|
if (is_fastrexmit && (CSeqNo::seqoff(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0))
|
|
retransmit = true;
|
|
|
|
if (retransmit)
|
|
{
|
|
// Sender: Insert all the packets sent after last received acknowledgement into the sender loss list.
|
|
CGuard acklock(m_RecvAckLock); // Protect packet retransmission
|
|
// 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)
|
|
{
|
|
CGuard::enterCS(m_StatsLock);
|
|
m_stats.traceSndLoss += num;
|
|
m_stats.sndLossTotal += num;
|
|
CGuard::leaveCS(m_StatsLock);
|
|
|
|
HLOGC(mglog.Debug,
|
|
log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT")
|
|
<< " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " ("
|
|
<< CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)");
|
|
}
|
|
}
|
|
|
|
++m_iReXmitCount;
|
|
|
|
checkSndTimers(DONT_REGEN_KM);
|
|
const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT;
|
|
updateCC(TEV_CHECKTIMER, stage);
|
|
|
|
// immediately restart transmission
|
|
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
|
|
}
|
|
|
|
void CUDT::checkTimers()
|
|
{
|
|
// update CC parameters
|
|
updateCC(TEV_CHECKTIMER, TEV_CHT_INIT);
|
|
// uint64_t minint = (uint64_t)(m_ullCPUFrequency * m_pSndTimeWindow->getMinPktSndInt() * 0.9);
|
|
// if (m_ullInterval_tk < minint)
|
|
// m_ullInterval_tk = minint;
|
|
// NOTE: This commented-out ^^^ code was commented out in original UDT. Leaving for historical reasons
|
|
|
|
uint64_t currtime_tk;
|
|
CTimer::rdtsc(currtime_tk);
|
|
|
|
// This is a very heavy log, unblock only for temporary debugging!
|
|
#if 0
|
|
HLOGC(mglog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_ullNextACKTime_tk)
|
|
<< " AckInterval=" << m_iACKInterval
|
|
<< " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount);
|
|
#endif
|
|
|
|
// Check if it is time to send ACK
|
|
checkACKTimer(currtime_tk);
|
|
|
|
// Check if it is time to send a loss report
|
|
checkNAKTimer(currtime_tk);
|
|
|
|
// Check if the connection is expired
|
|
if (checkExpTimer(currtime_tk))
|
|
return;
|
|
|
|
// Check if FAST or LATE packet retransmission is required
|
|
checkRexmitTimer(currtime_tk);
|
|
|
|
// uint64_t exp_int = (m_iRTT + 4 * m_iRTTVar + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency;
|
|
if (currtime_tk > m_ullLastSndTime_tk + (COMM_KEEPALIVE_PERIOD_US * m_ullCPUFrequency))
|
|
{
|
|
sendCtrl(UMSG_KEEPALIVE);
|
|
HLOGP(mglog.Debug, "KEEPALIVE");
|
|
}
|
|
}
|
|
|
|
void CUDT::addEPoll(const int eid)
|
|
{
|
|
CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
|
|
m_sPollID.insert(eid);
|
|
CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
|
|
|
|
if (!stillConnected())
|
|
return;
|
|
|
|
CGuard::enterCS(m_RecvLock);
|
|
if (m_pRcvBuffer->isRcvDataReady())
|
|
{
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true);
|
|
}
|
|
CGuard::leaveCS(m_RecvLock);
|
|
|
|
if (m_iSndBufSize > m_pSndBuffer->getCurrBufSize())
|
|
{
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
|
|
}
|
|
}
|
|
|
|
void CUDT::removeEPoll(const int eid)
|
|
{
|
|
// clear IO events notifications;
|
|
// since this happens after the epoll ID has been removed, they cannot be set again
|
|
set<int> remove;
|
|
remove.insert(eid);
|
|
s_UDTUnited.m_EPoll.update_events(m_SocketID, remove, UDT_EPOLL_IN | UDT_EPOLL_OUT, false);
|
|
|
|
CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
|
|
m_sPollID.erase(eid);
|
|
CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
|
|
}
|
|
|
|
void CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl)
|
|
{
|
|
if (evt >= TEV__SIZE)
|
|
return; // sanity check
|
|
|
|
m_Slots[evt].push_back(sl);
|
|
}
|
|
|
|
void CUDT::DisconnectSignal(ETransmissionEvent evt)
|
|
{
|
|
if (evt >= TEV__SIZE)
|
|
return; // sanity check
|
|
|
|
m_Slots[evt].clear();
|
|
}
|
|
|
|
void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var)
|
|
{
|
|
for (std::vector<EventSlot>::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 = s_UDTUnited.locate(u);
|
|
if (!s || !s->m_pUDT)
|
|
return -1;
|
|
|
|
CSndBuffer *b = s->m_pUDT->m_pSndBuffer;
|
|
|
|
if (!b)
|
|
return -1;
|
|
|
|
int bytecount, timespan;
|
|
int count = b->getCurrBufSize(Ref(bytecount), Ref(timespan));
|
|
|
|
if (blocks)
|
|
*blocks = count;
|
|
|
|
if (bytes)
|
|
*bytes = bytecount;
|
|
|
|
return std::abs(timespan);
|
|
}
|
|
|
|
SRT_REJECT_REASON CUDT::rejectReason(SRTSOCKET u)
|
|
{
|
|
CUDTSocket *s = s_UDTUnited.locate(u);
|
|
if (!s || !s->m_pUDT)
|
|
return SRT_REJ_UNKNOWN;
|
|
|
|
return s->m_pUDT->m_RejectReason;
|
|
}
|
|
|
|
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[MAX_SID_LENGTH + 1];
|
|
memset(target, 0, 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);
|
|
|
|
// 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<uint32_t *>(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, Ref(blocklen), Ref(next));
|
|
|
|
const size_t bytelen = blocklen * sizeof(uint32_t);
|
|
|
|
if (cmd == SRT_CMD_SID)
|
|
{
|
|
if (!bytelen || bytelen > MAX_SID_LENGTH)
|
|
{
|
|
LOGC(mglog.Error,
|
|
log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +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);
|
|
|
|
// Nothing more expected from connection block.
|
|
break;
|
|
}
|
|
else if (cmd == SRT_CMD_NONE)
|
|
{
|
|
// End of blocks
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Any other kind of message extracted. Search on.
|
|
length -= (next - begin);
|
|
begin = next;
|
|
if (begin)
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs->m_iVersion, peer, target);
|
|
if (result == -1)
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOGP(mglog.Error, "runAcceptHook: hook interrupted by exception");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|