Ver código fonte

Merge pull request #4397 from ReinUsesLisp/bsd

 services: Implement most of bsd:s and GetCurrentIpAddress from nifm
bunnei 5 anos atrás
pai
commit
e126021ffe

+ 3 - 0
src/core/CMakeLists.txt

@@ -491,6 +491,7 @@ add_library(core STATIC
     hle/service/sm/controller.h
     hle/service/sm/controller.h
     hle/service/sm/sm.cpp
     hle/service/sm/sm.cpp
     hle/service/sm/sm.h
     hle/service/sm/sm.h
+    hle/service/sockets/blocking_worker.h
     hle/service/sockets/bsd.cpp
     hle/service/sockets/bsd.cpp
     hle/service/sockets/bsd.h
     hle/service/sockets/bsd.h
     hle/service/sockets/ethc.cpp
     hle/service/sockets/ethc.cpp
@@ -501,6 +502,8 @@ add_library(core STATIC
     hle/service/sockets/sfdnsres.h
     hle/service/sockets/sfdnsres.h
     hle/service/sockets/sockets.cpp
     hle/service/sockets/sockets.cpp
     hle/service/sockets/sockets.h
     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.cpp
     hle/service/spl/csrng.h
     hle/service/spl/csrng.h
     hle/service/spl/module.cpp
     hle/service/spl/module.cpp

+ 12 - 1
src/core/hle/service/nifm/nifm.cpp

@@ -9,6 +9,7 @@
 #include "core/hle/kernel/writable_event.h"
 #include "core/hle/kernel/writable_event.h"
 #include "core/hle/service/nifm/nifm.h"
 #include "core/hle/service/nifm/nifm.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
+#include "core/network/network.h"
 #include "core/settings.h"
 #include "core/settings.h"
 
 
 namespace Service::NIFM {
 namespace Service::NIFM {
@@ -174,6 +175,16 @@ private:
         IPC::ResponseBuilder rb{ctx, 2};
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(RESULT_SUCCESS);
         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) {
     void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
         LOG_DEBUG(Service_NIFM, "called");
         LOG_DEBUG(Service_NIFM, "called");
 
 
@@ -235,7 +246,7 @@ IGeneralService::IGeneralService(Core::System& system)
         {9, nullptr, "SetNetworkProfile"},
         {9, nullptr, "SetNetworkProfile"},
         {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
         {10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
         {11, nullptr, "GetScanDataOld"},
         {11, nullptr, "GetScanDataOld"},
-        {12, nullptr, "GetCurrentIpAddress"},
+        {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"},
         {13, nullptr, "GetCurrentAccessPointOld"},
         {13, nullptr, "GetCurrentAccessPointOld"},
         {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
         {14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
         {15, nullptr, "GetCurrentIpConfigInfo"},
         {15, nullptr, "GetCurrentIpConfigInfo"},

+ 1 - 1
src/core/hle/service/service.cpp

@@ -246,7 +246,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
     PSC::InstallInterfaces(*sm);
     PSC::InstallInterfaces(*sm);
     PSM::InstallInterfaces(*sm);
     PSM::InstallInterfaces(*sm);
     Set::InstallInterfaces(*sm);
     Set::InstallInterfaces(*sm);
-    Sockets::InstallInterfaces(*sm);
+    Sockets::InstallInterfaces(*sm, system);
     SPL::InstallInterfaces(*sm);
     SPL::InstallInterfaces(*sm);
     SSL::InstallInterfaces(*sm);
     SSL::InstallInterfaces(*sm);
     Time::InstallInterfaces(system);
     Time::InstallInterfaces(system);

+ 162 - 0
src/core/hle/service/sockets/blocking_worker.h

@@ -0,0 +1,162 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <variant>
+#include <vector>
+
+#include <fmt/format.h>
+
+#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 Service, class... Types>
+class BlockingWorker {
+    using This = BlockingWorker<Service, Types...>;
+    using WorkVariant = std::variant<std::monostate, Types...>;
+
+public:
+    /// Create a new worker
+    static std::unique_ptr<This> Create(Core::System& system, Service* service,
+                                        std::string_view name) {
+        return std::unique_ptr<This>(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 <class Work>
+    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 <class Work>
+    auto Callback() {
+        return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx,
+                      Kernel::ThreadWakeupReason reason) {
+            ASSERT(reason == Kernel::ThreadWakeupReason::Signal);
+            std::get<Work>(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<Kernel::WritableEvent> 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<decltype(w)>;
+                if constexpr (std::is_same_v<T, std::monostate>) {
+                    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::WritableEvent> kernel_event;
+    std::atomic_bool is_available{true};
+};
+
+template <class Service, class... Types>
+class BlockingWorkerPool {
+    using Worker = BlockingWorker<Service, Types...>;
+
+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<std::unique_ptr<Worker>> workers;
+};
+
+} // namespace Service::Sockets

+ 762 - 47
src/core/hle/service/sockets/bsd.cpp

@@ -2,18 +2,138 @@
 // Licensed under GPLv2 or any later version
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/microprofile.h"
+#include "common/thread.h"
 #include "core/hle/ipc_helpers.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/bsd.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+#include "core/network/sockets.h"
 
 
 namespace Service::Sockets {
 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<int>(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<s32>(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<s32>(ret);
+    rb.PushEnum(bsd_errno);
+    rb.Push<u32>(static_cast<u32>(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<s32>(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<s32>(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<s32>(ret);
+    rb.PushEnum(bsd_errno);
+    rb.Push<u32>(static_cast<u32>(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<s32>(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<s32>(ret);
+    rb.PushEnum(bsd_errno);
+}
+
 void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
 void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
     LOG_WARNING(Service, "(STUBBED) called");
     LOG_WARNING(Service, "(STUBBED) called");
 
 
     IPC::ResponseBuilder rb{ctx, 3};
     IPC::ResponseBuilder rb{ctx, 3};
 
 
     rb.Push(RESULT_SUCCESS);
     rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(0); // bsd errno
+    rb.Push<s32>(0); // bsd errno
 }
 }
 
 
 void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
 void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
@@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
 
 
 void BSD::Socket(Kernel::HLERequestContext& ctx) {
 void BSD::Socket(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     IPC::RequestParser rp{ctx};
+    const u32 domain = rp.Pop<u32>();
+    const u32 type = rp.Pop<u32>();
+    const u32 protocol = rp.Pop<u32>();
 
 
-    u32 domain = rp.Pop<u32>();
-    u32 type = rp.Pop<u32>();
-    u32 protocol = rp.Pop<u32>();
-
-    LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol);
+    LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
 
 
-    u32 fd = next_fd++;
+    const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
+                                            static_cast<Protocol>(protocol));
 
 
     IPC::ResponseBuilder rb{ctx, 4};
     IPC::ResponseBuilder rb{ctx, 4};
-
     rb.Push(RESULT_SUCCESS);
     rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(fd);
-    rb.Push<u32>(0); // bsd errno
+    rb.Push<s32>(fd);
+    rb.PushEnum(bsd_errno);
 }
 }
 
 
 void BSD::Select(Kernel::HLERequestContext& ctx) {
 void BSD::Select(Kernel::HLERequestContext& ctx) {
@@ -52,67 +171,663 @@ void BSD::Select(Kernel::HLERequestContext& ctx) {
     rb.Push<u32>(0); // bsd errno
     rb.Push<u32>(0); // bsd errno
 }
 }
 
 
+void BSD::Poll(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const s32 nfds = rp.Pop<s32>();
+    const s32 timeout = rp.Pop<s32>();
+
+    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<u8>(ctx.GetWriteBufferSize()),
+                });
+}
+
+void BSD::Accept(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
+
+    LOG_DEBUG(Service, "called. fd={}", fd);
+
+    ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd),
+                AcceptWork{
+                    .fd = fd,
+                    .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+                });
+}
+
 void BSD::Bind(Kernel::HLERequestContext& ctx) {
 void BSD::Bind(Kernel::HLERequestContext& ctx) {
-    LOG_WARNING(Service, "(STUBBED) called");
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
 
 
-    IPC::ResponseBuilder rb{ctx, 4};
+    LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
 
 
-    rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(0); // ret
-    rb.Push<u32>(0); // bsd errno
+    BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer()));
 }
 }
 
 
 void BSD::Connect(Kernel::HLERequestContext& ctx) {
 void BSD::Connect(Kernel::HLERequestContext& ctx) {
-    LOG_WARNING(Service, "(STUBBED) called");
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
 
 
-    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<s32>();
+
+    LOG_DEBUG(Service, "called. fd={}", fd);
 
 
+    std::vector<u8> 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(RESULT_SUCCESS);
-    rb.Push<u32>(0); // ret
-    rb.Push<u32>(0); // bsd errno
+    rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+    rb.PushEnum(bsd_errno);
+    rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::GetSockName(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
+
+    LOG_DEBUG(Service, "called. fd={}", fd);
+
+    std::vector<u8> 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<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+    rb.PushEnum(bsd_errno);
+    rb.Push<u32>(static_cast<u32>(write_buffer.size()));
 }
 }
 
 
 void BSD::Listen(Kernel::HLERequestContext& ctx) {
 void BSD::Listen(Kernel::HLERequestContext& ctx) {
-    LOG_WARNING(Service, "(STUBBED) called");
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
+    const s32 backlog = rp.Pop<s32>();
 
 
-    IPC::ResponseBuilder rb{ctx, 4};
+    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<s32>();
+    const s32 cmd = rp.Pop<s32>();
+    const s32 arg = rp.Pop<s32>();
 
 
+    LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
+
+    const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
+
+    IPC::ResponseBuilder rb{ctx, 4};
     rb.Push(RESULT_SUCCESS);
     rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(0); // ret
-    rb.Push<u32>(0); // bsd errno
+    rb.Push<s32>(ret);
+    rb.PushEnum(bsd_errno);
 }
 }
 
 
 void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
 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<s32>();
+    const u32 level = rp.Pop<u32>();
+    const OptName optname = static_cast<OptName>(rp.Pop<u32>());
 
 
-    rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(0); // ret
-    rb.Push<u32>(0); // bsd errno
+    const std::vector<u8> buffer = ctx.ReadBuffer();
+    const u8* optval = buffer.empty() ? nullptr : buffer.data();
+    size_t optlen = buffer.size();
+
+    std::array<u64, 2> 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<const u8*>(values.data());
+    }
+
+    LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
+              static_cast<u32>(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<s32>();
+    const s32 how = rp.Pop<s32>();
+
+    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<s32>();
+    const u32 flags = rp.Pop<u32>();
+
+    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<u8>(ctx.GetWriteBufferSize()),
+                });
+}
+
+void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const s32 fd = rp.Pop<s32>();
+    const u32 flags = rp.Pop<u32>();
+
+    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<u8>(ctx.GetWriteBufferSize(0)),
+                    .addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
+                });
+}
+
+void BSD::Send(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+
+    const s32 fd = rp.Pop<s32>();
+    const u32 flags = rp.Pop<u32>();
+
+    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) {
 void BSD::SendTo(Kernel::HLERequestContext& ctx) {
-    LOG_WARNING(Service, "(STUBBED) called");
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
+    const u32 flags = rp.Pop<u32>();
+
+    LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
+              ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
+
+    ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd),
+                SendToWork{
+                    .fd = fd,
+                    .flags = flags,
+                    .message = ctx.ReadBuffer(0),
+                    .addr = ctx.ReadBuffer(1),
+                });
+}
 
 
-    IPC::ResponseBuilder rb{ctx, 4};
+void BSD::Write(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
 
 
-    rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(0); // ret
-    rb.Push<u32>(0); // bsd errno
+    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) {
 void BSD::Close(Kernel::HLERequestContext& ctx) {
-    LOG_WARNING(Service, "(STUBBED) called");
+    IPC::RequestParser rp{ctx};
+    const s32 fd = rp.Pop<s32>();
+
+    LOG_DEBUG(Service, "called. fd={}", fd);
+
+    BuildErrnoResponse(ctx, CloseImpl(fd));
+}
+
+template <typename Work>
+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<u64>::max(),
+                          worker->Callback<Work>(), worker->KernelEvent());
+
+    worker->SendWork(std::move(work));
+}
+
+std::pair<s32, Errno> 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<u32>(type) & 0x20000000) != 0;
+    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};
+    }
+
+    FileDescriptor& descriptor = file_descriptors[fd].emplace();
+    // ENONMEM might be thrown here
+
+    LOG_INFO(Service, "New socket fd={}", fd);
+
+    descriptor.socket = std::make_unique<Network::Socket>();
+    descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
+    descriptor.is_connection_based = IsConnectionBased(type);
+
+    return {fd, Errno::SUCCESS};
+}
 
 
+std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+                                    s32 nfds, s32 timeout) {
+    if (write_buffer.size() < nfds * sizeof(PollFD)) {
+        return {-1, Errno::INVAL};
+    }
+
+    if (nfds == 0) {
+        // When no entries are provided, -1 is returned with errno zero
+        return {-1, Errno::SUCCESS};
+    }
+
+    const size_t length = std::min(read_buffer.size(), write_buffer.size());
+    std::vector<PollFD> 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<u64>(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<FileDescriptor>& 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<Network::PollFD> 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<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& 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<u8>& 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<u8>& 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<u8>& 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<u8>& 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<s32, Errno> 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<int>(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<int>(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<ShutdownHow>(how));
+    return Translate(file_descriptors[fd]->socket->Shutdown(host_how));
+}
+
+std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) {
+    if (!IsFileDescriptorValid(fd)) {
+        return {-1, Errno::BADF};
+    }
+    return Translate(file_descriptors[fd]->socket->Recv(flags, message));
+}
+
+std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+                                        std::vector<u8>& 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<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) {
+    if (!IsFileDescriptorValid(fd)) {
+        return {-1, Errno::BADF};
+    }
+    return Translate(file_descriptors[fd]->socket->Send(message, flags));
+}
+
+std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+                                      const std::vector<u8>& 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<s32>(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};
     IPC::ResponseBuilder rb{ctx, 4};
 
 
     rb.Push(RESULT_SUCCESS);
     rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(0); // ret
-    rb.Push<u32>(0); // bsd errno
+    rb.Push<s32>(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
     // clang-format off
     static const FunctionInfo functions[] = {
     static const FunctionInfo functions[] = {
         {0, &BSD::RegisterClient, "RegisterClient"},
         {0, &BSD::RegisterClient, "RegisterClient"},
@@ -121,25 +836,25 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
         {3, nullptr, "SocketExempt"},
         {3, nullptr, "SocketExempt"},
         {4, nullptr, "Open"},
         {4, nullptr, "Open"},
         {5, &BSD::Select, "Select"},
         {5, &BSD::Select, "Select"},
-        {6, nullptr, "Poll"},
+        {6, &BSD::Poll, "Poll"},
         {7, nullptr, "Sysctl"},
         {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"},
         {11, &BSD::SendTo, "SendTo"},
-        {12, nullptr, "Accept"},
+        {12, &BSD::Accept, "Accept"},
         {13, &BSD::Bind, "Bind"},
         {13, &BSD::Bind, "Bind"},
         {14, &BSD::Connect, "Connect"},
         {14, &BSD::Connect, "Connect"},
-        {15, nullptr, "GetPeerName"},
-        {16, nullptr, "GetSockName"},
+        {15, &BSD::GetPeerName, "GetPeerName"},
+        {16, &BSD::GetSockName, "GetSockName"},
         {17, nullptr, "GetSockOpt"},
         {17, nullptr, "GetSockOpt"},
         {18, &BSD::Listen, "Listen"},
         {18, &BSD::Listen, "Listen"},
         {19, nullptr, "Ioctl"},
         {19, nullptr, "Ioctl"},
-        {20, nullptr, "Fcntl"},
+        {20, &BSD::Fcntl, "Fcntl"},
         {21, &BSD::SetSockOpt, "SetSockOpt"},
         {21, &BSD::SetSockOpt, "SetSockOpt"},
-        {22, nullptr, "Shutdown"},
+        {22, &BSD::Shutdown, "Shutdown"},
         {23, nullptr, "ShutdownAllSockets"},
         {23, nullptr, "ShutdownAllSockets"},
-        {24, nullptr, "Write"},
+        {24, &BSD::Write, "Write"},
         {25, nullptr, "Read"},
         {25, nullptr, "Read"},
         {26, &BSD::Close, "Close"},
         {26, &BSD::Close, "Close"},
         {27, nullptr, "DuplicateSocket"},
         {27, nullptr, "DuplicateSocket"},

+ 147 - 3
src/core/hle/service/sockets/bsd.h

@@ -4,30 +4,174 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "common/common_types.h"
 #include "core/hle/kernel/hle_ipc.h"
 #include "core/hle/kernel/hle_ipc.h"
 #include "core/hle/service/service.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 {
 namespace Service::Sockets {
 
 
 class BSD final : public ServiceFramework<BSD> {
 class BSD final : public ServiceFramework<BSD> {
 public:
 public:
-    explicit BSD(const char* name);
+    explicit BSD(Core::System& system, const char* name);
     ~BSD() override;
     ~BSD() override;
 
 
 private:
 private:
+    /// Maximum number of file descriptors
+    static constexpr size_t MAX_FD = 128;
+
+    struct FileDescriptor {
+        std::unique_ptr<Network::Socket> 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<u8> read_buffer;
+        std::vector<u8> write_buffer;
+        s32 ret{};
+        Errno bsd_errno{};
+    };
+
+    struct AcceptWork {
+        void Execute(BSD* bsd);
+        void Response(Kernel::HLERequestContext& ctx);
+
+        s32 fd;
+        std::vector<u8> write_buffer;
+        s32 ret{};
+        Errno bsd_errno{};
+    };
+
+    struct ConnectWork {
+        void Execute(BSD* bsd);
+        void Response(Kernel::HLERequestContext& ctx);
+
+        s32 fd;
+        std::vector<u8> addr;
+        Errno bsd_errno{};
+    };
+
+    struct RecvWork {
+        void Execute(BSD* bsd);
+        void Response(Kernel::HLERequestContext& ctx);
+
+        s32 fd;
+        u32 flags;
+        std::vector<u8> message;
+        s32 ret{};
+        Errno bsd_errno{};
+    };
+
+    struct RecvFromWork {
+        void Execute(BSD* bsd);
+        void Response(Kernel::HLERequestContext& ctx);
+
+        s32 fd;
+        u32 flags;
+        std::vector<u8> message;
+        std::vector<u8> addr;
+        s32 ret{};
+        Errno bsd_errno{};
+    };
+
+    struct SendWork {
+        void Execute(BSD* bsd);
+        void Response(Kernel::HLERequestContext& ctx);
+
+        s32 fd;
+        u32 flags;
+        std::vector<u8> message;
+        s32 ret{};
+        Errno bsd_errno{};
+    };
+
+    struct SendToWork {
+        void Execute(BSD* bsd);
+        void Response(Kernel::HLERequestContext& ctx);
+
+        s32 fd;
+        u32 flags;
+        std::vector<u8> message;
+        std::vector<u8> addr;
+        s32 ret{};
+        Errno bsd_errno{};
+    };
+
     void RegisterClient(Kernel::HLERequestContext& ctx);
     void RegisterClient(Kernel::HLERequestContext& ctx);
     void StartMonitoring(Kernel::HLERequestContext& ctx);
     void StartMonitoring(Kernel::HLERequestContext& ctx);
     void Socket(Kernel::HLERequestContext& ctx);
     void Socket(Kernel::HLERequestContext& ctx);
     void Select(Kernel::HLERequestContext& ctx);
     void Select(Kernel::HLERequestContext& ctx);
+    void Poll(Kernel::HLERequestContext& ctx);
+    void Accept(Kernel::HLERequestContext& ctx);
     void Bind(Kernel::HLERequestContext& ctx);
     void Bind(Kernel::HLERequestContext& ctx);
     void Connect(Kernel::HLERequestContext& ctx);
     void Connect(Kernel::HLERequestContext& ctx);
+    void GetPeerName(Kernel::HLERequestContext& ctx);
+    void GetSockName(Kernel::HLERequestContext& ctx);
     void Listen(Kernel::HLERequestContext& ctx);
     void Listen(Kernel::HLERequestContext& ctx);
+    void Fcntl(Kernel::HLERequestContext& ctx);
     void SetSockOpt(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 SendTo(Kernel::HLERequestContext& ctx);
+    void Write(Kernel::HLERequestContext& ctx);
     void Close(Kernel::HLERequestContext& ctx);
     void Close(Kernel::HLERequestContext& ctx);
 
 
-    /// Id to use for the next open file descriptor.
-    u32 next_fd = 1;
+    template <typename Work>
+    void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
+                     bool is_blocking, Work work);
+
+    std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
+    std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+                                   s32 nfds, s32 timeout);
+    std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
+    Errno BindImpl(s32 fd, const std::vector<u8>& addr);
+    Errno ConnectImpl(s32 fd, const std::vector<u8>& addr);
+    Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer);
+    Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
+    Errno ListenImpl(s32 fd, s32 backlog);
+    std::pair<s32, Errno> 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<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
+    std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+                                       std::vector<u8>& addr);
+    std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message);
+    std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+                                     const std::vector<u8>& 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<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
+
+    BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork,
+                       SendToWork>
+        worker_pool;
 };
 };
 
 
 class BSDCFG final : public ServiceFramework<BSDCFG> {
 class BSDCFG final : public ServiceFramework<BSDCFG> {

+ 3 - 3
src/core/hle/service/sockets/sockets.cpp

@@ -10,9 +10,9 @@
 
 
 namespace Service::Sockets {
 namespace Service::Sockets {
 
 
-void InstallInterfaces(SM::ServiceManager& service_manager) {
-    std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
-    std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+    std::make_shared<BSD>(system, "bsd:s")->InstallAsService(service_manager);
+    std::make_shared<BSD>(system, "bsd:u")->InstallAsService(service_manager);
     std::make_shared<BSDCFG>()->InstallAsService(service_manager);
     std::make_shared<BSDCFG>()->InstallAsService(service_manager);
 
 
     std::make_shared<ETHC_C>()->InstallAsService(service_manager);
     std::make_shared<ETHC_C>()->InstallAsService(service_manager);

+ 84 - 1
src/core/hle/service/sockets/sockets.h

@@ -4,11 +4,94 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "common/common_types.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
 
 
+namespace Core {
+class System;
+}
+
 namespace Service::Sockets {
 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<u8, 4> ip;
+    std::array<u8, 8> 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.
 /// 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
 } // namespace Service::Sockets

+ 165 - 0
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 <utility>
+
+#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<int>(value));
+        return Errno::SUCCESS;
+    }
+}
+
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> 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<int>(domain));
+        return {};
+    }
+}
+
+Domain Translate(Network::Domain domain) {
+    switch (domain) {
+    case Network::Domain::INET:
+        return Domain::INET;
+    default:
+        UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(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<int>(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<int>(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<Domain>(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<u8>(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<int>(how));
+        return {};
+    }
+}
+
+} // namespace Service::Sockets

+ 48 - 0
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 <utility>
+
+#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<s32, Errno> Translate(std::pair<s32, Network::Errno> 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