diff options
Diffstat (limited to 'src/core/hle/service')
| -rw-r--r-- | src/core/hle/service/ldn/lan_discovery.cpp | 85 | ||||
| -rw-r--r-- | src/core/hle/service/ldn/lan_discovery.h | 8 | ||||
| -rw-r--r-- | src/core/hle/service/nifm/nifm.cpp | 68 | ||||
| -rw-r--r-- | src/core/hle/service/sockets/bsd.cpp | 390 | ||||
| -rw-r--r-- | src/core/hle/service/sockets/bsd.h | 18 |
5 files changed, 357 insertions, 212 deletions
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp index 90adcdcee4..d7debdd67b 100644 --- a/src/core/hle/service/ldn/lan_discovery.cpp +++ b/src/core/hle/service/ldn/lan_discovery.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/service/ldn/lan_discovery.h" +#include "common/random.h" #include "core/internal_network/network.h" #include "core/internal_network/network_interface.h" @@ -70,6 +71,7 @@ void LANDiscovery::InitNodeStateChange() { } State LANDiscovery::GetState() const { + std::scoped_lock lock{packet_mutex}; return state; } @@ -78,6 +80,7 @@ void LANDiscovery::SetState(State new_state) { } Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const { + std::scoped_lock lock{packet_mutex}; if (state == State::AccessPointCreated || state == State::StationConnected) { std::memcpy(&out_network, &network_info, sizeof(network_info)); return ResultSuccess; @@ -88,6 +91,7 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const { Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network, std::span<NodeLatestUpdate> out_updates) { + std::scoped_lock lock{packet_mutex}; if (out_updates.size() > NodeCountMax) { return ResultInvalidBufferCount; } @@ -105,6 +109,7 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network, } DisconnectReason LANDiscovery::GetDisconnectReason() const { + std::scoped_lock lock{packet_mutex}; return disconnect_reason; } @@ -194,7 +199,7 @@ Result LANDiscovery::CloseAccessPoint() { } if (state == State::AccessPointCreated) { - DestroyNetwork(); + DestroyNetworkImpl(); } ResetStations(); @@ -222,8 +227,8 @@ Result LANDiscovery::CloseStation() { return ResultBadState; } - if (state == State::StationConnected) { - Disconnect(); + if (state == State::StationConnected || host_ip) { + DisconnectImpl(); } ResetStations(); @@ -251,7 +256,8 @@ Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config, network_info.common.channel = network_config.channel; } - std::independent_bits_engine<std::mt19937, 64, u64> bits_engine; + std::independent_bits_engine<std::mt19937, 64, u64> bits_engine{ + Common::Random::GetMT19937()}; network_info.network_id.session_id.high = bits_engine(); network_info.network_id.session_id.low = bits_engine(); network_info.network_id.intent_id = network_config.intent_id; @@ -272,6 +278,11 @@ Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config, } Result LANDiscovery::DestroyNetwork() { + std::scoped_lock lock{packet_mutex}; + return DestroyNetworkImpl(); +} + +Result LANDiscovery::DestroyNetworkImpl() { for (auto local_ip : connected_clients) { SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip); } @@ -286,7 +297,10 @@ Result LANDiscovery::DestroyNetwork() { Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config, u16 local_communication_version) { - std::scoped_lock lock{packet_mutex}; + std::unique_lock lock{packet_mutex}; + if (state != State::StationOpened || host_ip) { + return ResultBadState; + } if (network_info_.ldn.node_count == 0) { return ResultInvalidNodeCount; } @@ -299,21 +313,39 @@ Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address; std::reverse(std::begin(node_host), std::end(node_host)); // htonl host_ip = node_host; - SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip); - + network_info = network_info_; InitNodeStateChange(); + SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip); - std::this_thread::sleep_for(std::chrono::seconds(1)); + // The room callback needs packet_mutex to install the host's connection reply. + const bool replied = connection_event.wait_for(lock, connection_timeout, [this] { + return state != State::StationOpened || !host_ip; + }); + if (!replied) { + SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip); + host_ip.reset(); + return ResultAuthenticationTimeout; + } + if (state != State::StationConnected) { + return ResultConnectionFailed; + } return ResultSuccess; } Result LANDiscovery::Disconnect() { + std::scoped_lock lock{packet_mutex}; + return DisconnectImpl(); +} + +Result LANDiscovery::DisconnectImpl() { if (host_ip) { SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip); } + host_ip.reset(); SetState(State::StationOpened); + connection_event.notify_all(); lan_event(); return ResultSuccess; @@ -346,10 +378,10 @@ Result LANDiscovery::Finalize() { if (inited) { if (state == State::AccessPointCreated) { - DestroyNetwork(); + DestroyNetworkImpl(); } - if (state == State::StationConnected) { - Disconnect(); + if (state == State::StationConnected || host_ip) { + DisconnectImpl(); } ResetStations(); @@ -392,6 +424,7 @@ void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) { SetState(State::StationConnected); } OnNetworkInfoChanged(); + connection_event.notify_all(); } void LANDiscovery::OnDisconnectFromHost() { @@ -401,6 +434,7 @@ void LANDiscovery::OnDisconnectFromHost() { SetState(State::StationOpened); lan_event(); } + connection_event.notify_all(); } void LANDiscovery::OnNetworkInfoChanged() { @@ -489,6 +523,9 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) { break; } case Network::LDNPacketType::ScanResp: { + if (packet.data.size() != sizeof(NetworkInfo)) { + break; + } LOG_INFO(Frontend, "ScanResp packet received!"); NetworkInfo info{}; @@ -498,6 +535,17 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) { break; } case Network::LDNPacketType::Connect: { + if (state != State::AccessPointCreated || packet.data.size() != sizeof(NodeInfo)) { + break; + } + if (std::find(connected_clients.begin(), connected_clients.end(), packet.local_ip) != + connected_clients.end()) { + SendPacket(Network::LDNPacketType::SyncNetwork, network_info, packet.local_ip); + break; + } + if (network_info.ldn.node_count >= network_info.ldn.node_count_max) { + break; + } LOG_INFO(Frontend, "Connect packet received!"); NodeInfo info{}; @@ -518,6 +566,9 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) { break; } case Network::LDNPacketType::Disconnect: { + if (state != State::AccessPointCreated || packet.data.size() != sizeof(NodeInfo)) { + break; + } LOG_INFO(Frontend, "Disconnect packet received!"); connected_clients.erase( @@ -538,17 +589,23 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) { break; } case Network::LDNPacketType::DestroyNetwork: { + if (!host_ip || packet.local_ip != *host_ip) { + break; + } ResetStations(); OnDisconnectFromHost(); break; } case Network::LDNPacketType::SyncNetwork: { - if (state == State::StationOpened || state == State::StationConnected) { + if (host_ip && packet.local_ip == *host_ip && + packet.data.size() == sizeof(NetworkInfo) && + (state == State::StationOpened || state == State::StationConnected)) { LOG_INFO(Frontend, "SyncNetwork packet received!"); NetworkInfo info{}; std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo)); - - OnSyncNetwork(info); + if (info.network_id.session_id == network_info.network_id.session_id) { + OnSyncNetwork(info); + } } else { LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!"); } diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h index f1f3c4bece..e641b11409 100644 --- a/src/core/hle/service/ldn/lan_discovery.h +++ b/src/core/hle/service/ldn/lan_discovery.h @@ -10,6 +10,8 @@ #pragma once #include <array> +#include <chrono> +#include <condition_variable> #include <cstring> #include <functional> #include <memory> @@ -92,6 +94,8 @@ protected: void InitNodeStateChange(); void ResetStations(); + Result DestroyNetworkImpl(); + Result DisconnectImpl(); void UpdateNodes(); void OnSyncNetwork(const NetworkInfo& info); @@ -118,7 +122,9 @@ protected: static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"}; bool inited{}; - std::mutex packet_mutex; + mutable std::mutex packet_mutex; + std::condition_variable connection_event; + std::chrono::nanoseconds connection_timeout{std::chrono::seconds(5)}; std::array<LanStation, StationCountMax> stations; std::array<NodeLatestUpdate, NodeCountMax> node_changes{}; std::array<u8, NodeCountMax> node_last_states{}; diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index efada9b807..ea00e9f35c 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -70,6 +70,15 @@ static u128 MakeUuidFromName(std::string_view name) { return {h1, h2}; } +// 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 (Network::GetRoomIPv4Address()) { + return true; + } + return Network::GetHostIPv4Address().has_value() && !Settings::values.airplane_mode.GetValue(); +} + // This is nn::nifm::RequestState enum class RequestState : u32 { NotSubmitted = 1, @@ -110,6 +119,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(IpAddressSetting& setting) { + if (const auto room_ip = Network::GetRoomIPv4Address()) { + setting.ip_address = *room_ip; + setting.subnet_mask = RoomSubnetMask; + } +} + // This is nn::nifm::DnsSetting struct DnsSetting { bool is_automatic{}; @@ -430,8 +450,7 @@ private: LOG_DEBUG(Service_NIFM, "(STUBBED) called"); const auto result = [this] { - const auto has_connection = Network::GetHostIPv4Address().has_value() && - !Settings::values.airplane_mode.GetValue(); + const auto has_connection = IsNetworkAvailable(); switch (state) { case RequestState::NotSubmitted: return has_connection ? ResultSuccess : ResultNetworkCommunicationDisabled; @@ -572,10 +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()) { - if (room_member->IsConnected()) { - profile.ip_setting_data.ip_address_setting.ip_address = - room_member->GetFakeIpAddress(); - } + ApplyRoomAddress(profile.ip_setting_data.ip_address_setting); profile.ip_setting_data.dns_setting.secondary_dns = {8, 8, 8, 8}; } @@ -584,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<u8>(NetworkProfileType::User); + profile.interface_type = static_cast<u8>(NetworkInterfaceType::Ethernet); } ctx.WriteBuffer(profile); @@ -597,6 +626,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); @@ -612,6 +643,13 @@ 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. + // 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()); + } + std::string name_utf8 = host.name; name_utf8.resize(0x110, '\0'); std::memcpy(base + 0x2E0, name_utf8.data(), 0x110); @@ -711,11 +749,7 @@ void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) { .wireless_setting_data{wifi}}; }(); - if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - profile.ip_setting_data.ip_address_setting.ip_address = room_member->GetFakeIpAddress(); - } - } + ApplyRoomAddress(profile.ip_setting_data.ip_address_setting); ctx.WriteBuffer(profile); @@ -852,10 +886,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}; @@ -902,9 +934,7 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) { } if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - info.ip.ip_address = room_member->GetFakeIpAddress(); - } + ApplyRoomAddress(info.ip); info.dns.is_automatic = true; info.dns.primary_dns = {1, 1, 1, 1}; info.dns.secondary_dns = {8, 8, 8, 8}; @@ -984,7 +1014,7 @@ void IGeneralService::IsAnyInternetRequestAccepted(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - if (Network::GetHostIPv4Address().has_value() && !Settings::values.airplane_mode.GetValue()) { + if (IsNetworkAvailable()) { rb.Push<u8>(1); } else { rb.Push<u8>(0); diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 02c6bb7762..730a7edcc1 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 <algorithm> #include <array> #include <memory> #include <utility> @@ -13,6 +14,7 @@ #include "common/logging.h" #include "common/socket_types.h" +#include "common/zstd_compression.h" #include "core/core.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/service/ipc_helpers.h" @@ -535,33 +537,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}; } @@ -595,31 +600,39 @@ std::pair<s32, Errno> BSD_USA::PollImpl(std::vector<u8>& write_buffer, std::span return {-1, Errno::INVAL}; } - for (PollFD& pollfd : fds) { - ASSERT(False(pollfd.revents)); - - if (pollfd.fd > static_cast<s32>(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<FileDescriptor>& 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<std::shared_ptr<Network::SocketBase>> 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<s32>(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<FileDescriptor>& 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<Network::PollFD> 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); @@ -633,26 +646,30 @@ std::pair<s32, Errno> BSD_USA::PollImpl(std::vector<u8>& write_buffer, std::span } std::pair<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& 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)}; } - 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; + // 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, + }; + } const SockAddrIn guest_addr_in = Translate(result.sockaddr_in); PutValue(write_buffer, guest_addr_in); @@ -661,34 +678,28 @@ std::pair<s32, Errno> BSD_USA::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) } Errno BSD_USA::BindImpl(s32 fd, std::span<const u8> 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<SockAddrIn>(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<const u8> 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<SockAddrIn>(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"); @@ -699,16 +710,12 @@ Errno BSD_USA::ConnectImpl(s32 fd, std::span<const u8> addr) { } Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector<u8>& 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); } @@ -721,16 +728,12 @@ Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) { } Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector<u8>& 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); } @@ -743,38 +746,34 @@ Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector<u8>& 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<s32, Errno> 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)); + // Set the mode and the flags together, so SetDontWait never sees one without the other + std::scoped_lock lock{file_descriptors_mutex}; + const Errno bsd_errno = Translate(descriptor->socket->SetNonBlock(enable)); if (bsd_errno != Errno::SUCCESS) { return {-1, bsd_errno}; } - descriptor.flags = arg; + if (auto& entry = file_descriptors[fd]; entry && entry->socket == descriptor->socket) { + entry->flags = arg; + } return {0, Errno::SUCCESS}; } default: @@ -784,11 +783,8 @@ std::pair<s32, Errno> BSD_USA::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { } Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& 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; } @@ -797,7 +793,7 @@ Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8 return Errno::SUCCESS; } - Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); + Network::SocketBase* const socket = descriptor->socket.get(); switch (optname) { case OptName::ERROR_: { @@ -819,11 +815,8 @@ Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8 } Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8> 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; } @@ -832,7 +825,7 @@ Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<cons return Errno::SUCCESS; } - Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); + Network::SocketBase* const socket = descriptor->socket.get(); if (optname == OptName::LINGER) { ASSERT(optval.size() == sizeof(Linger)); @@ -873,39 +866,30 @@ Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<cons } Errno BSD_USA::ShutdownImpl(s32 fd, s32 how) { - 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 Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how)); - return Translate(file_descriptors[fd]->socket->Shutdown(host_how)); + return Translate(descriptor->socket->Shutdown(host_how)); } std::pair<s32, Errno> BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector<u8>& 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); - } - } + const bool dont_wait = (flags & FLAG_MSG_DONTWAIT) != 0 && SetDontWait(fd, *descriptor, true); + flags &= ~FLAG_MSG_DONTWAIT; - 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 (dont_wait) { + SetDontWait(fd, *descriptor, false); } return {ret, bsd_errno}; @@ -913,15 +897,14 @@ std::pair<s32, Errno> BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector<u8>& mess std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message, std::vector<u8>& 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 { @@ -930,19 +913,15 @@ std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& // 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); - } - } + const bool dont_wait = (flags & FLAG_MSG_DONTWAIT) != 0 && SetDontWait(fd, *descriptor, true); + flags &= ~FLAG_MSG_DONTWAIT; - 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 (dont_wait) { + SetDontWait(fd, *descriptor, false); } if (p_addr_in) { @@ -959,23 +938,17 @@ std::pair<s32, Errno> BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& } std::pair<s32, Errno> BSD_USA::SendImpl(s32 fd, u32 flags, std::span<const u8> 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<s32, Errno> BSD_USA::SendToImpl(s32 fd, u32 flags, std::span<const u8> message, std::span<const u8> 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}; } @@ -988,31 +961,33 @@ std::pair<s32, Errno> BSD_USA::SendToImpl(s32 fd, u32 flags, std::span<const u8> 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"); - return Errno::BADF; - } - - const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); - if (bsd_errno != Errno::SUCCESS) { - return bsd_errno; + std::shared_ptr<Network::SocketBase> socket; + { + // Free the slot first, so of two threads closing the same fd only one closes the socket. + // Like POSIX close(), the fd is released even if closing the socket fails. + std::scoped_lock lock{file_descriptors_mutex}; + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } + socket = std::move(file_descriptors[fd]->socket); + file_descriptors[fd].reset(); } LOG_INFO(Service, "Close socket fd={}", fd); - - file_descriptors[fd].reset(); - return bsd_errno; + return Translate(socket->Close()); } std::variant<s32, Errno> 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; } @@ -1031,6 +1006,16 @@ std::variant<s32, Errno> BSD_USA::DuplicateSocketImpl(s32 fd) { } std::optional<std::shared_ptr<Network::SocketBase>> BSD_USA::GetSocket(s32 fd) { + if (auto descriptor = CopyDescriptor(fd)) { + return std::move(descriptor->socket); + } + return std::nullopt; +} + +std::optional<BSD_USA::FileDescriptor> 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; } @@ -1038,7 +1023,7 @@ std::optional<std::shared_ptr<Network::SocketBase>> 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 { @@ -1050,8 +1035,21 @@ s32 BSD_USA::FindFreeFileDescriptorHandle() noexcept { return -1; } +bool BSD_USA::SetDontWait(s32 fd, const FileDescriptor& descriptor, bool enable) { + // Read the current flags, not the copy: fcntl may have changed them during the receive + std::scoped_lock lock{file_descriptors_mutex}; + const auto& entry = file_descriptors[fd]; + const s32 current_flags = + entry && entry->socket == descriptor.socket ? entry->flags : descriptor.flags; + if ((current_flags & Network::FLAG_O_NONBLOCK) != 0) { + return false; + } + descriptor.socket->SetNonBlock(enable); + return true; +} + bool BSD_USA::IsFileDescriptorValid(s32 fd) const noexcept { - if (fd > static_cast<s32>(MAX_FD) || fd < 0) { + if (fd >= static_cast<s32>(MAX_FD) || fd < 0) { LOG_ERROR(Service, "Invalid file descriptor handle={}", fd); return false; } @@ -1071,12 +1069,45 @@ void BSD_USA::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const } void BSD_USA::OnProxyPacketReceived(const Network::ProxyPacket& packet) { - for (auto& optional_descriptor : file_descriptors) { - if (!optional_descriptor.has_value()) { - continue; + // Runs on the room thread. The copies keep each socket alive if a bsd thread closes it, so + // the bsd threads do not wait for the decompression below. Only sockets that accept the + // packet are collected, so an unwanted packet is never decompressed. + std::array<std::shared_ptr<Network::SocketBase>, MAX_FD> sockets{}; + size_t num_sockets = 0; + { + std::scoped_lock lock{file_descriptors_mutex}; + for (const auto& descriptor : file_descriptors) { + if (!descriptor.has_value() || !descriptor->socket) { + continue; + } + // A duplicated fd shares its socket, which must still get each packet only once + const auto sockets_end = sockets.begin() + num_sockets; + if (std::find(sockets.begin(), sockets_end, descriptor->socket) == sockets_end && + descriptor->socket->AcceptsProxyPacket(packet)) { + sockets[num_sockets++] = descriptor->socket; + } } - FileDescriptor& descriptor = *optional_descriptor; - descriptor.socket.get()->HandleProxyPacket(packet); + } + if (num_sockets == 0) { + return; + } + + // Decompress once for all sockets that share the port. The room does not check the frame, so + // drop a malformed or oversized one instead of throwing on the room thread. + auto data = Common::Compression::TryDecompressDataZSTD(packet.data, MAX_ROOM_PACKET_SIZE); + if (!data) { + LOG_WARNING(Service, "Dropped a room packet with an invalid or oversized payload"); + return; + } + const Network::ProxyPacket decompressed{ + .local_endpoint = packet.local_endpoint, + .remote_endpoint = packet.remote_endpoint, + .protocol = packet.protocol, + .broadcast = packet.broadcast, + .data = std::move(*data), + }; + for (size_t i = 0; i < num_sockets; ++i) { + sockets[i]->HandleProxyPacket(decompressed); } } @@ -1140,18 +1171,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..bff80d7c00 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> @@ -43,6 +44,8 @@ public: private: /// Maximum number of file descriptors static constexpr size_t MAX_FD = 128; + /// Largest decompressed room packet. A UDP datagram cannot be larger. + static constexpr size_t MAX_ROOM_PACKET_SIZE = 0x10000; struct FileDescriptor { std::shared_ptr<Network::SocketBase> socket; @@ -177,16 +180,25 @@ 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<FileDescriptor> CopyDescriptor(s32 fd); + /// Switches a blocking socket for one MSG_DONTWAIT call; false if fd is already nonblocking + bool SetDontWait(s32 fd, const FileDescriptor& descriptor, bool enable); void BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept; static inline std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors{}; + /// 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. - 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; |
