From 7202cf2650a87fe082e2351ffdff223545e106ca Mon Sep 17 00:00:00 2001 From: Sepalani Date: Fri, 29 Jul 2022 18:36:01 +0400 Subject: [PATCH] BBA/BuiltIn: Add SSDP multicast support --- Source/Core/Common/Network.h | 1 + Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp | 92 ++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index 64958a5175..87f1f3a444 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -41,6 +41,7 @@ using IPAddress = std::array; constexpr IPAddress IP_ADDR_ANY = {0, 0, 0, 0}; constexpr IPAddress IP_ADDR_BROADCAST = {255, 255, 255, 255}; constexpr IPAddress IP_ADDR_SSDP = {239, 255, 255, 250}; +constexpr u16 SSDP_PORT = 1900; constexpr u16 IPV4_ETHERTYPE = 0x800; constexpr u16 ARP_ETHERTYPE = 0x806; diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp index a2483b00e9..3af54b4f23 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -1,12 +1,19 @@ // Copyright 2022 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include "Core/HW/EXI/BBA/BuiltIn.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#endif #include "Common/BitUtils.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Common/ScopeGuard.h" #include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_DeviceEthernet.h" @@ -462,25 +469,20 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(const Common::UDPPacket& ERROR_LOG_FMT(SP1, "Couldn't open UDP socket"); return; } - if (ntohs(udp_header.destination_port) == 1900 && ntohs(udp_header.length) > 150) + if (ntohs(udp_header.destination_port) == Common::SSDP_PORT && ntohs(udp_header.length) > 150) { // Quick hack to unlock the connection, throw it back at him Common::UDPPacket reply = packet; reply.eth_header.destination = hwdata.source; reply.eth_header.source = hwdata.destination; reply.ip_header.destination_addr = ip_header.source_addr; - if (ip_header.destination_addr == Common::IP_ADDR_SSDP) - reply.ip_header.source_addr = Common::IP_ADDR_BROADCAST; - else - reply.ip_header.source_addr = Common::BitCast(destination_addr); + reply.ip_header.source_addr = Common::BitCast(destination_addr); WriteToQueue(reply.Build()); } } } if (ntohs(udp_header.destination_port) == 53) target = sf::IpAddress(m_dns_ip.c_str()); // dns server ip - else if (ip_header.destination_addr == Common::IP_ADDR_SSDP) - target = sf::IpAddress(0xFFFFFFFF); // force real broadcast else target = sf::IpAddress(ntohl(Common::BitCast(ip_header.destination_addr))); ref->udp_socket.send(data.data(), data.size(), target, ntohs(udp_header.destination_port)); @@ -729,5 +731,75 @@ BbaUdpSocket::BbaUdpSocket() = default; sf::Socket::Status BbaUdpSocket::Bind(u16 port, u32 net_ip) { - return this->bind(port, sf::IpAddress(ntohl(net_ip))); + if (port != Common::SSDP_PORT) + return this->bind(port, sf::IpAddress(ntohl(net_ip))); + + // Handle SSDP multicast + create(); + const int on = 1; + if (setsockopt(getHandle(), SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&on), + sizeof(on)) != 0) + { + ERROR_LOG_FMT(SP1, "setsockopt failed to reuse SSDP address: {}", Common::StrNetworkError()); + } +#ifdef SO_REUSEPORT + if (setsockopt(getHandle(), SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&on), + sizeof(on)) != 0) + { + ERROR_LOG_FMT(SP1, "setsockopt failed to reuse SSDP port: {}", Common::StrNetworkError()); + } +#endif + if (const char loop = 1; + setsockopt(getHandle(), IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) != 0) + { + ERROR_LOG_FMT(SP1, "setsockopt failed to set SSDP loopback: {}", Common::StrNetworkError()); + } + + // sf::UdpSocket::bind will close the socket and get rid of its options + sockaddr_in addr; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_family = AF_INET; + addr.sin_port = htons(Common::SSDP_PORT); + Common::ScopeGuard error_guard([this] { close(); }); + if (::bind(getHandle(), reinterpret_cast(&addr), sizeof(addr)) != 0) + { + WARN_LOG_FMT(SP1, "bind with SSDP port and INADDR_ANY failed: {}", Common::StrNetworkError()); + addr.sin_addr.s_addr = net_ip; + if (::bind(getHandle(), reinterpret_cast(&addr), sizeof(addr)) != 0) + { + ERROR_LOG_FMT(SP1, "bind with SSDP port failed: {}", Common::StrNetworkError()); + return sf::Socket::Status::Error; + } + } + else + { + addr.sin_addr.s_addr = net_ip; // Set this here for IP_MULTICAST_IF + } + INFO_LOG_FMT(SP1, "SSDP bind successful"); + + // Bind to the right interface + if (setsockopt(getHandle(), IPPROTO_IP, IP_MULTICAST_IF, + reinterpret_cast(&addr.sin_addr), sizeof(addr.sin_addr)) != 0) + { + ERROR_LOG_FMT(SP1, "setsockopt failed to bind to the network interface: {}", + Common::StrNetworkError()); + return sf::Socket::Status::Error; + } + + // Subscribe to the SSDP multicast group + // NB: Other groups aren't supported because of HLE + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = Common::BitCast(Common::IP_ADDR_SSDP); + mreq.imr_interface.s_addr = net_ip; + if (setsockopt(getHandle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast(&mreq), + sizeof(mreq)) != 0) + { + ERROR_LOG_FMT(SP1, "setsockopt failed to subscribe to SSDP multicast group: {}", + Common::StrNetworkError()); + return sf::Socket::Status::Error; + } + + error_guard.Dismiss(); + INFO_LOG_FMT(SP1, "SSDP multicast membership successful"); + return sf::Socket::Status::Done; }