Skip to content

Commit

Permalink
[core][tests] Clarified conditions for setting IPTOS/TTL options. Add…
Browse files Browse the repository at this point in the history
…ed UT (#3096).

* Added requirement for IPv6 for UDP Options test.
* Updated documentation.
  • Loading branch information
ethouris authored Dec 24, 2024
1 parent 1ec61fc commit 8a89a3a
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 44 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,7 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11)
srt_add_program_dont_install(test-srt ${SOURCES_unittests})
srt_make_application(test-srt)
target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS})
target_compile_definitions(test-srt PRIVATE "-DSRT_TEST_SYSTEM_NAME=\"${CMAKE_SYSTEM_NAME}\"")

target_link_libraries(
test-srt
Expand Down
4 changes: 4 additions & 0 deletions docs/API/API-socket-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,8 @@ See [`SRTO_INPUTBW`](#SRTO_INPUTBW).
IPv4 Type of Service (see `IP_TOS` option for IP) or IPv6 Traffic Class (see `IPV6_TCLASS`
of IPv6) depending on socket address family. Applies to sender only.

NOTE: This option has been tested to work correctly on Linux only.

When *getting*, the returned value is the user preset for non-connected sockets
and the actual value for connected sockets.

Expand All @@ -632,6 +634,8 @@ and the actual value for connected sockets.
IPv4 Time To Live (see `IP_TTL` option for IP) or IPv6 unicast hops (see
`IPV6_UNICAST_HOPS` for IPv6) depending on socket address family. Applies to sender only.

NOTE: This option has been tested to work correctly on Linux only.

When *getting*, the returned value is the user preset for non-connected sockets
and the actual value for connected sockets.

Expand Down
143 changes: 99 additions & 44 deletions srtcore/channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,20 @@ void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr)
setUDPSockOpt();
}

static inline string fmt_opt(bool value, const string& label)
{
string out;
out.reserve(label.size() + 2);
out = value ? "+" : "-";
out += label;
return out;
}

static inline string fmt_alt(bool value, const string& label, const string& unlabel)
{
return value ? label : unlabel;
}

