// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include "common/logging.h" #include "core/internal_network/socket_types.h" #include "core/core.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/sockets/bsd.h" #include "core/hle/service/sockets/sockets_translate.h" #include "core/internal_network/network.h" #include "core/internal_network/socket_proxy.h" #include "core/internal_network/sockets.h" #include "network/network.h" #include namespace Service::Sockets { namespace { bool IsConnectionBased(Network::Type type) { switch (type) { case Network::Type::STREAM: return true; case Network::Type::DGRAM: return false; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return false; } } template T GetValue(std::span buffer) { T t{}; std::memcpy(&t, buffer.data(), (std::min)(sizeof(T), buffer.size())); return t; } template void PutValue(std::span buffer, const T& t) { std::memcpy(buffer.data(), &t, (std::min)(sizeof(T), buffer.size())); } } // 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(HLERequestContext& ctx) { if (write_buffer.size() > 0) { ctx.WriteBuffer(write_buffer); } IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); 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(HLERequestContext& ctx) { if (write_buffer.size() > 0) { ctx.WriteBuffer(write_buffer); } IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); 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(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(bsd_errno == Network::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(HLERequestContext& ctx) { ctx.WriteBuffer(message); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); 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(HLERequestContext& ctx) { ctx.WriteBuffer(message, 0); if (!addr.empty()) { ctx.WriteBuffer(addr, 1); } IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); 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(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); 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(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(ret); rb.PushEnum(bsd_errno); } void BSD::RegisterClient(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(0); // bsd errno } void BSD::StartMonitoring(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void BSD::Socket(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 domain = rp.Pop(); const u32 type = rp.Pop(); const u32 protocol = rp.Pop(); LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); const auto [fd, bsd_errno] = SocketImpl(Network::Domain(domain), Network::Type(type), Network::Protocol(protocol)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(fd); rb.PushEnum(bsd_errno); } void BSD::Select(HLERequestContext& ctx) { LOG_DEBUG(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(0); // ret rb.Push(0); // bsd errno } void BSD::Poll(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, PollWork{ .nfds = nfds, .timeout = timeout, .read_buffer = ctx.ReadBuffer(), .write_buffer = std::vector(ctx.GetWriteBufferSize()), }); } void BSD::Accept(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={}", fd); ExecuteWork(ctx, AcceptWork{ .fd = fd, .write_buffer = std::vector(ctx.GetWriteBufferSize()), }); } void BSD::Bind(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer())); } void BSD::Connect(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); ExecuteWork(ctx, ConnectWork{ .fd = fd, .addr = ctx.ReadBuffer(), }); } void BSD::GetPeerName(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={}", fd); std::vector write_buffer(ctx.GetWriteBufferSize()); const Network::Errno bsd_errno = GetPeerNameImpl(fd, write_buffer); ctx.WriteBuffer(write_buffer); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); rb.Push(bsd_errno != Network::Errno::SUCCESS ? -1 : 0); rb.PushEnum(bsd_errno); rb.Push(static_cast(write_buffer.size())); } void BSD::GetSockName(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={}", fd); std::vector write_buffer(ctx.GetWriteBufferSize()); const Network::Errno bsd_errno = GetSockNameImpl(fd, write_buffer); ctx.WriteBuffer(write_buffer); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); rb.Push(bsd_errno != Network::Errno::SUCCESS ? -1 : 0); rb.PushEnum(bsd_errno); rb.Push(static_cast(write_buffer.size())); } void BSD::GetSockOpt(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const auto level = Network::SocketLevel(rp.Pop()); const auto optname = Network::OptName(rp.Pop()); std::vector optval(ctx.GetWriteBufferSize()); LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} len={:#x}", fd, level, optname, optval.size()); const Network::Errno err = GetSockOptImpl(fd, level, optname, optval); ctx.WriteBuffer(optval); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); rb.Push(err == Network::Errno::SUCCESS ? 0 : -1); rb.PushEnum(err); rb.Push(static_cast(optval.size())); } void BSD::Listen(HLERequestContext& ctx) { 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(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, Network::FcntlCmd(cmd), arg); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(ret); rb.PushEnum(bsd_errno); } void BSD::SetSockOpt(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const Network::SocketLevel level = Network::SocketLevel(rp.Pop()); const Network::OptName optname = Network::OptName(rp.Pop()); const auto optval = ctx.ReadBuffer(); LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} optlen={}", fd, level, u32(optname), optval.size()); BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optval)); } void BSD::Shutdown(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(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); LOG_DEBUG(Service, "called. fd={} flags={:#x} len={}", fd, flags, ctx.GetWriteBufferSize()); ExecuteWork(ctx, RecvWork{ .fd = fd, .flags = flags, .message = std::vector(ctx.GetWriteBufferSize()), }); } void BSD::RecvFrom(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); LOG_DEBUG(Service, "called. fd={} flags={:#x} len={} addrlen={}", fd, flags, ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1)); ExecuteWork(ctx, RecvFromWork{ .fd = fd, .flags = flags, .message = std::vector(ctx.GetWriteBufferSize(0)), .addr = std::vector(ctx.GetWriteBufferSize(1)), }); } void BSD::Send(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); LOG_DEBUG(Service, "called. fd={} flags={:#x} len={}", fd, flags, ctx.GetReadBufferSize()); ExecuteWork(ctx, SendWork{ .fd = fd, .flags = flags, .message = ctx.ReadBuffer(), }); } void BSD::SendTo(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags, ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1)); ExecuteWork(ctx, SendToWork{ .fd = fd, .flags = flags, .message = ctx.ReadBuffer(0), .addr = ctx.ReadBuffer(1), }); } void BSD::Write(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize()); ExecuteWork(ctx, SendWork{ .fd = fd, .flags = 0, .message = ctx.ReadBuffer(), }); } void BSD::Read(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_WARNING(Service, "(STUBBED) called. fd={} len={}", fd, ctx.GetWriteBufferSize()); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(0); // ret rb.Push(0); // bsd errno } void BSD::Close(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); LOG_DEBUG(Service, "called. fd={}", fd); BuildErrnoResponse(ctx, CloseImpl(fd)); } void BSD::DuplicateSocket(HLERequestContext& ctx) { struct InputParameters { s32 fd; u64 reserved; }; static_assert(sizeof(InputParameters) == 0x10); struct OutputParameters { s32 ret; Network::Errno bsd_errno; }; static_assert(sizeof(OutputParameters) == 0x8); IPC::RequestParser rp{ctx}; auto input = rp.PopRaw(); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); auto const res_v = DuplicateSocketImpl(input.fd); if (auto* res = std::get_if(&res_v)) { rb.PushRaw(OutputParameters{ .ret = *res, .bsd_errno = Network::Errno::SUCCESS, }); } else { auto* err = std::get_if(&res_v); rb.PushRaw(OutputParameters{ .ret = 0, .bsd_errno = *err, }); } } void BSD::EventFd(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 initval = rp.Pop(); const u32 flags = rp.Pop(); LOG_WARNING(Service, "(STUBBED) called. initval={}, flags={}", initval, flags); BuildErrnoResponse(ctx, Network::Errno::SUCCESS); } template void BSD::ExecuteWork(HLERequestContext& ctx, Work work) { work.Execute(this); work.Response(ctx); } std::pair BSD::SocketImpl(Network::Domain domain, Network::Type type, Network::Protocol protocol) { LOG_DEBUG(Network, "domain={},type,protocol={}", u32(domain), u32(type), u32(protocol)); if (type == Network::Type::SEQPACKET) { UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management"); } else if (type == Network::Type::RAW && (domain != Network::Domain::INET || protocol != Network::Protocol::ICMP)) { UNIMPLEMENTED_MSG("SOCK_RAW errno management"); } [[maybe_unused]] const bool unk_flag = (u32(type) & 0x20000000) != 0; UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type"); type = Network::Type(u32(type) & ~0x20000000); const s32 fd = FindFreeFileDescriptorHandle(); if (fd < 0) { LOG_ERROR(Service, "No more file descriptors available"); return {-1, Network::Errno::MFILE}; } if (Settings::values.airplane_mode.GetValue() && IsConnectionBased(type)) { LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket"); file_descriptors[fd].reset(); return {-1, Network::Errno::NOTCONN}; } file_descriptors[fd] = FileDescriptor{}; FileDescriptor& descriptor = *file_descriptors[fd]; // ENONMEM might be thrown here LOG_INFO(Service, "New socket fd={},domain={},type={},prot={}", fd, domain, type, protocol); auto room_member = Network::GetRoomMember().lock(); if (room_member && room_member->IsConnected()) { descriptor.socket = std::make_shared(); } else { descriptor.socket = std::make_shared(); } auto const bsd_errno = descriptor.socket->Initialize(domain, type, protocol); descriptor.is_connection_based = IsConnectionBased(type); #ifdef _WIN32 if (descriptor.socket->fd == INVALID_SOCKET) { #else if (descriptor.socket->fd == Network::Socket::INVALID_SOCKET) { #endif file_descriptors[fd].reset(); return {-1, bsd_errno}; } return {fd, Network::Errno::SUCCESS}; } std::pair BSD::PollImpl(std::vector& write_buffer, std::span read_buffer, s32 nfds, s32 timeout) { LOG_DEBUG(Network, "nfds={},timeout={}", nfds, timeout); if (nfds <= 0) { // When no entries are provided, -1 is returned with errno zero return {-1, Network::Errno::SUCCESS}; } if (read_buffer.size() < nfds * sizeof(Network::PollFD)) { return {-1, Network::Errno::INVAL}; } if (write_buffer.size() < nfds * sizeof(Network::PollFD)) { return {-1, Network::Errno::INVAL}; } std::span in_fds(reinterpret_cast(read_buffer.data()), nfds); std::span out_fds(reinterpret_cast(write_buffer.data()), nfds); std::copy(in_fds.begin(), in_fds.end(), out_fds.begin()); if (timeout >= 0) { const s64 seconds = timeout / 1000; const u64 nanoseconds = 1'000'000 * (u64(timeout) % 1000); if (seconds < 0) { return {-1, Network::Errno::INVAL}; } if (nanoseconds > 999'999'999) { return {-1, Network::Errno::INVAL}; } } else if (timeout != -1) { return {-1, Network::Errno::INVAL}; } for (size_t i = 0; i < in_fds.size(); ++i) { ASSERT(out_fds[i].fd == in_fds[i].fd && False(in_fds[i].revents)); if (!IsFileDescriptorValid(in_fds[i].fd)) { out_fds[i].revents = {}; if (!file_descriptors[in_fds[i].fd]) out_fds[i].revents = Network::PollEvents::NVAL; return {0, Network::Errno::SUCCESS}; } } std::vector host_pollfds(in_fds.size()); std::transform(in_fds.begin(), in_fds.end(), host_pollfds.begin(), [](auto const e) { Network::HostPollFD result{}; result.socket = file_descriptors[e.fd]->socket.get(); result.events = e.events; result.revents = {}; return result; }); auto const res = Network::Poll(host_pollfds, timeout); for (size_t i = 0; i < in_fds.size(); ++i) out_fds[i].revents = host_pollfds[i].revents; return res; } std::pair BSD::AcceptImpl(s32 fd, std::vector& write_buffer) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return {-1, Network::Errno::BADF}; } const s32 new_fd = FindFreeFileDescriptorHandle(); if (new_fd < 0) { LOG_ERROR(Service, "No more file descriptors available"); return {-1, Network::Errno::MFILE}; } FileDescriptor& descriptor = *file_descriptors[fd]; auto [result, bsd_errno] = descriptor.socket->Accept(); if (bsd_errno != Network::Errno::SUCCESS) { return {-1, bsd_errno}; } file_descriptors[new_fd] = FileDescriptor{}; FileDescriptor& new_descriptor = *file_descriptors[new_fd]; new_descriptor.socket = std::move(result.socket); new_descriptor.is_connection_based = descriptor.is_connection_based; PutValue(write_buffer, result.sockaddr_in); return {new_fd, Network::Errno::SUCCESS}; } Network::Errno BSD::BindImpl(s32 fd, std::span addr) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } ASSERT(addr.size() >= 16); if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } auto addr_in = GetValue(addr); return file_descriptors[fd]->socket->Bind(addr_in); } Network::Errno BSD::ConnectImpl(s32 fd, std::span addr) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } ASSERT(addr.size() >= 16); if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } auto addr_in = GetValue(addr); const Network::Errno result = file_descriptors[fd]->socket->Connect(addr_in); if (result == Network::Errno::ISCONN) { LOG_DEBUG(Service, "returned ISCONN - socket already connected"); return Network::Errno::SUCCESS; } return result; } Network::Errno BSD::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName(); if (bsd_errno != Network::Errno::SUCCESS) { return bsd_errno; } ASSERT(write_buffer.size() >= addr_in.len); write_buffer.resize(addr_in.len); PutValue(write_buffer, addr_in); return bsd_errno; } Network::Errno BSD::GetSockNameImpl(s32 fd, std::vector& write_buffer) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName(); if (bsd_errno != Network::Errno::SUCCESS) { return bsd_errno; } ASSERT(write_buffer.size() >= addr_in.len); write_buffer.resize(addr_in.len); PutValue(write_buffer, addr_in); return bsd_errno; } Network::Errno BSD::ListenImpl(s32 fd, s32 backlog) { LOG_DEBUG(Network, "fd={},backlog={}", fd, backlog); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } return file_descriptors[fd]->socket->Listen(backlog); } std::pair BSD::FcntlImpl(s32 fd, Network::FcntlCmd cmd, s32 arg) { LOG_DEBUG(Network, "fd={},cmd={},arg={}", fd, u32(cmd), arg); if (!IsFileDescriptorValid(fd)) { return {-1, Network::Errno::BADF}; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return {-1, Network::Errno::BADF}; } FileDescriptor& descriptor = *file_descriptors[fd]; switch (cmd) { case Network::FcntlCmd::GETFL: ASSERT(arg == 0); return {descriptor.flags, Network::Errno::SUCCESS}; case Network::FcntlCmd::SETFL: { const bool enable = (arg & u32(Network::FcntlFlags::NONBLOCK_NX)) != 0; const Network::Errno bsd_errno = descriptor.socket->SetNonBlock(enable); if (bsd_errno != Network::Errno::SUCCESS) { return {-1, bsd_errno}; } descriptor.flags = arg; return {0, Network::Errno::SUCCESS}; } default: UNIMPLEMENTED_MSG("Unimplemented cmd={}", cmd); return {-1, Network::Errno::SUCCESS}; } } Network::Errno BSD::GetSockOptImpl(s32 fd, Network::SocketLevel level, Network::OptName optname, std::vector& optval) { LOG_DEBUG(Network, "fd={},level={},optname={}", fd, u32(level), u32(optname)); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } if (level != Network::SocketLevel::SOCKET) { LOG_WARNING(Service, "(stubbed) level fd={}, level={}, optname={}", fd, level, optname); } Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); switch (optname) { case Network::OptName::ERROR_: { auto [pending_err, getsockopt_err] = socket->GetPendingError(); if (getsockopt_err == Network::Errno::SUCCESS) { ASSERT_OR_EXECUTE_MSG( optval.size() == sizeof(Network::Errno), { return Network::Errno::INVAL; }, "Incorrect getsockopt option size"); optval.resize(sizeof(Network::Errno)); PutValue(optval, pending_err); } return getsockopt_err; } default: UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); return Network::Errno::SUCCESS; } } Network::Errno BSD::SetSockOptImpl(s32 fd, Network::SocketLevel level, Network::OptName optname, std::span optval) { LOG_DEBUG(Service, "fd={},level={},optname={}", fd, level, optname); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); return socket->SetSockOpt(level, optname, optval); } Network::Errno BSD::ShutdownImpl(s32 fd, s32 how) { LOG_DEBUG(Network, "fd={},how={}", fd, how); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } return file_descriptors[fd]->socket->Shutdown(Network::ShutdownHow(how)); } std::pair BSD::RecvImpl(s32 fd, u32 flags, std::vector& message) { LOG_DEBUG(Network, "fd={},flags={}", fd, flags); if (!IsFileDescriptorValid(fd)) { return {-1, Network::Errno::BADF}; } FileDescriptor& descriptor = *file_descriptors[fd]; // Apply flags if ((flags & u32(Network::MsgOpt::DONTWAIT)) != 0) { flags &= ~u32(Network::MsgOpt::DONTWAIT); if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) { descriptor.socket->SetNonBlock(true); } } const auto [ret, bsd_errno] = descriptor.socket->Recv(flags, message); // Restore original state if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) descriptor.socket->SetNonBlock(false); return {ret, bsd_errno}; } std::pair BSD::RecvFromImpl(s32 fd, u32 flags, std::vector& message, std::vector& addr) { LOG_DEBUG(Network, "fd={},flags={}", fd, flags); if (!IsFileDescriptorValid(fd)) { return {-1, Network::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 & u32(Network::MsgOpt::DONTWAIT)) != 0) { flags &= ~u32(Network::MsgOpt::DONTWAIT); if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) { descriptor.socket->SetNonBlock(true); } } const auto [ret, bsd_errno] = descriptor.socket->RecvFrom(flags, message, p_addr_in); // Restore original state if ((descriptor.flags & u32(Network::FcntlFlags::NONBLOCK_NX)) == 0) { descriptor.socket->SetNonBlock(false); } if (p_addr_in) { if (ret < 0) { addr.clear(); } else { ASSERT(addr.size() >= 16); PutValue(addr, addr_in); } } return {ret, bsd_errno}; } std::pair BSD::SendImpl(s32 fd, u32 flags, std::span message) { LOG_DEBUG(Network, "fd={},flags={}", fd, flags); if (!IsFileDescriptorValid(fd)) { return {-1, Network::Errno::BADF}; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return {-1, Network::Errno::BADF}; } return file_descriptors[fd]->socket->Send(message, flags); } std::pair BSD::SendToImpl(s32 fd, u32 flags, std::span message, std::span addr) { LOG_DEBUG(Network, "fd={},flags={}", fd, flags); if (!IsFileDescriptorValid(fd)) { return {-1, Network::Errno::BADF}; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return {-1, Network::Errno::BADF}; } Network::SockAddrIn addr_in{}; Network::SockAddrIn* p_addr_in = nullptr; if (!addr.empty()) { ASSERT(addr.size() >= 16); auto guest_addr_in = GetValue(addr); addr_in = guest_addr_in; p_addr_in = &addr_in; } return file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in); } Network::Errno BSD::CloseImpl(s32 fd) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return Network::Errno::BADF; } auto const bsd_errno = file_descriptors[fd]->socket->Close(); if (bsd_errno != Network::Errno::SUCCESS) { return bsd_errno; } LOG_INFO(Service, "Close socket fd={}", fd); file_descriptors[fd].reset(); return bsd_errno; } std::variant BSD::DuplicateSocketImpl(s32 fd) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return Network::Errno::BADF; } const s32 new_fd = FindFreeFileDescriptorHandle(); if (new_fd < 0) { LOG_ERROR(Service, "No more file descriptors available"); return Network::Errno::MFILE; } file_descriptors[new_fd] = FileDescriptor{ .socket = file_descriptors[fd]->socket, .flags = file_descriptors[fd]->flags, .is_connection_based = file_descriptors[fd]->is_connection_based, }; return new_fd; } std::optional> BSD::GetSocket(s32 fd) { LOG_DEBUG(Network, "fd={}", fd); if (!IsFileDescriptorValid(fd)) { return std::nullopt; } if (!file_descriptors[fd]->socket) { LOG_WARNING(Service, "Uninitialized socket"); return std::nullopt; } return file_descriptors[fd]->socket; } s32 BSD::FindFreeFileDescriptorHandle() noexcept { // first three file descriptors are reserved for: // STDOUT_FILENO, STDIN_FILENO and STDERR_FILENO for (s32 fd = 0; fd < s32(file_descriptors.size()); ++fd) if (!file_descriptors[fd]) return fd; return -1; } bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { if (fd < 0 || fd >= s32(file_descriptors.size())) { LOG_ERROR(Service, "Invalid handle={}", fd); return false; } if (!file_descriptors[fd]) { LOG_ERROR(Service, "handle={} is not allocated", fd); return false; } return true; } void BSD::BuildErrnoResponse(HLERequestContext& ctx, Network::Errno bsd_errno) const noexcept { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(bsd_errno == Network::Errno::SUCCESS ? 0 : -1); rb.PushEnum(bsd_errno); } void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) { for (auto& optional_descriptor : file_descriptors) { if (optional_descriptor.has_value()) { FileDescriptor& descriptor = *optional_descriptor; descriptor.socket.get()->HandleProxyPacket(packet); } } } BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, {1, &BSD::StartMonitoring, "StartMonitoring"}, {2, &BSD::Socket, "Socket"}, {3, nullptr, "SocketExempt"}, {4, nullptr, "Open"}, {5, &BSD::Select, "Select"}, {6, &BSD::Poll, "Poll"}, {7, nullptr, "Sysctl"}, {8, &BSD::Recv, "Recv"}, {9, &BSD::RecvFrom, "RecvFrom"}, {10, &BSD::Send, "Send"}, {11, &BSD::SendTo, "SendTo"}, {12, &BSD::Accept, "Accept"}, {13, &BSD::Bind, "Bind"}, {14, &BSD::Connect, "Connect"}, {15, &BSD::GetPeerName, "GetPeerName"}, {16, &BSD::GetSockName, "GetSockName"}, {17, &BSD::GetSockOpt, "GetSockOpt"}, {18, &BSD::Listen, "Listen"}, {19, nullptr, "Ioctl"}, {20, &BSD::Fcntl, "Fcntl"}, {21, &BSD::SetSockOpt, "SetSockOpt"}, {22, &BSD::Shutdown, "Shutdown"}, {23, nullptr, "ShutdownAllSockets"}, {24, &BSD::Write, "Write"}, {25, &BSD::Read, "Read"}, {26, &BSD::Close, "Close"}, {27, &BSD::DuplicateSocket, "DuplicateSocket"}, {28, nullptr, "GetResourceStatistics"}, {29, nullptr, "RecvMMsg"}, //3.0.0+ {30, nullptr, "SendMMsg"}, //3.0.0+ {31, &BSD::EventFd, "EventFd"}, //7.0.0+ {32, nullptr, "RegisterResourceStatisticsName"}, //7.0.0+ {33, nullptr, "RegisterClientShared"}, //10.0.0+ {34, nullptr, "GetSocketStatistics"}, //15.0.0+ {35, nullptr, "NifIoctl"}, //17.0.0+ {36, nullptr, "Unknown36"}, //18.0.0+ {37, nullptr, "Unknown37"}, //18.0.0+ {38, nullptr, "Unknown38"}, //18.0.0+ {39, nullptr, "Unknown39"}, //20.0.0+ {40, nullptr, "Unknown40"}, //20.0.0+ {41, nullptr, "Unknown41"}, //21.0.0+ {42, nullptr, "Unknown42"}, //21.0.0+ {43, nullptr, "Unknown43"}, //21.0.0+ {200, nullptr, "SetThreadCoreMask"}, //15.0.0+ {201, nullptr, "GetThreadCoreMask"}, //15.0.0+ }; // clang-format on RegisterHandlers(functions); if (auto room_member = Network::GetRoomMember().lock()) { proxy_packet_received = room_member->BindOnProxyPacketReceived( [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); } else { LOG_ERROR(Service, "Network isn't initialized"); } } BSD::~BSD() { if (auto room_member = Network::GetRoomMember().lock()) { room_member->Unbind(proxy_packet_received); } } std::unique_lock BSD::LockService() noexcept { return {}; } BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetIfUp"}, {1, nullptr, "SetIfUpWithEvent"}, {2, nullptr, "CancelIf"}, {3, nullptr, "SetIfDown"}, {4, nullptr, "GetIfState"}, {5, nullptr, "DhcpRenew"}, {6, nullptr, "AddStaticArpEntry"}, {7, nullptr, "RemoveArpEntry"}, {8, nullptr, "LookupArpEntry"}, {9, nullptr, "LookupArpEntry2"}, {10, nullptr, "ClearArpEntries"}, {11, nullptr, "ClearArpEntries2"}, {12, nullptr, "PrintArpEntries"}, {13, nullptr, "Unknown13"}, {14, nullptr, "Unknown14"}, {15, nullptr, "Unknown15"}, }; // clang-format on RegisterHandlers(functions); } BSDCFG::~BSDCFG() = default; } // namespace Service::Sockets