From ef8acc9c3d859b93190327bf992db1f7a639ee0f Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Sat, 11 Jul 2020 21:43:51 -0300 Subject: [PATCH 1/7] services/nifm: Implement GetCurrentIpAddress This is trivially implemented using the Network abstraction - Used by ftpd --- src/core/hle/service/nifm/nifm.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 01ddcdbd6..2e9d95195 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -9,6 +9,7 @@ #include "core/hle/kernel/writable_event.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/service.h" +#include "core/network/network.h" #include "core/settings.h" namespace Service::NIFM { @@ -174,6 +175,16 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + const auto [ipv4, error] = Network::GetHostIPv4Address(); + UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(ipv4); + } void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -235,7 +246,7 @@ IGeneralService::IGeneralService(Core::System& system) {9, nullptr, "SetNetworkProfile"}, {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"}, {11, nullptr, "GetScanDataOld"}, - {12, nullptr, "GetCurrentIpAddress"}, + {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"}, {13, nullptr, "GetCurrentAccessPointOld"}, {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"}, {15, nullptr, "GetCurrentIpConfigInfo"}, From 22263ccaa4f8f37c5fd76a2c3729fd46a1cd9631 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Sat, 11 Jul 2020 22:26:24 -0300 Subject: [PATCH 2/7] service/sockets: Add enumerations and structures Add guest enumerations and structures used in socket services --- src/core/hle/service/sockets/bsd.h | 2 + src/core/hle/service/sockets/sockets.h | 79 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 3098e3baf..7e1a64015 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -4,8 +4,10 @@ #pragma once +#include "common/common_types.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +#include "core/hle/service/sockets/sockets.h" namespace Service::Sockets { diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index ca8a6a7e0..55b110937 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -4,10 +4,89 @@ #pragma once +#include "common/common_types.h" #include "core/hle/service/service.h" namespace Service::Sockets { +enum class Errno : u32 { + SUCCESS = 0, + BADF = 9, + AGAIN = 11, + INVAL = 22, + MFILE = 24, + NOTCONN = 107, +}; + +enum class Domain : u32 { + INET = 2, +}; + +enum class Type : u32 { + STREAM = 1, + DGRAM = 2, + RAW = 3, + SEQPACKET = 5, +}; + +enum class Protocol : u32 { + UNSPECIFIED = 0, + ICMP = 1, + TCP = 6, + UDP = 17, +}; + +enum class OptName : u32 { + REUSEADDR = 0x4, + BROADCAST = 0x20, + LINGER = 0x80, + SNDBUF = 0x1001, + RCVBUF = 0x1002, + SNDTIMEO = 0x1005, + RCVTIMEO = 0x1006, +}; + +enum class ShutdownHow : s32 { + RD = 0, + WR = 1, + RDWR = 2, +}; + +enum class FcntlCmd : s32 { + GETFL = 3, + SETFL = 4, +}; + +struct SockAddrIn { + u8 len; + u8 family; + u16 portno; + std::array ip; + std::array zeroes; +}; + +struct PollFD { + s32 fd; + u16 events; + u16 revents; +}; + +struct Linger { + u32 onoff; + u32 linger; +}; + +constexpr u16 POLL_IN = 0x01; +constexpr u16 POLL_PRI = 0x02; +constexpr u16 POLL_OUT = 0x04; +constexpr u16 POLL_ERR = 0x08; +constexpr u16 POLL_HUP = 0x10; +constexpr u16 POLL_NVAL = 0x20; + +constexpr u32 FLAG_MSG_DONTWAIT = 0x80; + +constexpr u32 FLAG_O_NONBLOCK = 0x800; + /// Registers all Sockets services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); From 80b4bd358348893f891f722d70a0d8ff44cc44c6 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Sat, 11 Jul 2020 22:32:58 -0300 Subject: [PATCH 3/7] service/sockets: Add translate functions These functions translate from Network enumerations/structures to guest enumerations/structures and viceversa. --- src/core/CMakeLists.txt | 2 + .../hle/service/sockets/sockets_translate.cpp | 165 ++++++++++++++++++ .../hle/service/sockets/sockets_translate.h | 48 +++++ 3 files changed, 215 insertions(+) create mode 100644 src/core/hle/service/sockets/sockets_translate.cpp create mode 100644 src/core/hle/service/sockets/sockets_translate.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c85c9485f..48578ad48 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -501,6 +501,8 @@ add_library(core STATIC hle/service/sockets/sfdnsres.h hle/service/sockets/sockets.cpp hle/service/sockets/sockets.h + hle/service/sockets/sockets_translate.cpp + hle/service/sockets/sockets_translate.h hle/service/spl/csrng.cpp hle/service/spl/csrng.h hle/service/spl/module.cpp diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp new file mode 100644 index 000000000..2be8f642d --- /dev/null +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -0,0 +1,165 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/hle/service/sockets/sockets_translate.h" +#include "core/network/network.h" + +namespace Service::Sockets { + +Errno Translate(Network::Errno value) { + switch (value) { + case Network::Errno::SUCCESS: + return Errno::SUCCESS; + case Network::Errno::BADF: + return Errno::BADF; + case Network::Errno::AGAIN: + return Errno::AGAIN; + case Network::Errno::INVAL: + return Errno::INVAL; + case Network::Errno::MFILE: + return Errno::MFILE; + case Network::Errno::NOTCONN: + return Errno::NOTCONN; + default: + UNIMPLEMENTED_MSG("Unimplemented errno={}", static_cast(value)); + return Errno::SUCCESS; + } +} + +std::pair Translate(std::pair value) { + return {value.first, Translate(value.second)}; +} + +Network::Domain Translate(Domain domain) { + switch (domain) { + case Domain::INET: + return Network::Domain::INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast(domain)); + return {}; + } +} + +Domain Translate(Network::Domain domain) { + switch (domain) { + case Network::Domain::INET: + return Domain::INET; + default: + UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast(domain)); + return {}; + } +} + +Network::Type Translate(Type type) { + switch (type) { + case Type::STREAM: + return Network::Type::STREAM; + case Type::DGRAM: + return Network::Type::DGRAM; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast(type)); + } +} + +Network::Protocol Translate(Type type, Protocol protocol) { + switch (protocol) { + case Protocol::UNSPECIFIED: + LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type"); + switch (type) { + case Type::DGRAM: + return Network::Protocol::UDP; + case Type::STREAM: + return Network::Protocol::TCP; + default: + return Network::Protocol::TCP; + } + case Protocol::TCP: + return Network::Protocol::TCP; + case Protocol::UDP: + return Network::Protocol::UDP; + default: + UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast(protocol)); + return Network::Protocol::TCP; + } +} + +u16 TranslatePollEventsToHost(u16 flags) { + u16 result = 0; + const auto translate = [&result, &flags](u16 from, u16 to) { + if ((flags & from) != 0) { + flags &= ~from; + result |= to; + } + }; + translate(POLL_IN, Network::POLL_IN); + translate(POLL_PRI, Network::POLL_PRI); + translate(POLL_OUT, Network::POLL_OUT); + translate(POLL_ERR, Network::POLL_ERR); + translate(POLL_HUP, Network::POLL_HUP); + translate(POLL_NVAL, Network::POLL_NVAL); + + UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags); + return result; +} + +u16 TranslatePollEventsToGuest(u16 flags) { + u16 result = 0; + const auto translate = [&result, &flags](u16 from, u16 to) { + if ((flags & from) != 0) { + flags &= ~from; + result |= to; + } + }; + + translate(Network::POLL_IN, POLL_IN); + translate(Network::POLL_PRI, POLL_PRI); + translate(Network::POLL_OUT, POLL_OUT); + translate(Network::POLL_ERR, POLL_ERR); + translate(Network::POLL_HUP, POLL_HUP); + translate(Network::POLL_NVAL, POLL_NVAL); + + UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags); + return result; +} + +Network::SockAddrIn Translate(SockAddrIn value) { + ASSERT(value.len == 0 || value.len == sizeof(value)); + + Network::SockAddrIn result; + result.family = Translate(static_cast(value.family)); + result.ip = value.ip; + result.portno = value.portno >> 8 | value.portno << 8; + return result; +} + +SockAddrIn Translate(Network::SockAddrIn value) { + SockAddrIn result; + result.len = sizeof(result); + result.family = static_cast(Translate(value.family)); + result.portno = value.portno >> 8 | value.portno << 8; + result.ip = value.ip; + result.zeroes = {}; + return result; +} + +Network::ShutdownHow Translate(ShutdownHow how) { + switch (how) { + case ShutdownHow::RD: + return Network::ShutdownHow::RD; + case ShutdownHow::WR: + return Network::ShutdownHow::WR; + case ShutdownHow::RDWR: + return Network::ShutdownHow::RDWR; + default: + UNIMPLEMENTED_MSG("Unimplemented how={}", static_cast(how)); + return {}; + } +} + +} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h new file mode 100644 index 000000000..8ed041e31 --- /dev/null +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -0,0 +1,48 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" +#include "core/hle/service/sockets/sockets.h" +#include "core/network/network.h" + +namespace Service::Sockets { + +/// Translate abstract errno to guest errno +Errno Translate(Network::Errno value); + +/// Translate abstract return value errno pair to guest return value errno pair +std::pair Translate(std::pair value); + +/// Translate guest domain to abstract domain +Network::Domain Translate(Domain domain); + +/// Translate abstract domain to guest domain +Domain Translate(Network::Domain domain); + +/// Translate guest type to abstract type +Network::Type Translate(Type type); + +/// Translate guest protocol to abstract protocol +Network::Protocol Translate(Type type, Protocol protocol); + +/// Translate abstract poll event flags to guest poll event flags +u16 TranslatePollEventsToHost(u16 flags); + +/// Translate guest poll event flags to abstract poll event flags +u16 TranslatePollEventsToGuest(u16 flags); + +/// Translate guest socket address structure to abstract socket address structure +Network::SockAddrIn Translate(SockAddrIn value); + +/// Translate abstract socket address structure to guest socket address structure +SockAddrIn Translate(Network::SockAddrIn value); + +/// Translate guest shutdown mode to abstract shutdown mode +Network::ShutdownHow Translate(ShutdownHow how); + +} // namespace Service::Sockets From 5692c48ab7103a2051f351e08fd012fc9022d951 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Tue, 21 Jul 2020 04:26:20 -0300 Subject: [PATCH 4/7] service/sockets: Add worker abstraction to execute blocking calls asynchronously This abstraction allows executing blocking functions (like recvfrom on a socket configured for blocking) without blocking the service thread. It is intended to be used with SleepClientThread. --- src/core/CMakeLists.txt | 1 + .../hle/service/sockets/blocking_worker.h | 132 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 src/core/hle/service/sockets/blocking_worker.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 48578ad48..b96ca9374 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -491,6 +491,7 @@ add_library(core STATIC hle/service/sm/controller.h hle/service/sm/sm.cpp hle/service/sm/sm.h + hle/service/sockets/blocking_worker.h hle/service/sockets/bsd.cpp hle/service/sockets/bsd.h hle/service/sockets/ethc.cpp diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h new file mode 100644 index 000000000..7bd486530 --- /dev/null +++ b/src/core/hle/service/sockets/blocking_worker.h @@ -0,0 +1,132 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/assert.h" +#include "common/microprofile.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/writable_event.h" + +namespace Service::Sockets { + +/** + * Worker abstraction to execute blocking calls on host without blocking the guest thread + * + * @tparam Service Service where the work is executed + * @tparam ...Types Types of work to execute + */ +template +class BlockingWorker { + using This = BlockingWorker; + using WorkVariant = std::variant; + +public: + /// Create a new worker + static std::unique_ptr Create(Core::System& system, Service* service, + std::string_view name) { + return std::unique_ptr(new This(system, service, name)); + } + + ~BlockingWorker() { + while (!is_available.load(std::memory_order_relaxed)) { + // Busy wait until work is finished + std::this_thread::yield(); + } + // Monostate means to exit the thread + work = std::monostate{}; + work_event.Set(); + thread.join(); + } + + /** + * Try to capture the worker to send work after a success + * @returns True when the worker has been successfully captured + */ + bool TryCapture() { + bool expected = true; + return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed, + std::memory_order_relaxed); + } + + /** + * Send work to this worker abstraction + * @see TryCapture must be called before attempting to call this function + */ + template + void SendWork(Work new_work) { + ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured"); + work = std::move(new_work); + work_event.Set(); + } + + /// Generate a callback for @see SleepClientThread + template + auto Callback() { + return [this](std::shared_ptr, Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + ASSERT(reason == Kernel::ThreadWakeupReason::Signal); + std::get(work).Response(ctx); + is_available.store(true); + }; + } + + /// Get kernel event that will be signalled by the worker when the host operation finishes + std::shared_ptr KernelEvent() const { + return kernel_event; + } + +private: + explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) { + auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name)); + kernel_event = std::move(pair.writable); + thread = std::thread([this, &system, service, name] { Run(system, service, name); }); + } + + void Run(Core::System& system, Service* service, std::string_view name) { + system.RegisterHostThread(); + + const std::string thread_name = fmt::format("yuzu:{}", name); + MicroProfileOnThreadCreate(thread_name.c_str()); + Common::SetCurrentThreadName(thread_name.c_str()); + + bool keep_running = true; + while (keep_running) { + work_event.Wait(); + + const auto visit_fn = [service, &keep_running](auto&& w) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + keep_running = false; + } else { + w.Execute(service); + } + }; + std::visit(visit_fn, work); + + kernel_event->Signal(); + } + } + + std::thread thread; + WorkVariant work; + Common::Event work_event; + std::shared_ptr kernel_event; + std::atomic_bool is_available{true}; +}; + +} // namespace Service::Sockets From 2c67bbf6091477a09d280c322539cbaf1b6e4a8e Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Tue, 21 Jul 2020 04:40:28 -0300 Subject: [PATCH 5/7] service/sockets: Add worker pool abstraction Manage worker threads with an easy to use abstraction. We can expand this to support thread deletion in the future. --- .../hle/service/sockets/blocking_worker.h | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h index 7bd486530..31ef6b821 100644 --- a/src/core/hle/service/sockets/blocking_worker.h +++ b/src/core/hle/service/sockets/blocking_worker.h @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -129,4 +130,33 @@ private: std::atomic_bool is_available{true}; }; +template +class BlockingWorkerPool { + using Worker = BlockingWorker; + +public: + explicit BlockingWorkerPool(Core::System& system_, Service* service_) + : system{system_}, service{service_} {} + + /// Returns a captured worker thread, creating new ones if necessary + Worker* CaptureWorker() { + for (auto& worker : workers) { + if (worker->TryCapture()) { + return worker.get(); + } + } + auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size())); + [[maybe_unused]] const bool success = new_worker->TryCapture(); + ASSERT(success); + + return workers.emplace_back(std::move(new_worker)).get(); + } + +private: + Core::System& system; + Service* const service; + + std::vector> workers; +}; + } // namespace Service::Sockets From f7d59f3e0e63a33e758b92a857ca4add0a8ee524 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Sat, 11 Jul 2020 22:37:47 -0300 Subject: [PATCH 6/7] services/bsd: Implement most of bsd:s This implements: Socket, Poll, Accept, Bind, Connect, GetPeerName, GetSockName, Listen, Fcntl, SetSockOpt, Shutdown, Recv, RecvFrom, Send, SendTo, Write, and Close The implementation was done referencing: SwIPC, switchbrew, testing with libnx and inspecting its code, general information about bsd sockets online, and analysing official software. Not everything from these service calls is implemented, but everything that is not implemented will be logged in some way. --- src/core/hle/service/service.cpp | 2 +- src/core/hle/service/sockets/bsd.cpp | 804 +++++++++++++++++++++-- src/core/hle/service/sockets/bsd.h | 148 ++++- src/core/hle/service/sockets/sockets.cpp | 6 +- src/core/hle/service/sockets/sockets.h | 6 +- 5 files changed, 911 insertions(+), 55 deletions(-) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index fa5347af9..538f28495 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -246,7 +246,7 @@ void Init(std::shared_ptr& sm, Core::System& system) { PSC::InstallInterfaces(*sm); PSM::InstallInterfaces(*sm); Set::InstallInterfaces(*sm); - Sockets::InstallInterfaces(*sm); + Sockets::InstallInterfaces(*sm, system); SPL::InstallInterfaces(*sm); SSL::InstallInterfaces(*sm); Time::InstallInterfaces(system); diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 8d4952c0e..1d20f33a4 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -2,18 +2,138 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include +#include +#include + +#include + +#include "common/microprofile.h" +#include "common/thread.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/thread.h" #include "core/hle/service/sockets/bsd.h" +#include "core/hle/service/sockets/sockets_translate.h" +#include "core/network/network.h" +#include "core/network/sockets.h" namespace Service::Sockets { +namespace { + +bool IsConnectionBased(Type type) { + switch (type) { + case Type::STREAM: + return true; + case Type::DGRAM: + return false; + default: + UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast(type)); + return false; + } +} + +} // Anonymous namespace + +void BSD::PollWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout); +} + +void BSD::PollWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(ret); + rb.PushEnum(bsd_errno); +} + +void BSD::AcceptWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer); +} + +void BSD::AcceptWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push(ret); + rb.PushEnum(bsd_errno); + rb.Push(static_cast(write_buffer.size())); +} + +void BSD::ConnectWork::Execute(BSD* bsd) { + bsd_errno = bsd->ConnectImpl(fd, addr); +} + +void BSD::ConnectWork::Response(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(bsd_errno == Errno::SUCCESS ? 0 : -1); + rb.PushEnum(bsd_errno); +} + +void BSD::RecvWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message); +} + +void BSD::RecvWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(message); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(ret); + rb.PushEnum(bsd_errno); +} + +void BSD::RecvFromWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr); +} + +void BSD::RecvFromWork::Response(Kernel::HLERequestContext& ctx) { + ctx.WriteBuffer(message, 0); + if (!addr.empty()) { + ctx.WriteBuffer(addr, 1); + } + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push(ret); + rb.PushEnum(bsd_errno); + rb.Push(static_cast(addr.size())); +} + +void BSD::SendWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message); +} + +void BSD::SendWork::Response(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(ret); + rb.PushEnum(bsd_errno); +} + +void BSD::SendToWork::Execute(BSD* bsd) { + std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr); +} + +void BSD::SendToWork::Response(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(ret); + rb.PushEnum(bsd_errno); +} + void BSD::RegisterClient(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(0); // bsd errno + rb.Push(0); // bsd errno } void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { @@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) { void BSD::Socket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + const u32 domain = rp.Pop(); + const u32 type = rp.Pop(); + const u32 protocol = rp.Pop(); - u32 domain = rp.Pop(); - u32 type = rp.Pop(); - u32 protocol = rp.Pop(); + LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); - LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol); - - u32 fd = next_fd++; + const auto [fd, bsd_errno] = SocketImpl(static_cast(domain), static_cast(type), + static_cast(protocol)); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push(fd); - rb.Push(0); // bsd errno + rb.Push(fd); + rb.PushEnum(bsd_errno); } void BSD::Select(Kernel::HLERequestContext& ctx) { @@ -52,67 +171,658 @@ void BSD::Select(Kernel::HLERequestContext& ctx) { rb.Push(0); // bsd errno } +void BSD::Poll(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 nfds = rp.Pop(); + const s32 timeout = rp.Pop(); + + LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout); + + ExecuteWork(ctx, "BSD:Poll", timeout != 0, + PollWork{ + .nfds = nfds, + .timeout = timeout, + .read_buffer = ctx.ReadBuffer(), + .write_buffer = std::vector(ctx.GetWriteBufferSize()), + }); +} + +void BSD::Accept(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={}", fd); + + ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd), + AcceptWork{ + .fd = fd, + .write_buffer = std::vector(ctx.GetWriteBufferSize()), + }); +} + void BSD::Bind(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); - IPC::ResponseBuilder rb{ctx, 4}; + LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); - rb.Push(RESULT_SUCCESS); - rb.Push(0); // ret - rb.Push(0); // bsd errno + BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer())); } void BSD::Connect(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); - IPC::ResponseBuilder rb{ctx, 4}; + LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); + ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd), + ConnectWork{ + .fd = fd, + .addr = ctx.ReadBuffer(), + }); +} + +void BSD::GetPeerName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={}", fd); + + std::vector write_buffer(ctx.GetWriteBufferSize()); + const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer); + + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; rb.Push(RESULT_SUCCESS); - rb.Push(0); // ret - rb.Push(0); // bsd errno + rb.Push(bsd_errno != Errno::SUCCESS ? -1 : 0); + rb.PushEnum(bsd_errno); + rb.Push(static_cast(write_buffer.size())); +} + +void BSD::GetSockName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={}", fd); + + std::vector write_buffer(ctx.GetWriteBufferSize()); + const Errno bsd_errno = GetSockNameImpl(fd, write_buffer); + + ctx.WriteBuffer(write_buffer); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(RESULT_SUCCESS); + rb.Push(bsd_errno != Errno::SUCCESS ? -1 : 0); + rb.PushEnum(bsd_errno); + rb.Push(static_cast(write_buffer.size())); } void BSD::Listen(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + const s32 backlog = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} backlog={}", fd, backlog); + + BuildErrnoResponse(ctx, ListenImpl(fd, backlog)); +} + +void BSD::Fcntl(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + const s32 cmd = rp.Pop(); + const s32 arg = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg); + + const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast(cmd), arg); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push(0); // ret - rb.Push(0); // bsd errno + rb.Push(ret); + rb.PushEnum(bsd_errno); } void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; - IPC::ResponseBuilder rb{ctx, 4}; + const s32 fd = rp.Pop(); + const u32 level = rp.Pop(); + const OptName optname = static_cast(rp.Pop()); - rb.Push(RESULT_SUCCESS); - rb.Push(0); // ret - rb.Push(0); // bsd errno + const std::vector buffer = ctx.ReadBuffer(); + const u8* optval = buffer.empty() ? nullptr : buffer.data(); + size_t optlen = buffer.size(); + + std::array values; + if ((optname == OptName::SNDTIMEO || optname == OptName::RCVTIMEO) && buffer.size() == 8) { + std::memcpy(values.data(), buffer.data(), sizeof(values)); + optlen = sizeof(values); + optval = reinterpret_cast(values.data()); + } + + LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level, + static_cast(optname), optlen); + + BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval)); +} + +void BSD::Shutdown(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop(); + const s32 how = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} how={}", fd, how); + + BuildErrnoResponse(ctx, ShutdownImpl(fd, how)); +} + +void BSD::Recv(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop(); + const u32 flags = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize()); + + ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd), + RecvWork{ + .fd = fd, + .flags = flags, + .message = std::vector(ctx.GetWriteBufferSize()), + }); +} + +void BSD::RecvFrom(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop(); + const u32 flags = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags, + ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1)); + + ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd), + RecvFromWork{ + .fd = fd, + .flags = flags, + .message = std::vector(ctx.GetWriteBufferSize(0)), + .addr = std::vector(ctx.GetWriteBufferSize(1)), + }); +} + +void BSD::Send(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const s32 fd = rp.Pop(); + const u32 flags = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize()); + + ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd), + SendWork{ + .fd = fd, + .flags = flags, + .message = ctx.ReadBuffer(), + }); } void BSD::SendTo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + const u32 flags = rp.Pop(); - IPC::ResponseBuilder rb{ctx, 4}; + LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags, + ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1)); - rb.Push(RESULT_SUCCESS); - rb.Push(0); // ret - rb.Push(0); // bsd errno + ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd), + SendToWork{ + .fd = fd, + .flags = flags, + .message = ctx.ReadBuffer(0), + .addr = ctx.ReadBuffer(1), + }); +} + +void BSD::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + + LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize()); + + ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd), + SendWork{ + .fd = fd, + .flags = 0, + .message = ctx.ReadBuffer(), + }); } void BSD::Close(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop(); + LOG_DEBUG(Service, "called. fd={}", fd); + + BuildErrnoResponse(ctx, CloseImpl(fd)); +} + +template +void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, + bool is_blocking, Work work) { + if (!is_blocking) { + work.Execute(this); + work.Response(ctx); + return; + } + + // Signal a dummy response to make IPC validation happy + // This will be overwritten by the SleepClientThread callback + work.Response(ctx); + + auto worker = worker_pool.CaptureWorker(); + + ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits::max(), + worker->Callback(), worker->KernelEvent()); + + worker->SendWork(std::move(work)); +} + +std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protocol) { + if (type == Type::SEQPACKET) { + UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management"); + } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) { + UNIMPLEMENTED_MSG("SOCK_RAW errno management"); + } + + [[maybe_unused]] const bool unk_flag = (static_cast(type) & 0x20000000) != 0; + 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}; + } + + FileDescriptor& descriptor = file_descriptors[fd].emplace(); + // ENONMEM might be thrown here + + LOG_INFO(Service, "New socket fd={}", fd); + + descriptor.socket = std::make_unique(); + descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol)); + descriptor.is_connection_based = IsConnectionBased(type); + + return {fd, Errno::SUCCESS}; +} + +std::pair BSD::PollImpl(std::vector& write_buffer, std::vector read_buffer, + s32 nfds, s32 timeout) { + if (write_buffer.size() < nfds * sizeof(PollFD)) { + return {-1, Errno::INVAL}; + } + + const size_t length = std::min(read_buffer.size(), write_buffer.size()); + std::vector fds(nfds); + std::memcpy(fds.data(), read_buffer.data(), length); + + if (timeout >= 0) { + const s64 seconds = timeout / 1000; + const u64 nanoseconds = 1'000'000 * (static_cast(timeout) % 1000); + + if (seconds < 0) { + return {-1, Errno::INVAL}; + } + if (nanoseconds > 999'999'999) { + return {-1, Errno::INVAL}; + } + } else if (timeout != -1) { + return {-1, Errno::INVAL}; + } + + for (PollFD& pollfd : fds) { + ASSERT(pollfd.revents == 0); + + if (pollfd.fd > MAX_FD || pollfd.fd < 0) { + LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); + pollfd.revents = 0; + return {0, Errno::SUCCESS}; + } + + std::optional& descriptor = file_descriptors[pollfd.fd]; + if (!descriptor) { + LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd); + pollfd.revents = POLL_NVAL; + return {0, Errno::SUCCESS}; + } + } + + std::vector host_pollfds(fds.size()); + std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) { + Network::PollFD result; + result.socket = file_descriptors[pollfd.fd]->socket.get(); + result.events = TranslatePollEventsToHost(pollfd.events); + result.revents = 0; + return result; + }); + + const auto result = Network::Poll(host_pollfds, timeout); + + const size_t num = host_pollfds.size(); + for (size_t i = 0; i < num; ++i) { + fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents); + } + std::memcpy(write_buffer.data(), fds.data(), length); + + return Translate(result); +} + +std::pair BSD::AcceptImpl(s32 fd, std::vector& write_buffer) { + if (!IsFileDescriptorValid(fd)) { + 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(); + if (bsd_errno != Network::Errno::SUCCESS) { + return {-1, Translate(bsd_errno)}; + } + + FileDescriptor& new_descriptor = file_descriptors[new_fd].emplace(); + new_descriptor.socket = std::move(result.socket); + new_descriptor.is_connection_based = descriptor.is_connection_based; + + ASSERT(write_buffer.size() == sizeof(SockAddrIn)); + const SockAddrIn guest_addr_in = Translate(result.sockaddr_in); + std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in)); + + return {new_fd, Errno::SUCCESS}; +} + +Errno BSD::BindImpl(s32 fd, const std::vector& addr) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + ASSERT(addr.size() == sizeof(SockAddrIn)); + SockAddrIn addr_in; + std::memcpy(&addr_in, addr.data(), sizeof(addr_in)); + + return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in))); +} + +Errno BSD::ConnectImpl(s32 fd, const std::vector& addr) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn)); + SockAddrIn addr_in; + std::memcpy(&addr_in, addr.data(), sizeof(addr_in)); + + return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); +} + +Errno BSD::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName(); + if (bsd_errno != Network::Errno::SUCCESS) { + return Translate(bsd_errno); + } + const SockAddrIn guest_addrin = Translate(addr_in); + + ASSERT(write_buffer.size() == sizeof(guest_addrin)); + std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); + return Translate(bsd_errno); +} + +Errno BSD::GetSockNameImpl(s32 fd, std::vector& write_buffer) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName(); + if (bsd_errno != Network::Errno::SUCCESS) { + return Translate(bsd_errno); + } + const SockAddrIn guest_addrin = Translate(addr_in); + + ASSERT(write_buffer.size() == sizeof(guest_addrin)); + std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin)); + return Translate(bsd_errno); +} + +Errno BSD::ListenImpl(s32 fd, s32 backlog) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + return Translate(file_descriptors[fd]->socket->Listen(backlog)); +} + +std::pair BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + FileDescriptor& descriptor = *file_descriptors[fd]; + + switch (cmd) { + case FcntlCmd::GETFL: + ASSERT(arg == 0); + return {descriptor.flags, Errno::SUCCESS}; + case FcntlCmd::SETFL: { + const bool enable = (arg & FLAG_O_NONBLOCK) != 0; + const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable)); + if (bsd_errno != Errno::SUCCESS) { + return {-1, bsd_errno}; + } + descriptor.flags = arg; + return {0, Errno::SUCCESS}; + } + default: + UNIMPLEMENTED_MSG("Unimplemented cmd={}", static_cast(cmd)); + return {-1, Errno::SUCCESS}; + } +} + +Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { + UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET + + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + Network::Socket* const socket = file_descriptors[fd]->socket.get(); + + if (optname == OptName::LINGER) { + ASSERT(optlen == sizeof(Linger)); + Linger linger; + std::memcpy(&linger, optval, sizeof(linger)); + ASSERT(linger.onoff == 0 || linger.onoff == 1); + + return Translate(socket->SetLinger(linger.onoff != 0, linger.linger)); + } + + ASSERT(optlen == sizeof(u32)); + u32 value; + std::memcpy(&value, optval, sizeof(value)); + + switch (optname) { + case OptName::REUSEADDR: + ASSERT(value == 0 || value == 1); + return Translate(socket->SetReuseAddr(value != 0)); + case OptName::BROADCAST: + ASSERT(value == 0 || value == 1); + return Translate(socket->SetBroadcast(value != 0)); + case OptName::SNDBUF: + return Translate(socket->SetSndBuf(value)); + case OptName::RCVBUF: + return Translate(socket->SetRcvBuf(value)); + case OptName::SNDTIMEO: + return Translate(socket->SetSndTimeo(value)); + case OptName::RCVTIMEO: + return Translate(socket->SetRcvTimeo(value)); + default: + UNIMPLEMENTED_MSG("Unimplemented optname={}", static_cast(optname)); + return Errno::SUCCESS; + } +} + +Errno BSD::ShutdownImpl(s32 fd, s32 how) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + const Network::ShutdownHow host_how = Translate(static_cast(how)); + return Translate(file_descriptors[fd]->socket->Shutdown(host_how)); +} + +std::pair BSD::RecvImpl(s32 fd, u32 flags, std::vector& message) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + return Translate(file_descriptors[fd]->socket->Recv(flags, message)); +} + +std::pair BSD::RecvFromImpl(s32 fd, u32 flags, std::vector& message, + std::vector& addr) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + FileDescriptor& descriptor = *file_descriptors[fd]; + + Network::SockAddrIn addr_in{}; + Network::SockAddrIn* p_addr_in = nullptr; + if (descriptor.is_connection_based) { + // Connection based file descriptors (e.g. TCP) zero addr + addr.clear(); + } else { + p_addr_in = &addr_in; + } + + // Apply flags + if ((flags & FLAG_MSG_DONTWAIT) != 0) { + flags &= ~FLAG_MSG_DONTWAIT; + 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)); + + // Restore original state + if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { + descriptor.socket->SetNonBlock(false); + } + + if (p_addr_in) { + if (ret < 0) { + addr.clear(); + } else { + ASSERT(addr.size() == sizeof(SockAddrIn)); + const SockAddrIn result = Translate(addr_in); + std::memcpy(addr.data(), &result, sizeof(result)); + } + } + + return {ret, bsd_errno}; +} + +std::pair BSD::SendImpl(s32 fd, u32 flags, const std::vector& message) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + return Translate(file_descriptors[fd]->socket->Send(message, flags)); +} + +std::pair BSD::SendToImpl(s32 fd, u32 flags, const std::vector& message, + const std::vector& addr) { + if (!IsFileDescriptorValid(fd)) { + return {-1, Errno::BADF}; + } + + Network::SockAddrIn addr_in; + Network::SockAddrIn* p_addr_in = nullptr; + if (!addr.empty()) { + ASSERT(addr.size() == sizeof(SockAddrIn)); + SockAddrIn guest_addr_in; + std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in)); + addr_in = Translate(guest_addr_in); + } + + return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in)); +} + +Errno BSD::CloseImpl(s32 fd) { + if (!IsFileDescriptorValid(fd)) { + return Errno::BADF; + } + + const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); + if (bsd_errno != Errno::SUCCESS) { + return bsd_errno; + } + + LOG_INFO(Service, "Close socket fd={}", fd); + + file_descriptors[fd].reset(); + return bsd_errno; +} + +s32 BSD::FindFreeFileDescriptorHandle() noexcept { + for (s32 fd = 0; fd < static_cast(file_descriptors.size()); ++fd) { + if (!file_descriptors[fd]) { + return fd; + } + } + return -1; +} + +bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { + if (fd > MAX_FD || fd < 0) { + LOG_ERROR(Service, "Invalid file descriptor handle={}", fd); + return false; + } + if (!file_descriptors[fd]) { + LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd); + return false; + } + return true; +} + +bool BSD::IsBlockingSocket(s32 fd) const noexcept { + // Inform invalid sockets as non-blocking + // This way we avoid using a worker thread as it will fail without blocking host + if (fd > MAX_FD || fd < 0) { + return false; + } + if (!file_descriptors[fd]) { + return false; + } + return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0; +} + +void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(0); // ret - rb.Push(0); // bsd errno + rb.Push(bsd_errno == Errno::SUCCESS ? 0 : -1); + rb.PushEnum(bsd_errno); } -BSD::BSD(const char* name) : ServiceFramework(name) { +BSD::BSD(Core::System& system, const char* name) + : ServiceFramework(name), worker_pool{system, this} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, @@ -121,25 +831,25 @@ BSD::BSD(const char* name) : ServiceFramework(name) { {3, nullptr, "SocketExempt"}, {4, nullptr, "Open"}, {5, &BSD::Select, "Select"}, - {6, nullptr, "Poll"}, + {6, &BSD::Poll, "Poll"}, {7, nullptr, "Sysctl"}, - {8, nullptr, "Recv"}, - {9, nullptr, "RecvFrom"}, - {10, nullptr, "Send"}, + {8, &BSD::Recv, "Recv"}, + {9, &BSD::RecvFrom, "RecvFrom"}, + {10, &BSD::Send, "Send"}, {11, &BSD::SendTo, "SendTo"}, - {12, nullptr, "Accept"}, + {12, &BSD::Accept, "Accept"}, {13, &BSD::Bind, "Bind"}, {14, &BSD::Connect, "Connect"}, - {15, nullptr, "GetPeerName"}, - {16, nullptr, "GetSockName"}, + {15, &BSD::GetPeerName, "GetPeerName"}, + {16, &BSD::GetSockName, "GetSockName"}, {17, nullptr, "GetSockOpt"}, {18, &BSD::Listen, "Listen"}, {19, nullptr, "Ioctl"}, - {20, nullptr, "Fcntl"}, + {20, &BSD::Fcntl, "Fcntl"}, {21, &BSD::SetSockOpt, "SetSockOpt"}, - {22, nullptr, "Shutdown"}, + {22, &BSD::Shutdown, "Shutdown"}, {23, nullptr, "ShutdownAllSockets"}, - {24, nullptr, "Write"}, + {24, &BSD::Write, "Write"}, {25, nullptr, "Read"}, {26, &BSD::Close, "Close"}, {27, nullptr, "DuplicateSocket"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 7e1a64015..357531951 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -4,32 +4,174 @@ #pragma once +#include +#include +#include + #include "common/common_types.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +#include "core/hle/service/sockets/blocking_worker.h" #include "core/hle/service/sockets/sockets.h" +namespace Core { +class System; +} + +namespace Network { +class Socket; +} + namespace Service::Sockets { class BSD final : public ServiceFramework { public: - explicit BSD(const char* name); + explicit BSD(Core::System& system, const char* name); ~BSD() override; private: + /// Maximum number of file descriptors + static constexpr size_t MAX_FD = 128; + + struct FileDescriptor { + std::unique_ptr socket; + s32 flags = 0; + bool is_connection_based = false; + }; + + struct PollWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 nfds; + s32 timeout; + std::vector read_buffer; + std::vector write_buffer; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct AcceptWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + std::vector write_buffer; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct ConnectWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + std::vector addr; + Errno bsd_errno{}; + }; + + struct RecvWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector message; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct RecvFromWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector message; + std::vector addr; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct SendWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector message; + s32 ret{}; + Errno bsd_errno{}; + }; + + struct SendToWork { + void Execute(BSD* bsd); + void Response(Kernel::HLERequestContext& ctx); + + s32 fd; + u32 flags; + std::vector message; + std::vector addr; + s32 ret{}; + Errno bsd_errno{}; + }; + void RegisterClient(Kernel::HLERequestContext& ctx); void StartMonitoring(Kernel::HLERequestContext& ctx); void Socket(Kernel::HLERequestContext& ctx); void Select(Kernel::HLERequestContext& ctx); + void Poll(Kernel::HLERequestContext& ctx); + void Accept(Kernel::HLERequestContext& ctx); void Bind(Kernel::HLERequestContext& ctx); void Connect(Kernel::HLERequestContext& ctx); + void GetPeerName(Kernel::HLERequestContext& ctx); + void GetSockName(Kernel::HLERequestContext& ctx); void Listen(Kernel::HLERequestContext& ctx); + void Fcntl(Kernel::HLERequestContext& ctx); void SetSockOpt(Kernel::HLERequestContext& ctx); + void Shutdown(Kernel::HLERequestContext& ctx); + void Recv(Kernel::HLERequestContext& ctx); + void RecvFrom(Kernel::HLERequestContext& ctx); + void Send(Kernel::HLERequestContext& ctx); void SendTo(Kernel::HLERequestContext& ctx); + void Write(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); - /// Id to use for the next open file descriptor. - u32 next_fd = 1; + template + void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, + bool is_blocking, Work work); + + std::pair SocketImpl(Domain domain, Type type, Protocol protocol); + std::pair PollImpl(std::vector& write_buffer, std::vector read_buffer, + s32 nfds, s32 timeout); + std::pair AcceptImpl(s32 fd, std::vector& write_buffer); + Errno BindImpl(s32 fd, const std::vector& addr); + Errno ConnectImpl(s32 fd, const std::vector& addr); + Errno GetPeerNameImpl(s32 fd, std::vector& write_buffer); + Errno GetSockNameImpl(s32 fd, std::vector& write_buffer); + Errno ListenImpl(s32 fd, s32 backlog); + std::pair FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg); + Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval); + Errno ShutdownImpl(s32 fd, s32 how); + std::pair RecvImpl(s32 fd, u32 flags, std::vector& message); + std::pair RecvFromImpl(s32 fd, u32 flags, std::vector& message, + std::vector& addr); + std::pair SendImpl(s32 fd, u32 flags, const std::vector& message); + std::pair SendToImpl(s32 fd, u32 flags, const std::vector& message, + const std::vector& addr); + Errno CloseImpl(s32 fd); + + s32 FindFreeFileDescriptorHandle() noexcept; + bool IsFileDescriptorValid(s32 fd) const noexcept; + bool IsBlockingSocket(s32 fd) const noexcept; + + void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; + + std::array, MAX_FD> file_descriptors; + + BlockingWorkerPool + worker_pool; }; class BSDCFG final : public ServiceFramework { diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index 08d2d306a..1d27f7906 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -10,9 +10,9 @@ namespace Service::Sockets { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared("bsd:s")->InstallAsService(service_manager); - std::make_shared("bsd:u")->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared(system, "bsd:s")->InstallAsService(service_manager); + std::make_shared(system, "bsd:u")->InstallAsService(service_manager); std::make_shared()->InstallAsService(service_manager); std::make_shared()->InstallAsService(service_manager); diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 55b110937..89a410076 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -7,6 +7,10 @@ #include "common/common_types.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { enum class Errno : u32 { @@ -88,6 +92,6 @@ constexpr u32 FLAG_MSG_DONTWAIT = 0x80; constexpr u32 FLAG_O_NONBLOCK = 0x800; /// Registers all Sockets services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Sockets From bc699ace15fc2dd2cc7ff022fa9ec6031ba09d13 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Tue, 28 Jul 2020 01:51:44 -0300 Subject: [PATCH 7/7] service/bsd: Handle Poll with no entries accurately Testing shows that Poll called with zero entries returns -1 and signals an errno of zero. --- src/core/hle/service/sockets/bsd.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 1d20f33a4..803505452 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -465,6 +465,11 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::vector fds(nfds); std::memcpy(fds.data(), read_buffer.data(), length);