From e783a7f4090efa1251a0215756daaf6c4fb48930 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 24 Aug 2022 20:34:49 +0400 Subject: [PATCH 1/3] Common/Network: Add StrNetworkError helper function --- Source/Core/Common/Network.cpp | 10 ++++++++++ Source/Core/Common/Network.h | 1 + 2 files changed, 11 insertions(+) diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index a112fb9a99..0346fe427d 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -566,4 +566,14 @@ const char* DecodeNetworkError(s32 error_code) return strerror_r(error_code, buffer, sizeof(buffer)); #endif } + +const char* StrNetworkError() +{ +#ifdef _WIN32 + const s32 error_code = WSAGetLastError(); +#else + const s32 error_code = errno; +#endif + return DecodeNetworkError(error_code); +} } // namespace Common diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index c615d31451..64958a5175 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -266,4 +266,5 @@ u16 ComputeTCPNetworkChecksum(const IPAddress& from, const IPAddress& to, const NetworkErrorState SaveNetworkErrorState(); void RestoreNetworkErrorState(const NetworkErrorState& state); const char* DecodeNetworkError(s32 error_code); +const char* StrNetworkError(); } // namespace Common From f992f8f38c6c157533031c51f1ff224580b7c54a Mon Sep 17 00:00:00 2001 From: Sepalani Date: Mon, 25 Jul 2022 20:17:51 +0400 Subject: [PATCH 2/3] BBA/BuiltIn: Make network interface consistent --- Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp | 29 +++++++++++++++++++++---- Source/Core/Core/HW/EXI/BBA/BuiltIn.h | 25 +++++++++++++++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp index bdc08f21a4..a2483b00e9 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -68,6 +68,8 @@ bool CEXIETHERNET::BuiltInBBAInterface::Activate() buf.reserve(2048); // Workaround to get the host IP (might not be accurate) + // TODO: Fix the JNI crash and use GetSystemDefaultInterface() + // - https://pastebin.com/BFpmnxby (see https://dolp.in/pr10920) const u32 ip = m_local_ip.empty() ? sf::IpAddress::getLocalAddress().toInteger() : sf::IpAddress(m_local_ip).toInteger(); m_current_ip = htonl(ip); @@ -334,7 +336,7 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& ref->seq_num++; target = sf::IpAddress(ntohl(destination_ip)); - ref->tcp_socket.connect(target, ntohs(tcp_header.destination_port)); + ref->tcp_socket.Connect(target, ntohs(tcp_header.destination_port), m_current_ip); ref->ready = false; ref->ip = Common::BitCast(ip_header.destination_addr); @@ -420,7 +422,7 @@ void CEXIETHERNET::BuiltInBBAInterface::InitUDPPort(u16 port) ref->to.sin_addr.s_addr = m_current_ip; ref->to.sin_port = htons(port); ref->udp_socket.setBlocking(false); - if (ref->udp_socket.bind(port) != sf::Socket::Done) + if (ref->udp_socket.Bind(port, m_current_ip) != sf::Socket::Done) { ERROR_LOG_FMT(SP1, "Couldn't open UDP socket"); PanicAlertFmt("Could't open port {:x}, this game might not work proprely in LAN mode.", port); @@ -450,12 +452,12 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(const Common::UDPPacket& ref->to.sin_addr.s_addr = Common::BitCast(ip_header.source_addr); ref->to.sin_port = udp_header.source_port; ref->udp_socket.setBlocking(false); - if (ref->udp_socket.bind(ntohs(udp_header.source_port)) != sf::Socket::Done) + if (ref->udp_socket.Bind(ntohs(udp_header.source_port), m_current_ip) != sf::Socket::Done) { PanicAlertFmt( "Port {:x} is already in use, this game might not work as intented in LAN Mode.", htons(udp_header.source_port)); - if (ref->udp_socket.bind(sf::Socket::AnyPort) != sf::Socket::Done) + if (ref->udp_socket.Bind(sf::Socket::AnyPort, m_current_ip) != sf::Socket::Done) { ERROR_LOG_FMT(SP1, "Couldn't open UDP socket"); return; @@ -710,3 +712,22 @@ void CEXIETHERNET::BuiltInBBAInterface::RecvStop() m_queue_write = 0; } } // namespace ExpansionInterface + +BbaTcpSocket::BbaTcpSocket() = default; + +sf::Socket::Status BbaTcpSocket::Connect(const sf::IpAddress& dest, u16 port, u32 net_ip) +{ + sockaddr_in addr; + addr.sin_addr.s_addr = net_ip; + addr.sin_family = AF_INET; + addr.sin_port = 0; + ::bind(getHandle(), reinterpret_cast(&addr), sizeof(addr)); + return this->connect(dest, port); +} + +BbaUdpSocket::BbaUdpSocket() = default; + +sf::Socket::Status BbaUdpSocket::Bind(u16 port, u32 net_ip) +{ + return this->bind(port, sf::IpAddress(ntohl(net_ip))); +} diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.h b/Source/Core/Core/HW/EXI/BBA/BuiltIn.h index e1117c8b7e..35e74eae43 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.h +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.h @@ -34,6 +34,27 @@ struct TcpBuffer std::vector data; }; +// Socket helper classes to ensure network interface consistency. +// +// If the socket isn't bound, the system will pick the interface to use automatically. +// This might result in the source IP address changing when talking to the same peer +// and multiple interfaces/IP addresses can reach the socket's peer. +class BbaTcpSocket : public sf::TcpSocket +{ +public: + BbaTcpSocket(); + + sf::Socket::Status Connect(const sf::IpAddress& dest, u16 port, u32 net_ip); +}; + +class BbaUdpSocket : public sf::UdpSocket +{ +public: + BbaUdpSocket(); + + sf::Socket::Status Bind(u16 port, u32 net_ip); +}; + struct StackRef { u32 ip; @@ -52,7 +73,7 @@ struct StackRef sockaddr_in to; Common::MACAddress bba_mac{}; Common::MACAddress my_mac{}; - sf::UdpSocket udp_socket; - sf::TcpSocket tcp_socket; + BbaUdpSocket udp_socket; + BbaTcpSocket tcp_socket; u64 poke_time; }; From 7202cf2650a87fe082e2351ffdff223545e106ca Mon Sep 17 00:00:00 2001 From: Sepalani Date: Fri, 29 Jul 2022 18:36:01 +0400 Subject: [PATCH 3/3] 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; }