diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ed11ef8e..981440189 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index 3291f6e27..fed2a5b5a 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -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. @@ -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. diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 9967d7daf..0ec8f090f 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -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) @@ -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; } @@ -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*)¤tRCVBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tRCVBUF, &optSize)) { currentRCVBUF = -1; } @@ -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*)¤tSNDBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tSNDBUF, &optSize)) { currentSNDBUF = -1; } @@ -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; } @@ -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()) { @@ -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); } } @@ -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; @@ -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 diff --git a/srtcore/congctl.cpp b/srtcore/congctl.cpp index 85d4cda97..9bc43db8b 100644 --- a/srtcore/congctl.cpp +++ b/srtcore/congctl.cpp @@ -60,7 +60,7 @@ void SrtCongestion::Check() class LiveCC: public SrtCongestionControlBase { - int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec) + srt::sync::atomic m_llSndMaxBW; //Max bandwidth (bytes/sec) srt::sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit size_t m_zMaxPayloadSize; size_t m_zHeaderSize; diff --git a/srtcore/platform_sys.h b/srtcore/platform_sys.h index 83763e5ea..5dd2b928d 100644 --- a/srtcore/platform_sys.h +++ b/srtcore/platform_sys.h @@ -68,7 +68,9 @@ // also other macros, like TARGET_OS_IOS etc. #include "TargetConditionals.h" +#ifndef __APPLE_USE_RFC_3542 #define __APPLE_USE_RFC_3542 /* IPV6_PKTINFO */ +#endif #ifdef SRT_IMPORT_TIME #include diff --git a/test/test_env.h b/test/test_env.h index b425fa808..5ca1a536e 100644 --- a/test/test_env.h +++ b/test/test_env.h @@ -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 + 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; } diff --git a/test/test_reuseaddr.cpp b/test/test_reuseaddr.cpp index df4aec487..14789f897 100644 --- a/test/test_reuseaddr.cpp +++ b/test/test_reuseaddr.cpp @@ -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 , 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)