#include "platform_sys.h"
#include <iostream>
#include <iomanip>
#include <srt_compat.h>
#include <csignal>
#include "channel.h"
#include "core.h"
#include "packet.h"
#include "logging.h"
#include "netinet_any.h"
#include "utilities.h"
#ifdef _WIN32
typedef int socklen_t;
#endif
using namespace std;
using namespace srt_logging;
#ifdef _WIN32
#else
static const int INVALID_SOCKET = -1;
#endif
#if ENABLE_SOCK_CLOEXEC
#ifndef _WIN32
#if defined(_AIX) || \
defined(__APPLE__) || \
defined(__DragonFly__) || \
defined(__FreeBSD__) || \
defined(__FreeBSD_kernel__) || \
defined(__linux__) || \
defined(__OpenBSD__) || \
defined(__NetBSD__)
static int set_cloexec(int fd, int set) {
int r;
do
r = ioctl(fd, set ? FIOCLEX : FIONCLEX);
while (r == -1 && errno == EINTR);
if (r)
return errno;
return 0;
}
#else
static int set_cloexec(int fd, int set) {
int flags;
int r;
do
r = fcntl(fd, F_GETFD);
while (r == -1 && errno == EINTR);
if (r == -1)
return errno;
if (!!(r & FD_CLOEXEC) == !!set)
return 0;
if (set)
flags = r | FD_CLOEXEC;
else
flags = r & ~FD_CLOEXEC;
do
r = fcntl(fd, F_SETFD, flags);
while (r == -1 && errno == EINTR);
if (r)
return errno;
return 0;
}
#endif #endif #endif
CChannel::CChannel():
m_iSocket(INVALID_SOCKET),
m_iIpTTL(-1),
m_iIpToS(-1),
m_iSndBufSize(65536),
m_iRcvBufSize(65536),
m_iIpV6Only(-1)
{
}
CChannel::~CChannel()
{
}
void CChannel::createSocket(int family)
{
#if ENABLE_SOCK_CLOEXEC
bool cloexec_flag = false;
#if defined(SOCK_CLOEXEC)
m_iSocket = ::socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (m_iSocket == INVALID_SOCKET)
{
m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP);
cloexec_flag = true;
}
#else
m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP);
cloexec_flag = true;
#endif
#else
m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP);
#endif
if (m_iSocket == INVALID_SOCKET)
throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR);
#if ENABLE_SOCK_CLOEXEC
#ifdef _WIN32
#else
if (cloexec_flag) {
if (0 != set_cloexec(m_iSocket, 1)) {
throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR);
}
}
#endif
#endif
if ((m_iIpV6Only != -1) && (family == AF_INET6)) {
int res ATR_UNUSED = ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)(&m_iIpV6Only), sizeof(m_iIpV6Only));
if (res == -1)
{
int err = errno;
char msg[160];
LOGC(kmlog.Error, log << "::setsockopt: failed to set IPPROTO_IPV6/IPV6_V6ONLY = " << m_iIpV6Only
<< ": " << SysStrError(err, msg, 159));
}
}
}
void CChannel::open(const sockaddr_any& addr)
{
createSocket(addr.family());
socklen_t namelen = addr.size();
if (::bind(m_iSocket, &addr.sa, namelen) == -1)
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
m_BindAddr = addr;
LOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str());
setUDPSockOpt();
}
void CChannel::open(int family)
{
createSocket(family);
addrinfo hints;
addrinfo* res;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = family;
hints.ai_socktype = SOCK_DGRAM;
const int eai = ::getaddrinfo(NULL, "0", &hints, &res);
if (eai != 0)
{
throw CUDTException(MJ_SETUP, MN_NORES, eai);
}
if (0 != ::bind(m_iSocket, res->ai_addr, (socklen_t)res->ai_addrlen))
{
::freeaddrinfo(res);
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
m_BindAddr = sockaddr_any(res->ai_addr, res->ai_addrlen);
::freeaddrinfo(res);
HLOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str());
setUDPSockOpt();
}
void CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr)
{
m_iSocket = udpsock;
m_BindAddr = udpsocks_addr;
setUDPSockOpt();
}
void CChannel::setUDPSockOpt()
{
#if defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1)
int maxsize = 64000;
if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_iRcvBufSize, sizeof(int)))
::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&maxsize, sizeof(int));
if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_iSndBufSize, sizeof(int)))
::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&maxsize, sizeof(int));
#else
if ((0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_iRcvBufSize, sizeof(int))) ||
(0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_iSndBufSize, sizeof(int))))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#endif
if (-1 != m_iIpTTL)
{
if (m_BindAddr.family() == AF_INET)
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_iIpTTL, sizeof(m_iIpTTL)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
else
{
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_iIpTTL, sizeof(m_iIpTTL)))
{
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_iIpTTL, sizeof(m_iIpTTL)))
{
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
}
}
if (-1 != m_iIpToS)
{
if (m_BindAddr.family() == AF_INET)
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_iIpToS, sizeof(m_iIpToS)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
else
{
#ifdef IPV6_TCLASS
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_iIpToS, sizeof(m_iIpToS)))
{
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
#endif
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_iIpToS, sizeof(m_iIpToS)))
{
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
}
}
#ifdef SRT_ENABLE_BINDTODEVICE
if (!m_BindToDevice.empty())
{
if (m_BindAddr.family() != AF_INET)
{
LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE can only be set with AF_INET connections");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_BindToDevice.c_str(), m_BindToDevice.size()))
{
char buf[255];
const char* err = SysStrError(NET_ERROR, buf, 255);
LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << err);
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
#endif
#ifdef UNIX
int opts = ::fcntl(m_iSocket, F_GETFL);
if (-1 == ::fcntl(m_iSocket, F_SETFL, opts | O_NONBLOCK))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#elif defined(_WIN32)
u_long nonBlocking = 1;
if (0 != ioctlsocket (m_iSocket, FIONBIO, &nonBlocking))
throw CUDTException (MJ_SETUP, MN_NORES, NET_ERROR);
#else
timeval tv;
tv.tv_sec = 0;
#if defined (BSD) || defined (OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1)
tv.tv_usec = 10000;
#else
tv.tv_usec = 100;
#endif
if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(timeval)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#endif
}
void CChannel::close() const
{
#ifndef _WIN32
::close(m_iSocket);
#else
::closesocket(m_iSocket);
#endif
}
int CChannel::getSndBufSize()
{
socklen_t size = sizeof(socklen_t);
::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char *)&m_iSndBufSize, &size);
return m_iSndBufSize;
}
int CChannel::getRcvBufSize()
{
socklen_t size = sizeof(socklen_t);
::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char *)&m_iRcvBufSize, &size);
return m_iRcvBufSize;
}
void CChannel::setSndBufSize(int size)
{
m_iSndBufSize = size;
}
void CChannel::setRcvBufSize(int size)
{
m_iRcvBufSize = size;
}
void CChannel::setIpV6Only(int ipV6Only)
{
m_iIpV6Only = ipV6Only;
}
int CChannel::getIpTTL() const
{
if (m_iSocket == INVALID_SOCKET)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
socklen_t size = sizeof(m_iIpTTL);
if (m_BindAddr.family() == AF_INET)
{
::getsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (char *)&m_iIpTTL, &size);
}
else if (m_BindAddr.family() == AF_INET6)
{
::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&m_iIpTTL, &size);
}
else
{
LOGC(kmlog.Error, log << "IPE: CChannel::getIpTTL called with unset family");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
return m_iIpTTL;
}
int CChannel::getIpToS() const
{
if (m_iSocket == INVALID_SOCKET)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
socklen_t size = sizeof(m_iIpToS);
if (m_BindAddr.family() == AF_INET)
{
::getsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (char *)&m_iIpToS, &size);
}
else if (m_BindAddr.family() == AF_INET6)
{
#ifdef IPV6_TCLASS
::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (char *)&m_iIpToS, &size);
#endif
}
else
{
LOGC(kmlog.Error, log << "IPE: CChannel::getIpToS called with unset family");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
return m_iIpToS;
}
void CChannel::setIpTTL(int ttl)
{
m_iIpTTL = ttl;
}
void CChannel::setIpToS(int tos)
{
m_iIpToS = tos;
}
#ifdef SRT_ENABLE_BINDTODEVICE
void CChannel::setBind(const string& name)
{
m_BindToDevice = name;
}
bool CChannel::getBind(char* dst, size_t len)
{
if (m_iSocket == INVALID_SOCKET)
return false;
socklen_t length = len;
int res = ::getsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, dst, &length);
if (res == -1)
return false;
dst[length] = 0;
return true;
}
#endif
int CChannel::ioctlQuery(int type SRT_ATR_UNUSED) const
{
#if defined(unix) || defined(__APPLE__)
int value = 0;
int res = ::ioctl(m_iSocket, type, &value);
if ( res != -1 )
return value;
#endif
return -1;
}
int CChannel::sockoptQuery(int level SRT_ATR_UNUSED, int option SRT_ATR_UNUSED) const
{
#if defined(unix) || defined(__APPLE__)
int value = 0;
socklen_t len = sizeof (int);
int res = ::getsockopt(m_iSocket, level, option, &value, &len);
if ( res != -1 )
return value;
#endif
return -1;
}
void CChannel::getSockAddr(sockaddr_any& w_addr) const
{
socklen_t namelen = w_addr.storage_size();
::getsockname(m_iSocket, (w_addr.get()), (&namelen));
w_addr.len = namelen;
}
void CChannel::getPeerAddr(sockaddr_any& w_addr) const
{
socklen_t namelen = w_addr.storage_size();
::getpeername(m_iSocket, (w_addr.get()), (&namelen));
w_addr.len = namelen;
}
int CChannel::sendto(const sockaddr_any& addr, CPacket& packet) const
{
HLOGC(kslog.Debug, log << "CChannel::sendto: SENDING NOW DST=" << addr.str()
<< " target=@" << packet.m_iID
<< " size=" << packet.getLength()
<< " pkt.ts=" << packet.m_iTimeStamp
<< " " << packet.Info());
#ifdef SRT_TEST_FAKE_LOSS
#define FAKELOSS_STRING_0(x) #x
#define FAKELOSS_STRING(x) FAKELOSS_STRING_0(x)
const char* fakeloss_text = FAKELOSS_STRING(SRT_TEST_FAKE_LOSS);
#undef FAKELOSS_STRING
#undef FAKELOSS_WRAP
static int dcounter = 0;
static int flwcounter = 0;
struct FakelossConfig
{
pair<int,int> config;
FakelossConfig(const char* f)
{
vector<string> out;
Split(f, '+', back_inserter(out));
config.first = atoi(out[0].c_str());
config.second = out.size() > 1 ? atoi(out[1].c_str()) : 8;
}
};
static FakelossConfig fakeloss = fakeloss_text;
if (!packet.isControl())
{
if (dcounter == 0)
{
timeval tv;
gettimeofday(&tv, 0);
srand(tv.tv_usec & 0xFFFF);
}
++dcounter;
if (flwcounter)
{
--flwcounter;
HLOGC(kslog.Debug, log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (" << flwcounter << " more to drop)");
return packet.getLength(); }
if (dcounter > 8)
{
int rnd = rand() % 16 + SRT_TEST_FAKE_LOSS;
if (dcounter > rnd)
{
dcounter = 1;
HLOGC(kslog.Debug, log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (will drop " << fakeloss.config.first << " more)");
flwcounter = fakeloss.config.first;
return packet.getLength(); }
}
}
#endif
packet.toNL();
#ifndef _WIN32
msghdr mh;
mh.msg_name = (sockaddr*)&addr;
mh.msg_namelen = addr.size();
mh.msg_iov = (iovec*)packet.m_PacketVector;
mh.msg_iovlen = 2;
mh.msg_control = NULL;
mh.msg_controllen = 0;
mh.msg_flags = 0;
const int res = ::sendmsg(m_iSocket, &mh, 0);
#else
DWORD size = (DWORD) (CPacket::HDR_SIZE + packet.getLength());
int addrsize = addr.size();
int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, NULL, NULL);
res = (0 == res) ? size : -1;
#endif
packet.toHL();
return res;
}
EReadStatus CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const
{
EReadStatus status = RST_OK;
int msg_flags = 0;
int recv_size = -1;
#if defined(UNIX) || defined(_WIN32)
fd_set set;
timeval tv;
FD_ZERO(&set);
FD_SET(m_iSocket, &set);
tv.tv_sec = 0;
tv.tv_usec = 10000;
const int select_ret = ::select((int) m_iSocket + 1, &set, NULL, &set, &tv);
#else
const int select_ret = 1; #endif
if (select_ret == 0) {
w_packet.setLength(-1);
return RST_AGAIN;
}
#ifndef _WIN32
if (select_ret > 0)
{
msghdr mh;
mh.msg_name = (w_addr.get());
mh.msg_namelen = w_addr.size();
mh.msg_iov = (w_packet.m_PacketVector);
mh.msg_iovlen = 2;
mh.msg_control = NULL;
mh.msg_controllen = 0;
mh.msg_flags = 0;
recv_size = ::recvmsg(m_iSocket, (&mh), 0);
msg_flags = mh.msg_flags;
}
if (select_ret == -1 || recv_size == -1)
{
const int err = NET_ERROR;
if (err == EAGAIN || err == EINTR || err == ECONNREFUSED) {
status = RST_AGAIN;
}
else
{
HLOGC(krlog.Debug, log << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]");
status = RST_ERROR;
}
goto Return_error;
}
#else
int recv_ret = SOCKET_ERROR;
DWORD flag = 0;
if (select_ret > 0) {
DWORD size = (DWORD) (CPacket::HDR_SIZE + w_packet.getLength());
int addrsize = w_addr.size();
recv_ret = ::WSARecvFrom(m_iSocket, ((LPWSABUF)w_packet.m_PacketVector), 2,
(&size), (&flag), (w_addr.get()), (&addrsize), NULL, NULL);
if (recv_ret == 0)
recv_size = size;
}
if (select_ret == SOCKET_ERROR || recv_ret == SOCKET_ERROR) {
recv_size = -1;
static const int fatals [] =
{
WSAEFAULT,
WSAEINVAL,
WSAENETDOWN,
WSANOTINITIALISED,
WSA_OPERATION_ABORTED
};
static const int* fatals_end = fatals + Size(fatals);
const int err = NET_ERROR;
if (std::find(fatals, fatals_end, err) != fatals_end)
{
HLOGC(krlog.Debug, log << CONID() << "(sys)WSARecvFrom: " << SysStrError(err) << " [" << err << "]");
status = RST_ERROR;
}
else
{
status = RST_AGAIN;
}
goto Return_error;
}
if (flag & MSG_PARTIAL)
msg_flags = 1;
#endif
if (size_t(recv_size) < CPacket::HDR_SIZE)
{
status = RST_AGAIN;
HLOGC(krlog.Debug, log << CONID() << "POSSIBLE ATTACK: received too short packet with " << recv_size << " bytes");
goto Return_error;
}
if ( msg_flags != 0 )
{
HLOGC(krlog.Debug, log << CONID() << "NET ERROR: packet size=" << recv_size
<< " msg_flags=0x" << hex << msg_flags << ", possibly MSG_TRUNC (0x" << hex << int(MSG_TRUNC) << ")");
status = RST_AGAIN;
goto Return_error;
}
w_packet.setLength(recv_size - CPacket::HDR_SIZE);
{
uint32_t* p = w_packet.m_nHeader;
for (size_t i = 0; i < SRT_PH_E_SIZE; ++ i)
{
*p = ntohl(*p);
++ p;
}
}
if (w_packet.isControl())
{
for (size_t j = 0, n = w_packet.getLength() / sizeof (uint32_t); j < n; ++ j)
*((uint32_t *)w_packet.m_pcData + j) = ntohl(*((uint32_t *)w_packet.m_pcData + j));
}
return RST_OK;
Return_error:
w_packet.setLength(-1);
return status;
}