// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include "common/microprofile.h" #include "common/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" using Common::Expected; using Common::Unexpected; 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={}", 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(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 == 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(static_cast(domain), static_cast(type), static_cast(protocol)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(fd); rb.PushEnum(bsd_errno); } void BSD::Select(HLERequestContext& ctx) { LOG_WARNING(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 Errno bsd_errno = GetPeerNameImpl(fd, write_buffer); ctx.WriteBuffer(write_buffer); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); rb.Push(bsd_errno != 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 Errno bsd_errno = GetSockNameImpl(fd, write_buffer); ctx.WriteBuffer(write_buffer); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); rb.Push(bsd_errno != 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 u32 level = rp.Pop(); const auto optname = static_cast(rp.Pop()); std::vector optval(ctx.GetWriteBufferSize()); LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname, optval.size()); const Errno err = GetSockOptImpl(fd, level, optname, optval); ctx.WriteBuffer(optval); IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); rb.Push(err == 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, static_cast(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 u32 level = rp.Pop(); const OptName optname = static_cast(rp.Pop()); const auto 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(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=0x{: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=0x{: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=0x{: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) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); [[maybe_unused]] const u64 unused = rp.Pop(); Expected res = DuplicateSocketImpl(fd); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(res.value_or(0)); // ret rb.Push(res ? 0 : static_cast(res.error())); // bsd errno } 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, Errno::SUCCESS); } template void BSD::ExecuteWork(HLERequestContext& ctx, Work work) { work.Execute(this); work.Response(ctx); } 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}; } file_descriptors[fd] = FileDescriptor{}; FileDescriptor& descriptor = *file_descriptors[fd]; // ENONMEM might be thrown here LOG_INFO(Service, "New socket fd={}", fd); auto room_member = room_network.GetRoomMember().lock(); if (room_member && room_member->IsConnected()) { descriptor.socket = std::make_shared(room_network); } else { descriptor.socket = std::make_shared(); } descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol)); descriptor.is_connection_based = IsConnectionBased(type); return {fd, Errno::SUCCESS}; } std::pair BSD::PollImpl(std::vector& write_buffer, std::span 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 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(False(pollfd.revents)); if (pollfd.fd > static_cast(MAX_FD) || pollfd.fd < 0) { LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); pollfd.revents = PollEvents{}; return {0, Errno::SUCCESS}; } const std::optional& descriptor = file_descriptors[pollfd.fd]; if (!descriptor) { LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd); pollfd.revents = PollEvents::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 = Translate(pollfd.events); result.revents = Network::PollEvents{}; 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 = Translate(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)}; } 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; const SockAddrIn guest_addr_in = Translate(result.sockaddr_in); const size_t length = std::min(sizeof(guest_addr_in), write_buffer.size()); std::memcpy(write_buffer.data(), &guest_addr_in, length); return {new_fd, Errno::SUCCESS}; } Errno BSD::BindImpl(s32 fd, std::span 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, std::span 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)); write_buffer.resize(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)); write_buffer.resize(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 & Network::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={}", cmd); return {-1, Errno::SUCCESS}; } } Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } if (level != static_cast(SocketLevel::SOCKET)) { UNIMPLEMENTED_MSG("Unknown getsockopt level"); return Errno::SUCCESS; } Network::SocketBase* const socket = file_descriptors[fd]->socket.get(); switch (optname) { case OptName::ERROR_: { auto [pending_err, getsockopt_err] = socket->GetPendingError(); if (getsockopt_err == Network::Errno::SUCCESS) { Errno translated_pending_err = Translate(pending_err); ASSERT_OR_EXECUTE_MSG( optval.size() == sizeof(Errno), { return Errno::INVAL; }, "Incorrect getsockopt option size"); optval.resize(sizeof(Errno)); memcpy(optval.data(), &translated_pending_err, sizeof(Errno)); } return Translate(getsockopt_err); } default: UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); return Errno::SUCCESS; } } Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } if (level != static_cast(SocketLevel::SOCKET)) { UNIMPLEMENTED_MSG("Unknown setsockopt level"); return Errno::SUCCESS; } Network::SocketBase* 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::KEEPALIVE: ASSERT(value == 0 || value == 1); return Translate(socket->SetKeepAlive(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)); case OptName::NOSIGPIPE: LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value); return Errno::SUCCESS; default: UNIMPLEMENTED_MSG("Unimplemented optname={}", 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}; } FileDescriptor& descriptor = *file_descriptors[fd]; // Apply flags using Network::FLAG_MSG_DONTWAIT; using Network::FLAG_O_NONBLOCK; if ((flags & FLAG_MSG_DONTWAIT) != 0) { flags &= ~FLAG_MSG_DONTWAIT; if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { descriptor.socket->SetNonBlock(true); } } const auto [ret, bsd_errno] = Translate(descriptor.socket->Recv(flags, message)); // Restore original state if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { descriptor.socket->SetNonBlock(false); } return {ret, bsd_errno}; } 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 using Network::FLAG_MSG_DONTWAIT; using Network::FLAG_O_NONBLOCK; if ((flags & FLAG_MSG_DONTWAIT) != 0) { flags &= ~FLAG_MSG_DONTWAIT; if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) { descriptor.socket->SetNonBlock(true); } } const 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, std::span 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, std::span message, std::span 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); p_addr_in = &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; } Expected BSD::DuplicateSocketImpl(s32 fd) { if (!IsFileDescriptorValid(fd)) { return Unexpected(Errno::BADF); } const s32 new_fd = FindFreeFileDescriptorHandle(); if (new_fd < 0) { LOG_ERROR(Service, "No more file descriptors available"); return Unexpected(Errno::MFILE); } file_descriptors[new_fd] = file_descriptors[fd]; return new_fd; } std::optional> BSD::GetSocket(s32 fd) { if (!IsFileDescriptorValid(fd)) { return std::nullopt; } return file_descriptors[fd]->socket; } 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 > static_cast(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; } void BSD::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(bsd_errno == 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()) { continue; } FileDescriptor& descriptor = *optional_descriptor; descriptor.socket.get()->HandleProxyPacket(packet); } } BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name}, room_network{system_.GetRoomNetwork()} { // 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"}, {30, nullptr, "SendMMsg"}, {31, &BSD::EventFd, "EventFd"}, {32, nullptr, "RegisterResourceStatisticsName"}, {33, nullptr, "Initialize2"}, }; // clang-format on RegisterHandlers(functions); if (auto room_member = room_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 = room_network.GetRoomMember().lock()) { room_member->Unbind(proxy_packet_received); } } 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