From 34b8f54e90a684172c677cba12018424fc6721fe Mon Sep 17 00:00:00 2001 From: BX Date: Wed, 26 Feb 2025 18:46:49 +1000 Subject: [PATCH 1/3] Fix bind to local interface when sending to udp ep --- apps/transmitmedia.cpp | 57 ++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 8e535cf07..f9ceb0a93 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -805,8 +805,10 @@ class UdpCommon { protected: int m_sock = -1; - sockaddr_any sadr; string adapter; + sockaddr_any interface_addr; + sockaddr_any target_addr; + bool is_multicast; map m_options; void Setup(string host, int port, map attr) @@ -829,25 +831,30 @@ class UdpCommon Error(SysError(), "UdpCommon::Setup: ioctl FIONBIO"); } - sadr = CreateAddr(host, port); + interface_addr = sockaddr_any(AF_INET); + interface_addr.sin.sin_family = AF_INET; + interface_addr.sin.sin_addr.s_addr = htonl(INADDR_ANY); + interface_addr.sin.sin_port = htons(port); - bool is_multicast = false; + target_addr = CreateAddr(host, port); + + is_multicast = false; if (attr.count("multicast")) { // XXX: Here provide support for IPv6 multicast #1479 - if (sadr.family() != AF_INET) + if (target_addr.family() != AF_INET) { throw std::runtime_error("UdpCommon: Multicast on IPv6 is not yet supported"); } - if (!IsMulticast(sadr.sin.sin_addr)) + if (!IsMulticast(target_addr.sin.sin_addr)) { throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address"); } is_multicast = true; } - else if (sadr.family() == AF_INET && IsMulticast(sadr.sin.sin_addr)) + else if (target_addr.family() == AF_INET && IsMulticast(target_addr.sin.sin_addr)) { is_multicast = true; } @@ -855,7 +862,6 @@ class UdpCommon if (is_multicast) { ip_mreq mreq; - sockaddr_any maddr (AF_INET); int opt_name; void* mreq_arg_ptr; socklen_t mreq_arg_size; @@ -864,14 +870,11 @@ class UdpCommon if ( adapter == "" ) { Verb() << "Multicast: home address: INADDR_ANY:" << port; - maddr.sin.sin_family = AF_INET; - maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - maddr.sin.sin_port = htons(port); // necessary for temporary use } else { Verb() << "Multicast: home address: " << adapter << ":" << port; - maddr = CreateAddr(adapter, port); + interface_addr = CreateAddr(adapter, port); } if (attr.count("source")) @@ -880,8 +883,8 @@ class UdpCommon ip_mreq_source mreq_ssm; /* this is an ssm. we need to use the right struct and opt */ opt_name = IP_ADD_SOURCE_MEMBERSHIP; - mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq_ssm.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq_ssm.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); mreq_arg_ptr = &mreq_ssm; @@ -892,10 +895,10 @@ class UdpCommon else { opt_name = IP_ADD_MEMBERSHIP; - mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); mreq_arg_ptr = &mreq; + mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; } #ifdef _WIN32 @@ -911,7 +914,6 @@ class UdpCommon // is called with multicast address. Write the address // that designates the network device here. // Also, sets port sharing when working with multicast - sadr = maddr; int reuse = 1; int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); if (shareAddrRes == status_error) @@ -998,8 +1000,16 @@ class UdpSource: public Source, public UdpCommon UdpSource(string host, int port, const map& attr) { Setup(host, port, attr); - int stat = ::bind(m_sock, sadr.get(), sadr.size()); - if ( stat == -1 ) + + // In most circumstances, bind to the target addr (for unicast this is usually local) + // In linux multicast, bind to the remote address (224.0.0.0/4) + // In Windows multicast, bind to the local address + int stat = +#if defined(_WIN32) || defined(__CYGWIN__) + is_multicast ? ::bind(m_sock, interface_addr.get(), interface_addr.size()) : +#endif + ::bind(m_sock, target_addr.get(), target_addr.size()); + if (stat == -1) Error(SysError(), "Binding address for UDP"); eof = false; } @@ -1009,7 +1019,7 @@ class UdpSource: public Source, public UdpCommon if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); - sockaddr_any sa(sadr.family()); + sockaddr_any sa(target_addr.family()); socklen_t si = sa.size(); int stat = recvfrom(m_sock, pkt.payload.data(), (int) chunk, 0, sa.get(), &si); if (stat < 1) @@ -1045,15 +1055,14 @@ class UdpTarget: public Target, public UdpCommon cerr << "\nWARN Host for UDP target is not provided. Will send to localhost:" << port << ".\n"; Setup(host, port, attr); - if (adapter != "") + if (is_multicast && interface_addr.isany() == false) { - sockaddr_any maddr = CreateAddr(adapter, 0); - if (maddr.family() != AF_INET) + if (interface_addr.family() != AF_INET) { Error(0, "UDP/target: IPv6 multicast not supported in the application"); } - in_addr addr = maddr.sin.sin_addr; + in_addr addr = interface_addr.sin.sin_addr; int res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&addr), sizeof(addr)); if (res == -1) @@ -1066,7 +1075,7 @@ class UdpTarget: public Target, public UdpCommon int Write(const char* data, size_t len, int64_t src_time SRT_ATR_UNUSED, ostream & ignored SRT_ATR_UNUSED = cout) override { - int stat = sendto(m_sock, data, (int) len, 0, sadr.get(), sadr.size()); + int stat = sendto(m_sock, data, (int)len, 0, target_addr.get(), target_addr.size()); if ( stat == -1 ) { if ((false)) From dfeb2b5e77b955f92f78942f01581ab56f2a3cfa Mon Sep 17 00:00:00 2001 From: BX Date: Wed, 26 Feb 2025 18:57:57 +1000 Subject: [PATCH 2/3] set multicast size after setting members --- apps/transmitmedia.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index f9ceb0a93..7cc0c498d 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -895,10 +895,10 @@ class UdpCommon else { opt_name = IP_ADD_MEMBERSHIP; - mreq_arg_size = sizeof(mreq); - mreq_arg_ptr = &mreq; mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; + mreq_arg_size = sizeof(mreq); + mreq_arg_ptr = &mreq; } #ifdef _WIN32 From 10f43f802a46efefc4c74198ad9ec2cab50d2a3e Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Thu, 27 Feb 2025 11:53:27 +0100 Subject: [PATCH 3/3] [apps] Fixed correct bind address selection for windows multicast --- apps/transmitmedia.cpp | 74 ++++++++++++++++----------------- scripts/build-windows.ps1 | 2 + testing/testmedia.cpp | 86 +++++++++++++++++++++------------------ testing/testmedia.hpp | 4 +- 4 files changed, 87 insertions(+), 79 deletions(-) diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 7cc0c498d..862875fa2 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -808,7 +808,7 @@ class UdpCommon string adapter; sockaddr_any interface_addr; sockaddr_any target_addr; - bool is_multicast; + bool is_multicast = false; map m_options; void Setup(string host, int port, map attr) @@ -823,19 +823,16 @@ class UdpCommon // set non-blocking mode #if defined(_WIN32) unsigned long ulyes = 1; - if (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR) + const bool ioctl_error = (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR); #else - if (ioctl(m_sock, FIONBIO, (const char *)&yes) < 0) + const bool ioctl_error = (ioctl(m_sock, FIONBIO, (const char *)&yes) == -1); #endif + if (ioctl_error) { Error(SysError(), "UdpCommon::Setup: ioctl FIONBIO"); } - interface_addr = sockaddr_any(AF_INET); - interface_addr.sin.sin_family = AF_INET; - interface_addr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - interface_addr.sin.sin_port = htons(port); - + interface_addr = CreateAddr("", port, AF_INET); target_addr = CreateAddr(host, port); is_multicast = false; @@ -863,11 +860,11 @@ class UdpCommon { ip_mreq mreq; int opt_name; - void* mreq_arg_ptr; + const char* mreq_arg_ptr; socklen_t mreq_arg_size; adapter = attr.count("adapter") ? attr.at("adapter") : string(); - if ( adapter == "" ) + if (adapter == "") { Verb() << "Multicast: home address: INADDR_ANY:" << port; } @@ -887,7 +884,7 @@ class UdpCommon mreq_ssm.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); - mreq_arg_ptr = &mreq_ssm; + mreq_arg_ptr = (const char*)&mreq_ssm; #else throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS"); #endif @@ -898,35 +895,17 @@ class UdpCommon mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); - mreq_arg_ptr = &mreq; + mreq_arg_ptr = (const char*)&mreq; } #ifdef _WIN32 - const char* mreq_arg = (const char*)mreq_arg_ptr; const auto status_error = SOCKET_ERROR; #else - const void* mreq_arg = mreq_arg_ptr; const auto status_error = -1; #endif + auto res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg_ptr, mreq_arg_size); -#if defined(_WIN32) || defined(__CYGWIN__) - // On Windows it somehow doesn't work when bind() - // is called with multicast address. Write the address - // that designates the network device here. - // Also, sets port sharing when working with multicast - int reuse = 1; - int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); - if (shareAddrRes == status_error) - { - throw runtime_error("marking socket for shared use failed"); - } - Verb() << "Multicast(Windows): will bind to home address"; -#else - Verb() << "Multicast(POSIX): will bind to IGMP address: " << host; -#endif - int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size); - - if ( res == status_error ) + if (res == status_error) { Error(errno, "adding to multicast membership failed"); } @@ -1001,14 +980,33 @@ class UdpSource: public Source, public UdpCommon { Setup(host, port, attr); - // In most circumstances, bind to the target addr (for unicast this is usually local) - // In linux multicast, bind to the remote address (224.0.0.0/4) - // In Windows multicast, bind to the local address - int stat = + // On Windows it somehow doesn't work when bind() + // is called with multicast address. Write the address + // that designates the network device here, always. + + // Hance we have two fields holding the address: + // * target_addr: address from which reading will be done + // * unicast: local address of the interface + // * multicast: the IGMP address + // * interface_addr: address of the local interface + // (same as target_addr for unicast) + + // In case of multicast, binding should be done: + // On most POSIX systems, to the multicast address (target_addr in this case) + // On Windows, to a local address (hence use interface_addr, as in this + // case it differs to target_addr). + #if defined(_WIN32) || defined(__CYGWIN__) - is_multicast ? ::bind(m_sock, interface_addr.get(), interface_addr.size()) : + sockaddr_any baddr = is_multicast ? interface_addr : target_addr; + static const char* const sysname = "Windows"; +#else + static const char* const sysname = "POSIX"; + sockaddr_any baddr = target_addr; #endif - ::bind(m_sock, target_addr.get(), target_addr.size()); + Verb("UDP:", is_multicast ? "Multicast" : "Unicast", "(", sysname, + "): will bind to: ", baddr.str()); + int stat = ::bind(m_sock, baddr.get(), baddr.size()); + if (stat == -1) Error(SysError(), "Binding address for UDP"); eof = false; diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index d2f07972d..c4c7b5740 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -17,6 +17,7 @@ param ( [Parameter()][String]$STATIC_LINK_SSL = "OFF", [Parameter()][String]$CXX11 = "ON", [Parameter()][String]$BUILD_APPS = "ON", + [Parameter()][String]$BUILD_TESTAPPS = "OFF", [Parameter()][String]$UNIT_TESTS = "OFF", [Parameter()][String]$BUILD_DIR = "_build", [Parameter()][String]$VCPKG_OPENSSL = "OFF", @@ -135,6 +136,7 @@ $cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " + "-DENABLE_APPS=$BUILD_APPS " + "-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " + "-DENABLE_BONDING=$BONDING " + + "-DENABLE_TESTING=$BUILD_TESTAPPS " + "-DENABLE_UNITTESTS=$UNIT_TESTS" # if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index a7eb0541c..2c36a4d12 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -2753,20 +2753,22 @@ void UdpCommon::Setup(string host, int port, map attr) int yes = 1; ::setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof yes); - sadr = CreateAddr(host, port); + interface_addr = CreateAddr("", port, AF_INET); + target_addr = CreateAddr(host, port); - bool is_multicast = false; - if (sadr.family() == AF_INET) + is_multicast = false; + + if (target_addr.family() == AF_INET) { if (attr.count("multicast")) { - if (!IsMulticast(sadr.sin.sin_addr)) + if (!IsMulticast(target_addr.sin.sin_addr)) { throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address"); } is_multicast = true; } - else if (IsMulticast(sadr.sin.sin_addr)) + else if (IsMulticast(target_addr.sin.sin_addr)) { is_multicast = true; } @@ -2774,23 +2776,19 @@ void UdpCommon::Setup(string host, int port, map attr) if (is_multicast) { ip_mreq mreq; - sockaddr_any maddr (AF_INET); int opt_name; - void* mreq_arg_ptr; + const char* mreq_arg_ptr; socklen_t mreq_arg_size; adapter = attr.count("adapter") ? attr.at("adapter") : string(); if (adapter == "") { Verb() << "Multicast: home address: INADDR_ANY:" << port; - maddr.sin.sin_family = AF_INET; - maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - maddr.sin.sin_port = htons(port); // necessary for temporary use } else { Verb() << "Multicast: home address: " << adapter << ":" << port; - maddr = CreateAddr(adapter, port); + interface_addr = CreateAddr(adapter, port); } if (attr.count("source")) @@ -2799,11 +2797,11 @@ void UdpCommon::Setup(string host, int port, map attr) ip_mreq_source mreq_ssm; /* this is an ssm. we need to use the right struct and opt */ opt_name = IP_ADD_SOURCE_MEMBERSHIP; - mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq_ssm.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq_ssm.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); - mreq_arg_ptr = &mreq_ssm; + mreq_arg_ptr = (const char*)&mreq_ssm; #else throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS"); #endif @@ -2811,37 +2809,18 @@ void UdpCommon::Setup(string host, int port, map attr) else { opt_name = IP_ADD_MEMBERSHIP; - mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); - mreq_arg_ptr = &mreq; + mreq_arg_ptr = (const char*)&mreq; } #ifdef _WIN32 - const char* mreq_arg = (const char*)mreq_arg_ptr; const auto status_error = SOCKET_ERROR; #else - const void* mreq_arg = mreq_arg_ptr; const auto status_error = -1; #endif - -#if defined(_WIN32) || defined(__CYGWIN__) - // On Windows it somehow doesn't work when bind() - // is called with multicast address. Write the address - // that designates the network device here. - // Also, sets port sharing when working with multicast - sadr = maddr; - int reuse = 1; - int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); - if (shareAddrRes == status_error) - { - throw runtime_error("marking socket for shared use failed"); - } - Verb() << "Multicast(Windows): will bind to home address"; -#else - Verb() << "Multicast(POSIX): will bind to IGMP address: " << host; -#endif - int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size); + auto res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg_ptr, mreq_arg_size); if (res == status_error) { @@ -2913,7 +2892,34 @@ UdpCommon::~UdpCommon() UdpSource::UdpSource(string host, int port, const map& attr) { Setup(host, port, attr); - int stat = ::bind(m_sock, sadr.get(), sadr.size()); + + // On Windows it somehow doesn't work when bind() + // is called with multicast address. Write the address + // that designates the network device here, always. + + // Hance we have two fields holding the address: + // * target_addr: address from which reading will be done + // * unicast: local address of the interface + // * multicast: the IGMP address + // * interface_addr: address of the local interface + // (same as target_addr for unicast) + + // In case of multicast, binding should be done: + // On most POSIX systems, to the multicast address (target_addr in this case) + // On Windows, to a local address (hence use interface_addr, as in this + // case it differs to target_addr). + +#if defined(_WIN32) || defined(__CYGWIN__) + sockaddr_any baddr = is_multicast ? interface_addr : target_addr; + static const char* const sysname = "Windows"; +#else + static const char* const sysname = "POSIX"; + sockaddr_any baddr = target_addr; +#endif + Verb("UDP:", is_multicast ? "Multicast" : "Unicast", "(", sysname, + "): will bind to: ", baddr.str()); + int stat = ::bind(m_sock, baddr.get(), baddr.size()); + if (stat == -1) Error(SysError(), "Binding address for UDP"); eof = false; @@ -2927,7 +2933,7 @@ UdpSource::UdpSource(string host, int port, const map& attr) MediaPacket UdpSource::Read(size_t chunk) { bytevector data(chunk); - sockaddr_any sa(sadr.family()); + sockaddr_any sa(target_addr.family()); int64_t srctime = 0; AGAIN: int stat = recvfrom(m_sock, data.data(), (int) chunk, 0, sa.get(), &sa.syslen()); @@ -2975,7 +2981,7 @@ UdpTarget::UdpTarget(string host, int port, const map& attr) void UdpTarget::Write(const MediaPacket& data) { - int stat = sendto(m_sock, data.payload.data(), int(data.payload.size()), 0, (sockaddr*)&sadr, int(sizeof sadr)); + int stat = sendto(m_sock, data.payload.data(), int(data.payload.size()), 0, target_addr.get(), (int)target_addr.size()); if (stat == -1) Error(SysError(), "UDP Write/sendto"); } diff --git a/testing/testmedia.hpp b/testing/testmedia.hpp index 470e825ef..36a649130 100644 --- a/testing/testmedia.hpp +++ b/testing/testmedia.hpp @@ -304,8 +304,10 @@ class UdpCommon { protected: int m_sock = -1; - srt::sockaddr_any sadr; std::string adapter; + srt::sockaddr_any interface_addr; + srt::sockaddr_any target_addr; + bool is_multicast = false; std::map m_options; void Setup(std::string host, int port, std::map attr); void Error(int err, std::string src);