mistserver/lib/socket_srt.cpp
2021-10-19 22:29:41 +02:00

548 lines
20 KiB
C++

#include "defines.h"
#include "lib/http_parser.h"
#include "socket_srt.h"
#include <cstdlib>
#include <sstream>
#define INVALID_SRT_SOCKET -1
namespace Socket{
namespace SRT{
bool isInited = false;
// Both Init and Cleanup functions are called implicitly if not done ourselves.
// SRT documentation states explicitly that this is unreliable behaviour
bool libraryInit(){
if (!isInited){
int res = srt_startup();
if (res == -1){ERROR_MSG("Unable to initialize SRT Library!");}
isInited = (res != -1);
}
return isInited;
}
bool libraryCleanup(){
if (isInited){
srt_cleanup();
isInited = false;
}
return true;
}
}// namespace SRT
template <typename T> std::string asString(const T &val){
std::stringstream x;
x << val;
return x.str();
}
sockaddr_in createInetAddr(const std::string &_host, int _port){
sockaddr_in res;
memset(&res, 9, sizeof res);
res.sin_family = AF_INET;
res.sin_port = htons(_port);
if (_host != ""){
if (inet_pton(AF_INET, _host.c_str(), &res.sin_addr) == 1){return res;}
hostent *he = gethostbyname(_host.c_str());
if (!he || he->h_addrtype != AF_INET){ERROR_MSG("Host not found %s", _host.c_str());}
res.sin_addr = *(in_addr *)he->h_addr_list[0];
}
return res;
}
std::string interpretSRTMode(const std::string &_mode, const std::string &_host, const std::string &_adapter){
if (_mode == "client" || _mode == "caller"){return "caller";}
if (_mode == "server" || _mode == "listener"){return "listener";}
if (_mode == "rendezvouz"){return "rendezvous";}
if (_mode != "default"){return "";}
if (_host == ""){return "listener";}
if (_adapter != ""){return "rendezvous";}
return "caller";
}
std::string interpretSRTMode(const HTTP::URL &u){
paramList params;
HTTP::parseVars(u.args, params);
return interpretSRTMode(params.count("mode") ? params.at("mode") : "default", u.host, "");
}
SRTConnection::SRTConnection(){initializeEmpty();}
SRTConnection::SRTConnection(const std::string &_host, int _port, const std::string &_direction,
const std::map<std::string, std::string> &_params){
connect(_host, _port, _direction, _params);
}
std::string SRTConnection::getStreamName(){
int sNameLen = 512;
char sName[sNameLen];
int optRes = srt_getsockflag(sock, SRTO_STREAMID, (void *)sName, &sNameLen);
if (optRes != -1 && sNameLen){return sName;}
return "";
}
/// Updates the downbuffer internal variable.
/// Returns true if new data was received, false otherwise.
std::string SRTConnection::RecvNow(){
char recvbuf[5000];
bool blockState = blocking;
setBlocking(true);
SRT_MSGCTRL mc = srt_msgctrl_default;
int32_t receivedBytes = srt_recvmsg2(sock, recvbuf, 5000, &mc);
if (prev_pktseq != 0 && (mc.pktseq - prev_pktseq > 1)){WARN_MSG("Packet lost");}
prev_pktseq = mc.pktseq;
setBlocking(blockState);
if (receivedBytes == -1){
ERROR_MSG("Unable to receive data over socket: %s", srt_getlasterror_str());
if (srt_getsockstate(sock) != SRTS_CONNECTED){close();}
return "";
}
srt_bstats(sock, &performanceMonitor, false);
return std::string(recvbuf, receivedBytes);
}
void SRTConnection::connect(const std::string &_host, int _port, const std::string &_direction,
const std::map<std::string, std::string> &_params){
initializeEmpty();
direction = _direction;
handleConnectionParameters(_host, _params);
HIGH_MSG("Opening SRT connection %s in %s mode on %s:%d", modeName.c_str(), direction.c_str(),
_host.c_str(), _port);
sock = srt_create_socket();
if (sock == SRT_ERROR){
ERROR_MSG("Error creating an SRT socket");
return;
}
if (modeName == "rendezvous"){
bool v = true;
srt_setsockopt(sock, 0, SRTO_RENDEZVOUS, &v, sizeof v);
}
if (preConfigureSocket() == SRT_ERROR){
ERROR_MSG("Error configuring SRT socket");
return;
}
if (modeName == "caller"){
if (outgoing_port){setupAdapter("", outgoing_port);}
sockaddr_in sa = createInetAddr(_host, _port);
sockaddr *psa = (sockaddr *)&sa;
HIGH_MSG("Going to connect sock %d", sock);
if (srt_connect(sock, psa, sizeof sa) == SRT_ERROR){
srt_close(sock);
ERROR_MSG("Can't connect SRT Socket");
return;
}
HIGH_MSG("Connected sock %d", sock);
if (postConfigureSocket() == SRT_ERROR){
ERROR_MSG("Error during postconfigure socket");
return;
}
INFO_MSG("Caller SRT socket %" PRId32 " success targetting %s:%u", sock, _host.c_str(), _port);
return;
}
if (modeName == "listener"){
HIGH_MSG("Going to bind a server on %s:%u", _host.c_str(), _port);
sockaddr_in sa = createInetAddr(_host, _port);
sockaddr *psa = (sockaddr *)&sa;
if (srt_bind(sock, psa, sizeof sa) == SRT_ERROR){
srt_close(sock);
ERROR_MSG("Can't connect SRT Socket");
return;
}
if (srt_listen(sock, 1) == SRT_ERROR){
srt_close(sock);
ERROR_MSG("Can not listen on Socket");
}
INFO_MSG("Listener SRT socket sucess @ %s:%u", _host.c_str(), _port);
return;
}
if (modeName == "rendezvous"){
int outport = (outgoing_port ? outgoing_port : _port);
HIGH_MSG("Going to bind a server on %s:%u", _host.c_str(), _port);
sockaddr_in sa = createInetAddr(_host, outport);
sockaddr *psa = (sockaddr *)&sa;
if (srt_bind(sock, psa, sizeof sa) == SRT_ERROR){
srt_close(sock);
ERROR_MSG("Can't connect SRT Socket");
return;
}
sockaddr_in sb = createInetAddr(_host, outport);
sockaddr *psb = (sockaddr *)&sb;
if (srt_connect(sock, psb, sizeof sb) == SRT_ERROR){
srt_close(sock);
ERROR_MSG("Can't connect SRT Socket");
return;
}
if (postConfigureSocket() == SRT_ERROR){
ERROR_MSG("Error during postconfigure socket");
return;
}
INFO_MSG("Rendezvous SRT socket sucess @ %s:%u", _host.c_str(), _port);
return;
}
ERROR_MSG("Invalid mode parameter. Use 'client' or 'server'");
}
void SRTConnection::setupAdapter(const std::string &_host, int _port){
sockaddr_in localsa = createInetAddr(_host, _port);
sockaddr *psa = (sockaddr *)&localsa;
if (srt_bind(sock, psa, sizeof localsa) == SRT_ERROR){
ERROR_MSG("Unable to bind socket to %s:%u", _host.c_str(), _port);
}
}
void SRTConnection::SendNow(const std::string &data){SendNow(data.data(), data.size());}
void SRTConnection::SendNow(const char *data, size_t len){
srt_clearlasterror();
int res = srt_sendmsg2(sock, data, len, NULL);
if (res == SRT_ERROR){
ERROR_MSG("Unable to send data over socket %" PRId32 ": %s", sock, srt_getlasterror_str());
if (srt_getsockstate(sock) != SRTS_CONNECTED){close();}
}
srt_bstats(sock, &performanceMonitor, false);
}
unsigned int SRTConnection::connTime(){
srt_bstats(sock, &performanceMonitor, false);
return performanceMonitor.msTimeStamp / 1000;
}
uint64_t SRTConnection::dataUp(){return performanceMonitor.byteSentTotal;}
uint64_t SRTConnection::dataDown(){return performanceMonitor.byteRecvTotal;}
uint64_t SRTConnection::packetCount(){
return (direction == "input" ? performanceMonitor.pktRecvTotal : performanceMonitor.pktSentTotal);
}
uint64_t SRTConnection::packetLostCount(){
return (direction == "input" ? performanceMonitor.pktRcvLossTotal : performanceMonitor.pktSndLossTotal);
}
uint64_t SRTConnection::packetRetransmitCount(){
//\todo This should be updated with pktRcvRetransTotal on the retrieving end once srt has implemented this.
return (direction == "input" ? 0 : performanceMonitor.pktRetransTotal);
}
void SRTConnection::initializeEmpty(){
prev_pktseq = 0;
sock = SRT_INVALID_SOCK;
outgoing_port = 0;
chunkTransmitSize = 1316;
blocking = false;
}
void SRTConnection::setBlocking(bool _blocking){
if (_blocking == blocking){return;}
// If we have an error setting the new blocking state, the state is unchanged so we return early.
if (srt_setsockopt(sock, 0, (direction == "output" ? SRTO_SNDSYN : SRTO_RCVSYN), &_blocking,
sizeof _blocking) == -1){
return;
}
blocking = _blocking;
}
bool SRTConnection::isBlocking(){return blocking;}
void SRTConnection::handleConnectionParameters(const std::string &_host,
const std::map<std::string, std::string> &_params){
params = _params;
DONTEVEN_MSG("SRT Received parameters: ");
for (std::map<std::string, std::string>::const_iterator it = params.begin(); it != params.end(); it++){
DONTEVEN_MSG(" %s: %s", it->first.c_str(), it->second.c_str());
}
adapter = (params.count("adapter") ? params.at("adapter") : "");
modeName = interpretSRTMode((params.count("mode") ? params.at("mode") : "default"), _host, adapter);
if (modeName == ""){
ERROR_MSG("Invalid SRT mode encountered");
return;
}
// Using strtol because the original code uses base 0 -> automatic detection of octal and hexadecimal systems.
timeout = (params.count("timeout") ? strtol(params.at("timeout").c_str(), 0, 0) : 0);
if (adapter == "" && modeName == "listener"){adapter = _host;}
tsbpdMode = ((params.count("tsbpd") && isFalseString(params.at("tsbpd"))) ? false : true);
outgoing_port = (params.count("port") ? strtol(params.at("port").c_str(), 0, 0) : 0);
if ((!params.count("transtype") || params.at("transtype") != "file") && chunkTransmitSize > SRT_LIVE_DEF_PLSIZE){
if (chunkTransmitSize > SRT_LIVE_MAX_PLSIZE){
ERROR_MSG("Chunk size in live mode exceeds 1456 bytes!");
return;
}
}
params["payloadsize"] = asString(chunkTransmitSize);
}
int SRTConnection::preConfigureSocket(){
bool no = false;
if (!tsbpdMode){
if (srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no) == -1){return -1;}
}
if (srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no) == -1){return -1;}
if (params.count("linger")){
linger lin;
lin.l_linger = atoi(params.at("linger").c_str());
lin.l_onoff = lin.l_linger > 0 ? 1 : 0;
srt_setsockopt(sock, 0, SRTO_LINGER, &lin, sizeof(linger));
}
std::string errMsg = configureSocketLoop(SRT::SockOpt::PRE);
if (errMsg.size()){
WARN_MSG("Failed to set the following options: %s", errMsg.c_str());
return SRT_ERROR;
}
if (direction == "output"){
int v = 1;
if (srt_setsockopt(sock, 0, SRTO_SENDER, &v, sizeof v) == SRT_ERROR){return SRT_ERROR;}
}
return 0;
}
int SRTConnection::postConfigureSocket(){
bool no = false;
if (srt_setsockopt(sock, 0, (direction == "output" ? SRTO_SNDSYN : SRTO_RCVSYN), &no, sizeof no) == -1){
return -1;
}
if (timeout){
if (srt_setsockopt(sock, 0, (direction == "output" ? SRTO_SNDTIMEO : SRTO_RCVTIMEO), &timeout,
sizeof timeout) == -1){
return -1;
}
}
std::string errMsg = configureSocketLoop(SRT::SockOpt::POST);
if (errMsg.size()){
WARN_MSG("Failed to set the following options: %s", errMsg.c_str());
return SRT_ERROR;
}
return 0;
}
std::string SRTConnection::configureSocketLoop(SRT::SockOpt::Binding _binding){
std::string errMsg;
std::vector<SocketOption> allSrtOptions = srtOptions();
for (std::vector<SocketOption>::iterator it = allSrtOptions.begin(); it != allSrtOptions.end(); it++){
if (it->binding == _binding && params.count(it->name)){
std::string value = params.at(it->name);
if (!it->apply(sock, value)){errMsg += it->name + " ";}
}
}
return errMsg;
}
void SRTConnection::close(){
if (sock != -1){
srt_close(sock);
sock = -1;
}
}
SRTServer::SRTServer(){}
SRTServer::SRTServer(int fromSock){conn = SRTConnection(fromSock);}
SRTServer::SRTServer(int port, std::string hostname, bool nonblock, const std::string &_direction){
// We always create a server as listening
std::map<std::string, std::string> listenMode;
listenMode["mode"] = "listener";
if (hostname == ""){hostname = "0.0.0.0";}
conn.connect(hostname, port, _direction, listenMode);
conn.setBlocking(true);
if (!conn){
ERROR_MSG("Unable to create socket");
return;
}
}
SRTConnection SRTServer::accept(bool nonblock, const std::string &direction){
if (!conn){return SRTConnection();}
struct sockaddr_in6 tmpaddr;
int len = sizeof(tmpaddr);
SRTConnection r(srt_accept(conn.getSocket(), (sockaddr *)&tmpaddr, &len));
if (!r){
if (conn.getSocket() != -1 && srt_getlasterror(0) != SRT_EASYNCRCV){
FAIL_MSG("Error during accept: %s. Closing server socket %d.", srt_getlasterror_str(), conn.getSocket());
close();
}
return r;
}
r.direction = direction;
r.postConfigureSocket();
r.setBlocking(!nonblock);
static char addrconv[INET6_ADDRSTRLEN];
r.remoteaddr = tmpaddr;
if (tmpaddr.sin6_family == AF_INET6){
r.remotehost = inet_ntop(AF_INET6, &(tmpaddr.sin6_addr), addrconv, INET6_ADDRSTRLEN);
HIGH_MSG("IPv6 addr [%s]", r.remotehost.c_str());
}
if (tmpaddr.sin6_family == AF_INET){
r.remotehost = inet_ntop(AF_INET, &(((sockaddr_in *)&tmpaddr)->sin_addr), addrconv, INET6_ADDRSTRLEN);
HIGH_MSG("IPv4 addr [%s]", r.remotehost.c_str());
}
INFO_MSG("Accepted a socket coming from %s", r.remotehost.c_str());
return r;
}
void SRTServer::setBlocking(bool blocking){conn.setBlocking(blocking);}
bool SRTServer::isBlocking(){return (conn ? conn.isBlocking() : false);}
void SRTServer::close(){conn.close();}
bool SRTServer::connected() const{return conn.connected();}
int SRTServer::getSocket(){return conn.getSocket();}
inline int SocketOption::setSo(int socket, int proto, int sym, const void *data, size_t size, bool isSrtOpt){
if (isSrtOpt){return srt_setsockopt(socket, 0, SRT_SOCKOPT(sym), data, (int)size);}
return ::setsockopt(socket, proto, sym, (const char *)data, (int)size);
}
bool SocketOption::extract(const std::string &v, OptionValue &val, SRT::SockOpt::Type asType){
switch (asType){
case SRT::SockOpt::STRING:
val.s = v;
val.value = val.s.data();
val.size = val.s.size();
break;
case SRT::SockOpt::INT:
case SRT::SockOpt::INT64:{
int64_t tmp = strtol(v.c_str(), 0, 0);
if (tmp == 0 && (!v.size() || v[0] != '0')){return false;}
if (asType == SRT::SockOpt::INT){
val.i = tmp;
val.value = &val.i;
val.size = sizeof(val.i);
}else{
val.l = tmp;
val.value = &val.l;
val.size = sizeof(val.l);
}
}break;
case SRT::SockOpt::BOOL:{
bool tmp;
if (isFalseString(v)){
tmp = true;
}else if (isTrueString(v)){
tmp = true;
}else{
return false;
}
val.b = tmp;
val.value = &val.b;
val.size = sizeof val.b;
}break;
case SRT::SockOpt::ENUM:{
// Search value in the map. If found, set to o.
SockOptVals::const_iterator p = valmap.find(v);
if (p != valmap.end()){
val.i = p->second;
val.value = &val.i;
val.size = sizeof val.i;
return true;
}
// Fallback: try interpreting it as integer.
return extract(v, val, SRT::SockOpt::INT);
}
}
return true;
}
bool SocketOption::apply(int socket, const std::string &value, bool isSrtOpt){
OptionValue o;
int result = -1;
if (extract(value, o, type)){
result = setSo(socket, protocol, symbol, o.value, o.size, isSrtOpt);
}
return result != -1;
}
const std::map<std::string, int> enummap_transtype;
std::vector<SocketOption> srtOptions(){
static std::map<std::string, int> enummap_transtype;
if (!enummap_transtype.size()){
enummap_transtype["live"] = SRTT_LIVE;
enummap_transtype["file"] = SRTT_FILE;
}
static std::vector<SocketOption> res;
if (res.size()){return res;}
res.push_back(SocketOption("transtype", 0, SRTO_TRANSTYPE, SRT::SockOpt::PRE,
SRT::SockOpt::ENUM, enummap_transtype));
res.push_back(SocketOption("maxbw", 0, SRTO_MAXBW, SRT::SockOpt::PRE, SRT::SockOpt::INT64));
res.push_back(SocketOption("pbkeylen", 0, SRTO_PBKEYLEN, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("passphrase", 0, SRTO_PASSPHRASE, SRT::SockOpt::PRE, SRT::SockOpt::STRING));
res.push_back(SocketOption("mss", 0, SRTO_MSS, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("fc", 0, SRTO_FC, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("sndbuf", 0, SRTO_SNDBUF, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("rcvbuf", 0, SRTO_RCVBUF, SRT::SockOpt::PRE, SRT::SockOpt::INT));
// linger option is handled outside of the common loop, therefore commented out.
// res.push_back(SocketOption( "linger", 0, SRTO_LINGER, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("ipttl", 0, SRTO_IPTTL, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("iptos", 0, SRTO_IPTOS, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("inputbw", 0, SRTO_INPUTBW, SRT::SockOpt::POST, SRT::SockOpt::INT64));
res.push_back(SocketOption("oheadbw", 0, SRTO_OHEADBW, SRT::SockOpt::POST, SRT::SockOpt::INT));
res.push_back(SocketOption("latency", 0, SRTO_LATENCY, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("tsbpdmode", 0, SRTO_TSBPDMODE, SRT::SockOpt::PRE, SRT::SockOpt::BOOL));
res.push_back(SocketOption("tlpktdrop", 0, SRTO_TLPKTDROP, SRT::SockOpt::PRE, SRT::SockOpt::BOOL));
res.push_back(SocketOption("snddropdelay", 0, SRTO_SNDDROPDELAY, SRT::SockOpt::POST, SRT::SockOpt::INT));
res.push_back(SocketOption("nakreport", 0, SRTO_NAKREPORT, SRT::SockOpt::PRE, SRT::SockOpt::BOOL));
res.push_back(SocketOption("conntimeo", 0, SRTO_CONNTIMEO, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("lossmaxttl", 0, SRTO_LOSSMAXTTL, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("rcvlatency", 0, SRTO_RCVLATENCY, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("peerlatency", 0, SRTO_PEERLATENCY, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("minversion", 0, SRTO_MINVERSION, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("streamid", 0, SRTO_STREAMID, SRT::SockOpt::PRE, SRT::SockOpt::STRING));
res.push_back(SocketOption("congestion", 0, SRTO_CONGESTION, SRT::SockOpt::PRE, SRT::SockOpt::STRING));
res.push_back(SocketOption("messageapi", 0, SRTO_MESSAGEAPI, SRT::SockOpt::PRE, SRT::SockOpt::BOOL));
// res.push_back(SocketOption("payloadsize", 0, SRTO_PAYLOADSIZE, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("kmrefreshrate", 0, SRTO_KMREFRESHRATE, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("kmpreannounce", 0, SRTO_KMPREANNOUNCE, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("enforcedencryption", 0, SRTO_ENFORCEDENCRYPTION, SRT::SockOpt::PRE,
SRT::SockOpt::BOOL));
res.push_back(SocketOption("peeridletimeo", 0, SRTO_PEERIDLETIMEO, SRT::SockOpt::PRE, SRT::SockOpt::INT));
res.push_back(SocketOption("packetfilter", 0, SRTO_PACKETFILTER, SRT::SockOpt::PRE, SRT::SockOpt::STRING));
// res.push_back(SocketOption( "groupconnect", 0, SRTO_GROUPCONNECT, SRT::SockOpt::PRE, SRT::SockOpt::INT));
// res.push_back(SocketOption( "groupstabtimeo", 0, SRTO_GROUPSTABTIMEO, SRT::SockOpt::PRE, SRT::SockOpt::INT));
return res;
}
}// namespace Socket