diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-11-02 23:40:30 +0100 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-11-02 23:42:50 +0100 |
| commit | 3e10d48548a285172ea7cb1f94c58783e1d97754 (patch) | |
| tree | 203de9fd00993656728c37b3e148eac66f954065 | |
| parent | 693529b27ec898b867ad5806a0174f69597decc9 (diff) | |
network: fix room host LAN Play and room socket review issues
The room host and room moderators are in the Moderator state, not
Joined, so the airplane mode override for joined rooms did not apply to
them and LAN Play still returned them to the menu. Accept both states.
Share the room address override between the NIFM profile and IP config
calls so they cannot drift apart.
recv() on a room TCP socket now waited for the receive timeout, because
Recv goes through RecvFrom. Room TCP sockets have no peer, so keep the
previous immediate end-of-stream result when nothing is queued.
Close() now drops queued packets and wakes a blocked recv, which then
fails with EBADF. The socket state that the room thread reads when it
queues a packet is now guarded by the packet mutex.
Assisted-by: Claude Opus 5.5 (investigation and implementation)
| -rw-r--r-- | src/core/hle/service/nifm/nifm.cpp | 41 | ||||
| -rw-r--r-- | src/core/internal_network/socket_proxy.cpp | 62 |
2 files changed, 65 insertions, 38 deletions
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 153b0f2193..e0a23f7a63 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -73,17 +73,17 @@ static u128 MakeUuidFromName(std::string_view name) { // A joined room is the emulated network, even in airplane mode. Its sockets only reach room // members, and LAN games (e.g. Mario Kart World LAN Play) need their connection request accepted. static bool IsNetworkAvailable() { - if (const auto room_member = Network::GetRoomMember().lock(); - room_member && room_member->GetState() == Network::RoomMember::State::Joined) { - return true; + if (const auto room_member = Network::GetRoomMember().lock()) { + // The room host and moderators are in the Moderator state instead of Joined + const auto state = room_member->GetState(); + if (state == Network::RoomMember::State::Joined || + state == Network::RoomMember::State::Moderator) { + return true; + } } return Network::GetHostIPv4Address().has_value() && !Settings::values.airplane_mode.GetValue(); } -// Room fake IPs are spread over 192.168.0.0/16, and some room servers give members different /24s. -// A host /24 mask would then put room members on different subnets, so LAN games ignore each other. -constexpr Network::IPv4Address RoomSubnetMask{255, 255, 0, 0}; - // This is nn::nifm::RequestState enum class RequestState : u32 { NotSubmitted = 1, @@ -124,6 +124,17 @@ struct IpAddressSetting { }; static_assert(sizeof(IpAddressSetting) == 0xD, "IpAddressSetting has incorrect size."); +// Room fake IPs are spread over 192.168.0.0/16, and some room servers give members different /24s. +// A host /24 mask would then put room members on different subnets, so LAN games ignore each other. +constexpr Network::IPv4Address RoomSubnetMask{255, 255, 0, 0}; + +static void ApplyRoomAddress(const Network::RoomMember& room_member, IpAddressSetting& setting) { + if (room_member.IsConnected()) { + setting.ip_address = room_member.GetFakeIpAddress(); + setting.subnet_mask = RoomSubnetMask; + } +} + // This is nn::nifm::DnsSetting struct DnsSetting { bool is_automatic{}; @@ -585,11 +596,7 @@ void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { std::strncpy(profile.network_name.data(), st.ssid, sizeof(profile.network_name) - 1); if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - profile.ip_setting_data.ip_address_setting.ip_address = - room_member->GetFakeIpAddress(); - profile.ip_setting_data.ip_address_setting.subnet_mask = RoomSubnetMask; - } + ApplyRoomAddress(*room_member, profile.ip_setting_data.ip_address_setting); profile.ip_setting_data.dns_setting.secondary_dns = {8, 8, 8, 8}; } @@ -726,10 +733,7 @@ void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) { }(); if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - profile.ip_setting_data.ip_address_setting.ip_address = room_member->GetFakeIpAddress(); - profile.ip_setting_data.ip_address_setting.subnet_mask = RoomSubnetMask; - } + ApplyRoomAddress(*room_member, profile.ip_setting_data.ip_address_setting); } ctx.WriteBuffer(profile); @@ -917,10 +921,7 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) { } if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - info.ip.ip_address = room_member->GetFakeIpAddress(); - info.ip.subnet_mask = RoomSubnetMask; - } + ApplyRoomAddress(*room_member, info.ip); info.dns.is_automatic = true; info.dns.primary_dns = {1, 1, 1, 1}; info.dns.secondary_dns = {8, 8, 8, 8}; diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp index f42ed61e21..83eb2ce9d0 100644 --- a/src/core/internal_network/socket_proxy.cpp +++ b/src/core/internal_network/socket_proxy.cpp @@ -31,21 +31,21 @@ ProxySocket::~ProxySocket() { } void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) { - if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno || - closed) { - return; - } - - if (!broadcast && packet.broadcast) { - LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode"); - return; - } - - auto decompressed = packet; - decompressed.data = Common::Compression::DecompressDataZSTD(packet.data); - { + // Runs on the room thread, while the game thread may bind, close or reconfigure the socket std::lock_guard guard(packets_mutex); + if (protocol != packet.protocol || local_endpoint.portno != packet.remote_endpoint.portno || + closed) { + return; + } + + if (!broadcast && packet.broadcast) { + LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode"); + return; + } + + auto decompressed = packet; + decompressed.data = Common::Compression::DecompressDataZSTD(packet.data); received_packets.push(std::move(decompressed)); } packets_cv.notify_one(); @@ -58,7 +58,10 @@ Errno ProxySocket::SetSockOpt(SOCKET fd_, int option, T value) { } Errno ProxySocket::Initialize(Domain domain, Type type, Protocol socket_protocol) { - protocol = socket_protocol; + { + std::lock_guard guard(packets_mutex); + protocol = socket_protocol; + } SetSockOpt(fd, SO_TYPE, type); return Errno::SUCCESS; @@ -85,6 +88,7 @@ std::pair<SockAddrIn, Errno> ProxySocket::GetSockName() { } Errno ProxySocket::Bind(SockAddrIn addr) { + std::lock_guard guard(packets_mutex); if (is_bound) { LOG_WARNING(Network, "Rebinding Socket is unimplemented!"); return Errno::SUCCESS; @@ -106,6 +110,14 @@ Errno ProxySocket::Shutdown(ShutdownHow how) { } std::pair<s32, Errno> ProxySocket::Recv(int flags, std::span<u8> message) { + if (protocol == Protocol::TCP) { + // Room TCP sockets have no peer (Connect and Send are stubs). Report an empty queue as the + // end of the stream, as before, instead of blocking the bsd service until the timeout. + std::lock_guard guard(packets_mutex); + if (received_packets.empty()) { + return {0, Errno::SUCCESS}; + } + } return RecvFrom(flags, message, nullptr); } @@ -114,6 +126,9 @@ std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, So ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)())); std::unique_lock lock(packets_mutex); + if (closed) { + return {-1, Errno::BADF}; + } if (received_packets.empty()) { if (!blocking) { return {-1, Errno::AGAIN}; @@ -124,9 +139,12 @@ std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, So // the timeout to 5s instead. An expired wait is reported like an SO_RCVTIMEO timeout. const u32 timeout = receive_timeout == 0 ? 5000 : receive_timeout; if (!packets_cv.wait_for(lock, std::chrono::milliseconds{timeout}, - [this] { return !received_packets.empty(); })) { + [this] { return closed || !received_packets.empty(); })) { return {-1, Errno::AGAIN}; } + if (closed) { + return {-1, Errno::BADF}; + } } return ReceivePacket(flags, message, addr, message.size()); @@ -217,8 +235,13 @@ std::pair<s32, Errno> ProxySocket::SendTo(u32 flags, std::span<const u8> message } Errno ProxySocket::Close() { - fd = INVALID_SOCKET; - closed = true; + { + std::lock_guard guard(packets_mutex); + fd = INVALID_SOCKET; + closed = true; + received_packets = {}; + } + packets_cv.notify_all(); return Errno::SUCCESS; } @@ -239,7 +262,10 @@ Errno ProxySocket::SetReuseAddr(bool enable) { } Errno ProxySocket::SetBroadcast(bool enable) { - broadcast = enable; + { + std::lock_guard guard(packets_mutex); + broadcast = enable; + } return SetSockOpt<u32>(fd, SO_BROADCAST, enable ? 1 : 0); } |