void srt::CChannel::setUDPSockOpt()
{
#if defined(SUNOS)
Expand All @@ -297,13 +311,13 @@ void srt::CChannel::setUDPSockOpt()
// Retrieve starting SND/RCV Buffer sizes.
int startRCVBUF = 0;
optSize = sizeof(startRCVBUF);
if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize))
if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize))
{
startRCVBUF = -1;
}
int startSNDBUF = 0;
optSize = sizeof(startSNDBUF);
if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize))
if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize))
{
startSNDBUF = -1;
}
Expand All @@ -312,13 +326,12 @@ void srt::CChannel::setUDPSockOpt()
// maximum value.
// However, do not reduce the buffer size.
const int maxsize = 64000;
if (0 !=
::setsockopt(
m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF,
(const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
{
int currentRCVBUF = 0;
optSize = sizeof(currentRCVBUF);
if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&currentRCVBUF, &optSize))
if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&currentRCVBUF, &optSize))
{
currentRCVBUF = -1;
}
Expand All @@ -327,13 +340,12 @@ void srt::CChannel::setUDPSockOpt()
::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize);
}
}
if (0 !=
::setsockopt(
m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))
if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF,
(const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))
{
int currentSNDBUF = 0;
optSize = sizeof(currentSNDBUF);
if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&currentSNDBUF, &optSize))
if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&currentSNDBUF, &optSize))
{
currentSNDBUF = -1;
}
Expand All @@ -346,13 +358,13 @@ void srt::CChannel::setUDPSockOpt()
// Retrieve ending SND/RCV Buffer sizes.
int endRCVBUF = 0;
optSize = sizeof(endRCVBUF);
if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize))
if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize))
{
endRCVBUF = -1;
}
int endSNDBUF = 0;
optSize = sizeof(endSNDBUF);
if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize))
if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize))
{
endSNDBUF = -1;
}
Expand All @@ -368,90 +380,137 @@ void srt::CChannel::setUDPSockOpt()
#elif defined(BSD) || TARGET_OS_MAC
// BSD system will fail setsockopt if the requested buffer size exceeds system maximum value
int maxsize = 64000;
if (0 != ::setsockopt(
if (-1 == ::setsockopt(
m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize);
if (0 != ::setsockopt(
if (-1 == ::setsockopt(
m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))
::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize);
#else
// for other systems, if requested is greated than maximum, the maximum value will be automactally used
if ((0 !=
::setsockopt(
m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) ||
(0 != ::setsockopt(
m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)))
if ((-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF,
(const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
||
(-1 == ::setsockopt( m_iSocket, SOL_SOCKET, SO_SNDBUF,
(const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#endif

bool is_set = false;
bool adr_unspec = false, adr_mapped = false, adr_v6 = false;
if (m_BindAddr.family() == AF_INET)
{
adr_unspec = m_BindAddr.isany();
}
else
{
adr_unspec = IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr);
adr_mapped = IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr);
adr_v6 = true;
}

if (m_mcfg.iIpTTL != -1)
{
if (m_BindAddr.family() == AF_INET)
if (!adr_v6)
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
{
LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
is_set = true;
}
else
{
// If IPv6 address is unspecified, set BOTH IP_TTL and IPV6_UNICAST_HOPS.

// For specified IPv6 address, set IPV6_UNICAST_HOPS ONLY UNLESS it's an IPv4-mapped-IPv6
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) ||
!IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
if (adr_unspec || !adr_mapped)
{
if (0 !=
::setsockopt(
m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
if (-1 == ::setsockopt( m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
(const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
{
LOGC(kmlog.Error, log << "setsockopt(IPV6_UNICAST_HOPS): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
is_set = true;
}
// For specified IPv6 address, set IP_TTL ONLY WHEN it's an IPv4-mapped-IPv6
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
if (!is_set) // adr_mapped (because adr_unspec was handled above)
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
{
LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR)
<< fmt_alt(adr_unspec, " (v6 unspec)", " (v6 mapped v4)"));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
is_set = true;
}
}

if (!is_set)
{
LOGC(kmlog.Error, log << "srt_setsockflag(SRTO_IPTTL): No suitable condition for adr=" << m_BindAddr.str()
<< " : " << fmt_opt(adr_v6, "v6 ") << fmt_opt(adr_unspec, "unspec ") << fmt_opt(adr_mapped, "mapped"));
throw CUDTException(MJ_SETUP, MN_INVAL, 0);
}
}

is_set = false;
if (m_mcfg.iIpToS != -1)
{
if (m_BindAddr.family() == AF_INET)
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
{
LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
is_set = true;
}
else
{
// If IPv6 address is unspecified, set BOTH IP_TOS and IPV6_TCLASS.

SRT_ATR_UNUSED bool using_tclass = false;
#ifdef IPV6_TCLASS
using_tclass = true;
// For specified IPv6 address, set IPV6_TCLASS ONLY UNLESS it's an IPv4-mapped-IPv6
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) ||
!IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
if (adr_unspec || !adr_mapped)
{
if (0 != ::setsockopt(
m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
if (-1 == ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS,
(const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
{
LOGC(kmlog.Error, log << "setsockopt(IPV6_TCLASS): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
is_set = true;
}
#endif

// For specified IPv6 address, set IP_TOS ONLY WHEN it's an IPv4-mapped-IPv6
if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
if (!is_set && (adr_unspec || adr_mapped))
{
if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
{
LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR)
<< (adr_unspec ? " (v6 unspecified)" : " (v6 mapped v4)")
<< (using_tclass ? "(fallback to IP_TOS)" : ""));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
is_set = true;
}
}

if (!is_set)
{
LOGC(kmlog.Error, log << "srt_setsockflag(SRTO_IPTOS): No suitable condition for adr=" << m_BindAddr.str()
<< " : " << fmt_opt(adr_v6, "v6 ") << fmt_opt(adr_unspec, "unspec ") << fmt_opt(adr_mapped, "mapped"));
throw CUDTException(MJ_SETUP, MN_INVAL, 0);
}
}


#ifdef SRT_ENABLE_BINDTODEVICE
if (!m_mcfg.sBindToDevice.empty())
{
Expand All @@ -461,14 +520,10 @@ void srt::CChannel::setUDPSockOpt()
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}

if (0 != ::setsockopt(
m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size()))
if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE,
m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size()))
{
#if ENABLE_LOGGING
char buf[255];
const char* err = SysStrError(NET_ERROR, buf, 255);
LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << err);
#endif // ENABLE_LOGGING
LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
Expand All @@ -482,7 +537,7 @@ void srt::CChannel::setUDPSockOpt()
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#elif defined(_WIN32)
u_long nonBlocking = 1;
if (0 != ioctlsocket(m_iSocket, FIONBIO, &nonBlocking))
if (-1 == ioctlsocket(m_iSocket, FIONBIO, &nonBlocking))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#else
timeval tv;
Expand All @@ -495,7 +550,7 @@ void srt::CChannel::setUDPSockOpt()
tv.tv_usec = 100;
#endif
// Set receiving time-out value
if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval)))
if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#endif

Expand Down
10 changes: 10 additions & 0 deletions test/test_env.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ class TestEnv: public testing::Environment
// All must be static, return bool. Arguments allowed.
// The name must start with Allowed_.
static bool Allowed_IPv6();

template<typename... Args>
static bool Allowed_Platform(const std::string& first, const Args&... follow)
{
if (first == SRT_TEST_SYSTEM_NAME)
return true;
return Allowed_Platform(follow...);
}

static bool Allowed_Platform() { return false; }
};

#define SRTST_REQUIRES(feature,...) if (!srt::TestEnv::Allowed_##feature(__VA_ARGS__)) { return; }
Expand Down
23 changes: 23 additions & 0 deletions test/test_reuseaddr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,29 @@ TEST_F(ReuseAddr, DiffAddr)
shutdownListener(bindsock_2);
}

TEST_F(ReuseAddr, UDPOptions)
{
// IP_TOS and IP_TTL don't work on Windows and Mac
SRTST_REQUIRES(Platform, "Linux", "GNU");

// Travis doesn't work with IPv6
SRTST_REQUIRES(IPv6);

MAKE_UNIQUE_SOCK(bs1, "general ipv6", prepareServerSocket());
MAKE_UNIQUE_SOCK(bs2, "mapped ipv4", prepareServerSocket());

int val_TOS = 4; // IPTOS_RELIABILITY per <netinet/ip.h>, but not available on Windows
int val_TTL = 10;

EXPECT_NE(srt_setsockflag(bs1, SRTO_IPTOS, &val_TOS, sizeof val_TOS), SRT_ERROR);
EXPECT_NE(srt_setsockflag(bs1, SRTO_IPTTL, &val_TTL, sizeof val_TTL), SRT_ERROR);
EXPECT_NE(srt_setsockflag(bs2, SRTO_IPTOS, &val_TOS, sizeof val_TOS), SRT_ERROR);
EXPECT_NE(srt_setsockflag(bs2, SRTO_IPTTL, &val_TTL, sizeof val_TTL), SRT_ERROR);

bindSocket(bs1, "::1", 5000, true);
bindSocket(bs2, "::FFFF:127.0.0.1", 5001, true);
}

TEST_F(ReuseAddr, Wildcard)
{
#if defined(_WIN32) || defined(CYGWIN)
Expand Down

0 comments on commit 8a89a3a

Please sign in to comment.