aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-11-03 00:17:34 +0100
committercirakg <cirakg@cirakg.xyz>2026-11-03 00:17:34 +0100
commit78e0a12180a71adf8dffc2c2f6697a0a0735d5ea (patch)
treeaa01f2f66b870d40780f957e97f7a071533e2b5a /src/core
parentb233b36862d370d73fe517a1e53c34b50c2845f5 (diff)
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)
Diffstat (limited to 'src/core')
-rw-r--r--src/core/hle/service/nifm/nifm.cpp37
-rw-r--r--src/core/hle/service/sockets/bsd.cpp71
-rw-r--r--src/core/hle/service/sockets/bsd.h12
-rw-r--r--src/core/internal_network/network.cpp11
-rw-r--r--src/core/internal_network/network.h4
-rw-r--r--src/core/internal_network/socket_proxy.cpp29
-rw-r--r--src/core/internal_network/socket_proxy.h3
7 files changed, 111 insertions, 56 deletions
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index e0a23f7a63..8714597f89 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -73,13 +73,8 @@ 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()) {
- // 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;
- }
+ if (Network::GetRoomIPv4Address()) {
+ return true;
}
return Network::GetHostIPv4Address().has_value() && !Settings::values.airplane_mode.GetValue();
}
@@ -128,9 +123,9 @@ static_assert(sizeof(IpAddressSetting) == 0xD, "IpAddressSetting has incorrect s
// 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();
+static void ApplyRoomAddress(IpAddressSetting& setting) {
+ if (const auto room_ip = Network::GetRoomIPv4Address()) {
+ setting.ip_address = *room_ip;
setting.subnet_mask = RoomSubnetMask;
}
}
@@ -596,7 +591,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()) {
- ApplyRoomAddress(*room_member, profile.ip_setting_data.ip_address_setting);
+ ApplyRoomAddress(profile.ip_setting_data.ip_address_setting);
profile.ip_setting_data.dns_setting.secondary_dns = {8, 8, 8, 8};
}
@@ -618,6 +613,8 @@ void IGeneralService::EnumerateNetworkInterfaces(HLERequestContext& ctx) {
using Network::HostAdapterKind;
const auto adapters = Network::GetAvailableNetworkInterfaces();
+ const auto selected = Network::GetSelectedNetworkInterface();
+ const auto room_ip = Network::GetRoomIPv4Address();
constexpr size_t kEntry = 0x3F0;
std::vector<u8> blob(adapters.size() * kEntry, 0);
@@ -633,6 +630,12 @@ void IGeneralService::EnumerateNetworkInterfaces(HLERequestContext& ctx) {
*reinterpret_cast<in_addr*>(base + 0x1C) = host.subnet_mask;
*reinterpret_cast<in_addr*>(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) {
+ std::memcpy(base + 0x18, room_ip->data(), room_ip->size());
+ std::memcpy(base + 0x1C, RoomSubnetMask.data(), RoomSubnetMask.size());
+ }
+
std::string name_utf8 = host.name;
name_utf8.resize(0x110, '\0');
std::memcpy(base + 0x2E0, name_utf8.data(), 0x110);
@@ -732,9 +735,7 @@ void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) {
.wireless_setting_data{wifi}};
}();
- if (auto room_member = Network::GetRoomMember().lock()) {
- ApplyRoomAddress(*room_member, profile.ip_setting_data.ip_address_setting);
- }
+ ApplyRoomAddress(profile.ip_setting_data.ip_address_setting);
ctx.WriteBuffer(profile);
@@ -871,10 +872,8 @@ void IGeneralService::GetCurrentIpAddress(HLERequestContext& ctx) {
ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
}
- if (auto room_member = Network::GetRoomMember().lock()) {
- if (room_member->IsConnected()) {
- ipv4 = room_member->GetFakeIpAddress();
- }
+ if (const auto room_ip = Network::GetRoomIPv4Address()) {
+ ipv4 = room_ip;
}
IPC::ResponseBuilder rb{ctx, 3};
@@ -921,7 +920,7 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) {
}
if (auto room_member = Network::GetRoomMember().lock()) {
- ApplyRoomAddress(*room_member, info.ip);
+ ApplyRoomAddress(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/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<s32, Errno> BSD_USA::SocketImpl(Domain domain, Type type, Protocol pro
UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type");
type = static_cast<Type>(static_cast<u32>(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<Network::SocketBase> socket;
auto room_member = Network::GetRoomMember().lock();
if (room_member && room_member->IsConnected()) {
- descriptor.socket = std::make_shared<Network::ProxySocket>();
+ socket = std::make_shared<Network::ProxySocket>();
} else {
- descriptor.socket = std::make_shared<Network::Socket>();
+ socket = std::make_shared<Network::Socket>();
}
+ 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<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& 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<s32, Errno> 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<std::mutex> BSD_USA::LockService() noexcept {
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 5cc3eb5b81..0df5c58945 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -7,6 +7,7 @@
#pragma once
#include <memory>
+#include <mutex>
#include <span>
#include <vector>
#include <variant>
@@ -181,12 +182,17 @@ private:
void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept;
static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{};
+ /// Guards file_descriptors against the room thread, which delivers proxy packets to them
+ static inline std::mutex file_descriptors_mutex;
/// Callback to parse and handle a received wifi packet.
- void OnProxyPacketReceived(const Network::ProxyPacket& packet);
+ static void OnProxyPacketReceived(const Network::ProxyPacket& packet);
- // Callback identifier for the OnProxyPacketReceived event.
- Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
+ // Callback identifier for the OnProxyPacketReceived event. bsd:s, bsd:u and bsd:a share
+ // file_descriptors, so they bind once; otherwise each packet is queued once per service.
+ static inline Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received;
+ static inline std::mutex proxy_binding_mutex;
+ static inline size_t proxy_binding_count = 0;
protected:
std::unique_lock<std::mutex> LockService() noexcept override;
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index 01d37fb35b..d1c436aa08 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -722,6 +722,17 @@ std::optional<IPv4Address> GetHostIPv4Address() {
return TranslateIPv4(network_interface->ip_address);
}
+std::optional<IPv4Address> GetRoomIPv4Address() {
+ if (const auto room_member = GetRoomMember().lock()) {
+ // The room host and moderators are in the Moderator state instead of Joined
+ const auto state = room_member->GetState();
+ if (state == RoomMember::State::Joined || state == RoomMember::State::Moderator) {
+ return room_member->GetFakeIpAddress();
+ }
+ }
+ return std::nullopt;
+}
+
std::string IPv4AddressToString(IPv4Address ip_addr) {
std::array<char, INET_ADDRSTRLEN> buf = {};
ASSERT(inet_ntop(AF_INET, &ip_addr, buf.data(), sizeof(buf)) == buf.data());
diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h
index 2c9ae523c5..a598f75348 100644
--- a/src/core/internal_network/network.h
+++ b/src/core/internal_network/network.h
@@ -121,6 +121,10 @@ constexpr IPv4Address TranslateIPv4(in_addr addr) {
/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
std::optional<IPv4Address> GetHostIPv4Address();
+/// @brief Returns the fake IPv4 address of the joined room, the emulated network while in a room
+/// @return nullopt outside a room and while still joining it
+std::optional<IPv4Address> GetRoomIPv4Address();
+
std::string IPv4AddressToString(IPv4Address ip_addr);
u32 IPv4AddressToInteger(IPv4Address ip_addr);
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
index 83eb2ce9d0..6c2f79da6e 100644
--- a/src/core/internal_network/socket_proxy.cpp
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -43,9 +43,21 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
LOG_INFO(Network, "Received broadcast packet, but not configured for broadcast mode");
return;
}
+ }
- auto decompressed = packet;
- decompressed.data = Common::Compression::DecompressDataZSTD(packet.data);
+ // Decompress without the lock, so the game thread does not wait for it
+ ProxyPacket decompressed{
+ .local_endpoint = packet.local_endpoint,
+ .remote_endpoint = packet.remote_endpoint,
+ .protocol = packet.protocol,
+ .broadcast = packet.broadcast,
+ .data = Common::Compression::DecompressDataZSTD(packet.data),
+ };
+ {
+ std::lock_guard guard(packets_mutex);
+ if (closed) {
+ return;
+ }
received_packets.push(std::move(decompressed));
}
packets_cv.notify_one();
@@ -152,26 +164,28 @@ std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, So
std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::span<u8> message, SockAddrIn* addr,
std::size_t max_length) {
- ProxyPacket& packet = received_packets.front();
+ const ProxyPacket& packet = received_packets.front();
if (addr) {
addr->family = Domain::INET;
addr->ip = packet.local_endpoint.ip; // The senders ip address
addr->portno = packet.local_endpoint.portno; // The senders port number
}
- const std::size_t read_bytes = (std::min)(packet.data.size(), max_length);
- std::memcpy(message.data(), packet.data.data(), read_bytes);
+ const std::span<const u8> unread = std::span{packet.data}.subspan(front_read_offset);
+ const std::size_t read_bytes = (std::min)(unread.size(), max_length);
+ std::memcpy(message.data(), unread.data(), read_bytes);
if ((flags & FLAG_MSG_PEEK) != 0) {
return {static_cast<s32>(read_bytes), Errno::SUCCESS};
}
- if (protocol == Protocol::TCP && read_bytes < packet.data.size()) {
+ if (protocol == Protocol::TCP && read_bytes < unread.size()) {
// Keep the unread part of the stream for the next call
- packet.data.erase(packet.data.begin(), packet.data.begin() + read_bytes);
+ front_read_offset += read_bytes;
} else {
// Like BSD recv(), the rest of a datagram that does not fit is discarded
received_packets.pop();
+ front_read_offset = 0;
}
return {static_cast<s32>(read_bytes), Errno::SUCCESS};
@@ -240,6 +254,7 @@ Errno ProxySocket::Close() {
fd = INVALID_SOCKET;
closed = true;
received_packets = {};
+ front_read_offset = 0;
}
packets_cv.notify_all();
diff --git a/src/core/internal_network/socket_proxy.h b/src/core/internal_network/socket_proxy.h
index 8343fcbae1..d2bd7aae77 100644
--- a/src/core/internal_network/socket_proxy.h
+++ b/src/core/internal_network/socket_proxy.h
@@ -90,7 +90,8 @@ private:
SockAddrIn local_endpoint{};
bool blocking = true;
std::queue<ProxyPacket> received_packets;
- Protocol protocol;
+ std::size_t front_read_offset = 0; ///< Bytes of the front TCP packet that were already read
+ Protocol protocol{};
std::mutex packets_mutex;
std::condition_variable packets_cv;