From a7e03702c10290dd46bc40e46599729510eda458 Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 3 Nov 2026 00:46:55 +0100 Subject: network: keep bsd sockets alive across concurrent close and fix room gaps bsd runs on three host threads, but its calls kept a raw reference into the shared descriptor table while they used a socket. A close() on another thread could then destroy the socket mid-call. Waking a blocked room recv() on close made this happen every time. Every call now works on a copy of the descriptor taken under the table lock, poll() keeps its sockets alive while it waits, and close() resets the slot only if it still holds the same socket. accept() picked a free fd before blocking and only locked the write, so another thread could publish a socket in the same slot. It now picks and writes the fd in one critical section after Accept() returns. dup() now re-checks the source fd under the lock. A duplicated fd shares its socket, so the room packet loop queued each packet once per fd. Deliver it once per socket. NIFM: when the selected host adapter is missing, the room still works and IsNetworkAvailable reports it, but GetCurrentNetworkProfile returned an empty profile. Report a wired room profile in that case, and put the room address on the first adapter in EnumerateNetworkInterfaces. Assisted-by: Claude Opus 5.5 (investigation and implementation) --- src/core/hle/service/nifm/nifm.cpp | 18 ++- src/core/hle/service/sockets/bsd.cpp | 257 +++++++++++++++++------------------ src/core/hle/service/sockets/bsd.h | 4 +- 3 files changed, 144 insertions(+), 135 deletions(-) diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 8714597f89..ea00e9f35c 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -600,6 +600,19 @@ void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { std::memcpy(profile.wireless_setting_data.ssid.data(), st.ssid, profile.wireless_setting_data.ssid_length); } + } else if (Network::GetRoomIPv4Address()) { + // The selected adapter is missing, but the room still works and IsNetworkAvailable + // reports it, so describe the room as a wired network + profile.ip_setting_data.ip_address_setting.is_automatic = true; + ApplyRoomAddress(profile.ip_setting_data.ip_address_setting); + + profile.ip_setting_data.dns_setting.is_automatic = true; + profile.ip_setting_data.dns_setting.primary_dns = {1, 1, 1, 1}; + profile.ip_setting_data.dns_setting.secondary_dns = {8, 8, 8, 8}; + + profile.uuid = MakeUuidFromName(st.ssid); + profile.profile_type = static_cast(NetworkProfileType::User); + profile.interface_type = static_cast(NetworkInterfaceType::Ethernet); } ctx.WriteBuffer(profile); @@ -630,8 +643,9 @@ void IGeneralService::EnumerateNetworkInterfaces(HLERequestContext& ctx) { *reinterpret_cast(base + 0x1C) = host.subnet_mask; *reinterpret_cast(base + 0x20) = host.gateway; - // The room traffic goes through the selected adapter, so report it like the other calls - if (room_ip && selected && host.name == selected->name) { + // The room traffic goes through the selected adapter, so report it like the other calls. + // Without a selected adapter, the first one stands for the room. + if (room_ip && (selected ? host.name == selected->name : i == 0)) { std::memcpy(base + 0x18, room_ip->data(), room_ip->size()); std::memcpy(base + 0x1C, RoomSubnetMask.data(), RoomSubnetMask.size()); } diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 1b6b132769..477f32626d 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -598,31 +599,39 @@ std::pair BSD_USA::PollImpl(std::vector& write_buffer, std::span return {-1, Errno::INVAL}; } - for (PollFD& pollfd : fds) { - ASSERT(False(pollfd.revents)); - - if (pollfd.fd > static_cast(MAX_FD) || pollfd.fd < 0) { - LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); - pollfd.revents = PollEvents{}; - return {0, Errno::SUCCESS}; - } - - const std::optional& descriptor = file_descriptors[pollfd.fd]; - if (!descriptor) { - LOG_TRACE(Service, "File descriptor handle={} is not allocated", pollfd.fd); - pollfd.revents = PollEvents::Nval; - return {0, Errno::SUCCESS}; + // The copies keep the sockets alive while Network::Poll waits, even if another bsd thread + // closes one of them meanwhile + std::vector> sockets(fds.size()); + { + std::scoped_lock lock{file_descriptors_mutex}; + for (size_t i = 0; i < fds.size(); ++i) { + PollFD& pollfd = fds[i]; + ASSERT(False(pollfd.revents)); + + if (pollfd.fd > static_cast(MAX_FD) || pollfd.fd < 0) { + LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); + pollfd.revents = PollEvents{}; + return {0, Errno::SUCCESS}; + } + + const std::optional& descriptor = file_descriptors[pollfd.fd]; + if (!descriptor) { + LOG_TRACE(Service, "File descriptor handle={} is not allocated", pollfd.fd); + pollfd.revents = PollEvents::Nval; + return {0, Errno::SUCCESS}; + } + sockets[i] = descriptor->socket; } } std::vector host_pollfds(fds.size()); - std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [](PollFD pollfd) { - Network::PollFD result; - result.socket = file_descriptors[pollfd.fd]->socket.get(); - result.events = Translate(pollfd.events); - result.revents = Network::PollEvents{}; - return result; - }); + for (size_t i = 0; i < fds.size(); ++i) { + host_pollfds[i] = Network::PollFD{ + .socket = sockets[i].get(), + .events = Translate(fds[i].events), + .revents = Network::PollEvents{}, + }; + } const auto result = Network::Poll(host_pollfds, timeout); @@ -636,27 +645,28 @@ std::pair BSD_USA::PollImpl(std::vector& write_buffer, std::span } std::pair BSD_USA::AcceptImpl(s32 fd, std::vector& write_buffer) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return {-1, Errno::BADF}; } - const s32 new_fd = FindFreeFileDescriptorHandle(); - if (new_fd < 0) { - LOG_ERROR(Service, "No more file descriptors available"); - return {-1, Errno::MFILE}; - } - - FileDescriptor& descriptor = *file_descriptors[fd]; - auto [result, bsd_errno] = descriptor.socket->Accept(); + auto [result, bsd_errno] = descriptor->socket->Accept(); if (bsd_errno != Network::Errno::SUCCESS) { return {-1, Translate(bsd_errno)}; } + // Accept may block, so the fd is picked afterwards, in the same critical section as the write + s32 new_fd; { std::scoped_lock lock{file_descriptors_mutex}; + new_fd = FindFreeFileDescriptorHandle(); + if (new_fd < 0) { + LOG_ERROR(Service, "No more file descriptors available"); + return {-1, Errno::MFILE}; + } file_descriptors[new_fd] = FileDescriptor{ .socket = std::move(result.socket), - .is_connection_based = descriptor.is_connection_based, + .is_connection_based = descriptor->is_connection_based, }; } @@ -667,34 +677,28 @@ std::pair BSD_USA::AcceptImpl(s32 fd, std::vector& write_buffer) } Errno BSD_USA::BindImpl(s32 fd, std::span addr) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } ASSERT(addr.size() >= 16); - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); - return Errno::BADF; - } auto addr_in = GetValue(addr); - return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in))); + return Translate(descriptor->socket->Bind(Translate(addr_in))); } Errno BSD_USA::ConnectImpl(s32 fd, std::span addr) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } ASSERT(addr.size() >= 16); - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); - return Errno::BADF; - } auto addr_in = GetValue(addr); - const Errno result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); + const Errno result = Translate(descriptor->socket->Connect(Translate(addr_in))); if (result == Errno::ISCONN) { LOG_DEBUG(Service, "returned ISCONN - socket already connected"); @@ -705,16 +709,12 @@ Errno BSD_USA::ConnectImpl(s32 fd, std::span addr) { } Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { - if (!IsFileDescriptorValid(fd)) { - return Errno::BADF; - } - - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } - const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName(); + const auto [addr_in, bsd_errno] = descriptor->socket->GetPeerName(); if (bsd_errno != Network::Errno::SUCCESS) { return Translate(bsd_errno); } @@ -727,16 +727,12 @@ Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { } Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector& write_buffer) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); - return Errno::BADF; - } - - const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName(); + const auto [addr_in, bsd_errno] = descriptor->socket->GetSockName(); if (bsd_errno != Network::Errno::SUCCESS) { return Translate(bsd_errno); } @@ -749,38 +745,33 @@ Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector& write_buffer) { } Errno BSD_USA::ListenImpl(s32 fd, s32 backlog) { - if (!IsFileDescriptorValid(fd)) { - return Errno::BADF; - } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } - return Translate(file_descriptors[fd]->socket->Listen(backlog)); + return Translate(descriptor->socket->Listen(backlog)); } std::pair BSD_USA::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { - if (!IsFileDescriptorValid(fd)) { - return {-1, Errno::BADF}; - } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return {-1, Errno::BADF}; } - FileDescriptor& descriptor = *file_descriptors[fd]; - switch (cmd) { case FcntlCmd::GETFL: ASSERT(arg == 0); - return {descriptor.flags, Errno::SUCCESS}; + return {descriptor->flags, Errno::SUCCESS}; case FcntlCmd::SETFL: { const bool enable = (arg & Network::FLAG_O_NONBLOCK) != 0; - const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable)); + const Errno bsd_errno = Translate(descriptor->socket->SetNonBlock(enable)); if (bsd_errno != Errno::SUCCESS) { return {-1, bsd_errno}; } - descriptor.flags = arg; + std::scoped_lock lock{file_descriptors_mutex}; + if (auto& entry = file_descriptors[fd]; entry && entry->socket == descriptor->socket) { + entry->flags = arg; + } return {0, Errno::SUCCESS}; } default: @@ -790,11 +781,8 @@ std::pair BSD_USA::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { } Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval) { - if (!IsFileDescriptorValid(fd)) { - return Errno::BADF; - } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } @@ -803,7 +791,7 @@ Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vectorsocket.get(); + Network::SocketBase* const socket = descriptor->socket.get(); switch (optname) { case OptName::ERROR_: { @@ -825,11 +813,8 @@ Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector optval) { - if (!IsFileDescriptorValid(fd)) { - return Errno::BADF; - } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } @@ -838,7 +823,7 @@ Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::spansocket.get(); + Network::SocketBase* const socket = descriptor->socket.get(); if (optname == OptName::LINGER) { ASSERT(optval.size() == sizeof(Linger)); @@ -879,39 +864,35 @@ Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::spansocket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } const Network::ShutdownHow host_how = Translate(static_cast(how)); - return Translate(file_descriptors[fd]->socket->Shutdown(host_how)); + return Translate(descriptor->socket->Shutdown(host_how)); } std::pair BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector& message) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return {-1, Errno::BADF}; } - FileDescriptor& descriptor = *file_descriptors[fd]; - // Apply flags using Network::FLAG_MSG_DONTWAIT; using Network::FLAG_O_NONBLOCK; if ((flags & FLAG_MSG_DONTWAIT) != 0) { flags &= ~FLAG_MSG_DONTWAIT; - if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { - descriptor.socket->SetNonBlock(true); + if ((descriptor->flags & FLAG_O_NONBLOCK) == 0) { + descriptor->socket->SetNonBlock(true); } } - const auto [ret, bsd_errno] = Translate(descriptor.socket->Recv(flags, message)); + const auto [ret, bsd_errno] = Translate(descriptor->socket->Recv(flags, message)); // Restore original state - if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { - descriptor.socket->SetNonBlock(false); + if ((descriptor->flags & FLAG_O_NONBLOCK) == 0) { + descriptor->socket->SetNonBlock(false); } return {ret, bsd_errno}; @@ -919,15 +900,14 @@ std::pair BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector& mess std::pair BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector& message, std::vector& addr) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return {-1, Errno::BADF}; } - FileDescriptor& descriptor = *file_descriptors[fd]; - Network::SockAddrIn addr_in{}; Network::SockAddrIn* p_addr_in = nullptr; - if (descriptor.is_connection_based) { + if (descriptor->is_connection_based) { // Connection based file descriptors (e.g. TCP) zero addr addr.clear(); } else { @@ -939,16 +919,17 @@ std::pair BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector& using Network::FLAG_O_NONBLOCK; if ((flags & FLAG_MSG_DONTWAIT) != 0) { flags &= ~FLAG_MSG_DONTWAIT; - if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { - descriptor.socket->SetNonBlock(true); + if ((descriptor->flags & FLAG_O_NONBLOCK) == 0) { + descriptor->socket->SetNonBlock(true); } } - const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in)); + const auto [ret, bsd_errno] = + Translate(descriptor->socket->RecvFrom(flags, message, p_addr_in)); // Restore original state - if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { - descriptor.socket->SetNonBlock(false); + if ((descriptor->flags & FLAG_O_NONBLOCK) == 0) { + descriptor->socket->SetNonBlock(false); } if (p_addr_in) { @@ -965,23 +946,17 @@ std::pair BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector& } std::pair BSD_USA::SendImpl(s32 fd, u32 flags, std::span message) { - if (!IsFileDescriptorValid(fd)) { + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return {-1, Errno::BADF}; } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); - return {-1, Errno::BADF}; - } - return Translate(file_descriptors[fd]->socket->Send(message, flags)); + return Translate(descriptor->socket->Send(message, flags)); } std::pair BSD_USA::SendToImpl(s32 fd, u32 flags, std::span message, std::span addr) { - if (!IsFileDescriptorValid(fd)) { - return {-1, Errno::BADF}; - } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return {-1, Errno::BADF}; } @@ -994,36 +969,36 @@ std::pair BSD_USA::SendToImpl(s32 fd, u32 flags, std::span p_addr_in = &addr_in; } - return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in)); + return Translate(descriptor->socket->SendTo(flags, message, p_addr_in)); } Errno BSD_USA::CloseImpl(s32 fd) { - if (!IsFileDescriptorValid(fd)) { - return Errno::BADF; - } - if (!file_descriptors[fd]->socket) { - LOG_WARNING(Service, "Uninitialized socket"); + const auto descriptor = CopyDescriptor(fd); + if (!descriptor) { return Errno::BADF; } - const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); + const Errno bsd_errno = Translate(descriptor->socket->Close()); if (bsd_errno != Errno::SUCCESS) { return bsd_errno; } LOG_INFO(Service, "Close socket fd={}", fd); + // Another thread may have closed this fd and reused the slot meanwhile std::scoped_lock lock{file_descriptors_mutex}; - file_descriptors[fd].reset(); + if (file_descriptors[fd] && file_descriptors[fd]->socket == descriptor->socket) { + file_descriptors[fd].reset(); + } return bsd_errno; } std::variant BSD_USA::DuplicateSocketImpl(s32 fd) { - if (!IsFileDescriptorValid(fd)) { + std::scoped_lock lock{file_descriptors_mutex}; + if (!IsFileDescriptorValid(fd) || !file_descriptors[fd]->socket) { 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"); @@ -1039,6 +1014,16 @@ std::variant BSD_USA::DuplicateSocketImpl(s32 fd) { } std::optional> BSD_USA::GetSocket(s32 fd) { + if (auto descriptor = CopyDescriptor(fd)) { + return std::move(descriptor->socket); + } + return std::nullopt; +} + +std::optional BSD_USA::CopyDescriptor(s32 fd) { + // The copy holds a reference to the socket, so a close on another bsd thread cannot destroy + // it while the caller uses it + std::scoped_lock lock{file_descriptors_mutex}; if (!IsFileDescriptorValid(fd)) { return std::nullopt; } @@ -1046,7 +1031,7 @@ std::optional> BSD_USA::GetSocket(s32 fd) { LOG_WARNING(Service, "Uninitialized socket"); return std::nullopt; } - return file_descriptors[fd]->socket; + return file_descriptors[fd]; } s32 BSD_USA::FindFreeFileDescriptorHandle() noexcept { @@ -1081,12 +1066,20 @@ 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}; + // A duplicated fd shares its socket, which must still get each packet only once + std::array delivered{}; + size_t num_delivered = 0; for (auto& optional_descriptor : file_descriptors) { if (!optional_descriptor.has_value() || !optional_descriptor->socket) { continue; } - FileDescriptor& descriptor = *optional_descriptor; - descriptor.socket->HandleProxyPacket(packet); + Network::SocketBase* const socket = optional_descriptor->socket.get(); + const auto delivered_end = delivered.begin() + num_delivered; + if (std::find(delivered.begin(), delivered_end, socket) != delivered_end) { + continue; + } + delivered[num_delivered++] = socket; + socket->HandleProxyPacket(packet); } } diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 0df5c58945..f908edc7d2 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -178,11 +178,13 @@ private: s32 FindFreeFileDescriptorHandle() noexcept; bool IsFileDescriptorValid(s32 fd) const noexcept; + /// Copies a descriptor under file_descriptors_mutex; nullopt if fd has no socket + std::optional CopyDescriptor(s32 fd); void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept; static inline std::array, MAX_FD> file_descriptors{}; - /// Guards file_descriptors against the room thread, which delivers proxy packets to them + /// Guards file_descriptors. Calls use CopyDescriptor, so sockets outlive a concurrent close. static inline std::mutex file_descriptors_mutex; /// Callback to parse and handle a received wifi packet. -- cgit v1.2.3