From fd3e2fe18a101e2bbfd5935263183e25ab26497b Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 3 Nov 2026 00:17:50 +0100 Subject: network: fix room socket races and share the room network check bsd:s, bsd:u and bsd:a share one file descriptor table, but each service bound its own proxy packet callback, so every room packet was queued three times on each socket. Bind the callback once. The room thread walked the descriptor table without a lock while bsd created and closed sockets, so it could reach a destroyed or not yet created socket. Guard the table with a mutex, and publish a socket only after it is created and initialized. Check airplane mode before an fd is taken, so a refused socket no longer leaks its slot. ProxySocket now initializes protocol, decompresses packets outside the packet lock without copying the compressed data first, and keeps a read offset for partial TCP reads instead of erasing from the front. Add Network::GetRoomIPv4Address(), set only in the Joined and Moderator states. NIFM uses it for network availability and for every reported address, including EnumerateNetworkInterfaces and GetCurrentIpAddress, so no call reports the room address while another rejects the network. Assisted-by: Claude Opus 5.5 (investigation and implementation) --- src/core/hle/service/sockets/bsd.cpp | 71 +++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 26 deletions(-) (limited to 'src/core/hle/service/sockets/bsd.cpp') diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 02c6bb7762..1b6b132769 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -535,33 +535,36 @@ std::pair BSD_USA::SocketImpl(Domain domain, Type type, Protocol pro UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type"); type = static_cast(static_cast(type) & ~0x20000000); - const s32 fd = FindFreeFileDescriptorHandle(); - if (fd < 0) { - LOG_ERROR(Service, "No more file descriptors available"); - return {-1, Errno::MFILE}; + // Checked before taking an fd. A joined room does not lift it: room TCP is only stubbed. + if (Settings::values.airplane_mode.GetValue() && IsConnectionBased(type)) { + LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket"); + return {-1, Errno::NOTCONN}; } - file_descriptors[fd] = FileDescriptor{}; - FileDescriptor& descriptor = *file_descriptors[fd]; - // ENONMEM might be thrown here - - LOG_INFO(Service, "New socket fd={}", fd); - + std::shared_ptr socket; auto room_member = Network::GetRoomMember().lock(); if (room_member && room_member->IsConnected()) { - descriptor.socket = std::make_shared(); + socket = std::make_shared(); } else { - descriptor.socket = std::make_shared(); + socket = std::make_shared(); } + socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); - descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); - descriptor.is_connection_based = IsConnectionBased(type); - - if (Settings::values.airplane_mode.GetValue() && descriptor.is_connection_based) { - LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket"); - return {-1, Errno::NOTCONN}; + // Publish the socket only once it is set up, since the room thread delivers packets to it + std::scoped_lock lock{file_descriptors_mutex}; + const s32 fd = FindFreeFileDescriptorHandle(); + if (fd < 0) { + LOG_ERROR(Service, "No more file descriptors available"); + return {-1, Errno::MFILE}; } + file_descriptors[fd] = FileDescriptor{ + .socket = std::move(socket), + .is_connection_based = IsConnectionBased(type), + }; + + LOG_INFO(Service, "New socket fd={}", fd); + return {fd, Errno::SUCCESS}; } @@ -649,10 +652,13 @@ std::pair BSD_USA::AcceptImpl(s32 fd, std::vector& write_buffer) return {-1, Translate(bsd_errno)}; } - file_descriptors[new_fd] = FileDescriptor{}; - FileDescriptor& new_descriptor = *file_descriptors[new_fd]; - new_descriptor.socket = std::move(result.socket); - new_descriptor.is_connection_based = descriptor.is_connection_based; + { + std::scoped_lock lock{file_descriptors_mutex}; + file_descriptors[new_fd] = FileDescriptor{ + .socket = std::move(result.socket), + .is_connection_based = descriptor.is_connection_based, + }; + } const SockAddrIn guest_addr_in = Translate(result.sockaddr_in); PutValue(write_buffer, guest_addr_in); @@ -1007,6 +1013,7 @@ Errno BSD_USA::CloseImpl(s32 fd) { LOG_INFO(Service, "Close socket fd={}", fd); + std::scoped_lock lock{file_descriptors_mutex}; file_descriptors[fd].reset(); return bsd_errno; } @@ -1016,6 +1023,7 @@ std::variant BSD_USA::DuplicateSocketImpl(s32 fd) { return Errno::BADF; } + std::scoped_lock lock{file_descriptors_mutex}; const s32 new_fd = FindFreeFileDescriptorHandle(); if (new_fd < 0) { LOG_ERROR(Service, "No more file descriptors available"); @@ -1071,12 +1079,14 @@ void BSD_USA::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const } void BSD_USA::OnProxyPacketReceived(const Network::ProxyPacket& packet) { + // Runs on the room thread. The lock keeps a socket alive while a bsd thread closes it. + std::scoped_lock lock{file_descriptors_mutex}; for (auto& optional_descriptor : file_descriptors) { - if (!optional_descriptor.has_value()) { + if (!optional_descriptor.has_value() || !optional_descriptor->socket) { continue; } FileDescriptor& descriptor = *optional_descriptor; - descriptor.socket.get()->HandleProxyPacket(packet); + descriptor.socket->HandleProxyPacket(packet); } } @@ -1140,18 +1150,27 @@ BSD_USA::BSD_USA(Core::System& system_, const char* name, bool is_user_) RegisterHandlers(functions); + std::scoped_lock lock{proxy_binding_mutex}; + if (proxy_binding_count++ != 0) { + return; + } if (auto room_member = Network::GetRoomMember().lock()) { - proxy_packet_received = room_member->BindOnProxyPacketReceived( - [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); + proxy_packet_received = + room_member->BindOnProxyPacketReceived(&BSD_USA::OnProxyPacketReceived); } else { LOG_ERROR(Service, "Network isn't initialized"); } } BSD_USA::~BSD_USA() { + std::scoped_lock lock{proxy_binding_mutex}; + if (--proxy_binding_count != 0) { + return; + } if (auto room_member = Network::GetRoomMember().lock()) { room_member->Unbind(proxy_packet_received); } + proxy_packet_received.reset(); } std::unique_lock BSD_USA::LockService() noexcept { -- cgit v1.2.3