Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[apps] Fixed correct bind address selection for windows multicast #3126

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 57 additions & 50 deletions apps/transmitmedia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 = false;
map<string, string> m_options;

void Setup(string host, int port, map<string,string> attr)
Expand All @@ -821,57 +823,55 @@ 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");
}

sadr = CreateAddr(host, port);
interface_addr = CreateAddr("", port, AF_INET);
target_addr = CreateAddr(host, port);

bool is_multicast = false;
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;
}

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 == "" )
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"))
Expand All @@ -880,51 +880,32 @@ 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;
mreq_arg_ptr = (const char*)&mreq_ssm;
#else
throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS");
#endif
}
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
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
sadr = maddr;
int reuse = 1;
int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&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");
}
Expand Down Expand Up @@ -998,8 +979,35 @@ class UdpSource: public Source, public UdpCommon
UdpSource(string host, int port, const map<string,string>& attr)
{
Setup(host, port, attr);
int stat = ::bind(m_sock, sadr.get(), sadr.size());
if ( stat == -1 )

// 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;
}
Expand All @@ -1009,7 +1017,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)
Expand Down Expand Up @@ -1045,15 +1053,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<const char*>(&addr), sizeof(addr));
if (res == -1)
Expand All @@ -1066,7 +1073,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))
Expand Down
2 changes: 2 additions & 0 deletions scripts/build-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
86 changes: 46 additions & 40 deletions testing/testmedia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2753,44 +2753,42 @@ void UdpCommon::Setup(string host, int port, map<string,string> 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;
}

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"))
Expand All @@ -2799,49 +2797,30 @@ void UdpCommon::Setup(string host, int port, map<string,string> 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
}
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<const char*>(&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)
{
Expand Down Expand Up @@ -2913,7 +2892,34 @@ UdpCommon::~UdpCommon()
UdpSource::UdpSource(string host, int port, const map<string,string>& 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;
Expand All @@ -2927,7 +2933,7 @@ UdpSource::UdpSource(string host, int port, const map<string,string>& 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());
Expand Down Expand Up @@ -2975,7 +2981,7 @@ UdpTarget::UdpTarget(string host, int port, const map<string,string>& 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");
}
Expand Down
Loading
Loading