forked from eden-emu/eden
There is a cmd `IsAnyInternetRequestAccepted` which is called by games like DOOM to check if internet access is available. Adds the newly added airplane mode there. Also, moved down the airplane mode check on bsd to also check if it's a local file request, if it is, let it connect. (SMO guide web applet for example) + adds a check in IRequest if airplane mode is active, which then results in an not succeeding requests. Reviewed-on: eden-emu/eden#225 Co-authored-by: Maufeat <sahyno1996@gmail.com> Co-committed-by: Maufeat <sahyno1996@gmail.com>
1071 lines
32 KiB
C++
1071 lines
32 KiB
C++
// SPDX-FileCopyrightText: Copyright 2025 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 <array>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <fmt/ranges.h>
|
|
|
|
#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"
|
|
#include <common/settings.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;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
T GetValue(std::span<const u8> buffer) {
|
|
T t{};
|
|
std::memcpy(&t, buffer.data(), std::min(sizeof(T), buffer.size()));
|
|
return t;
|
|
}
|
|
|
|
template <typename T>
|
|
void PutValue(std::span<u8> 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<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(HLERequestContext& ctx) {
|
|
if (write_buffer.size() > 0) {
|
|
ctx.WriteBuffer(write_buffer);
|
|
}
|
|
|
|
IPC::ResponseBuilder rb{ctx, 5};
|
|
rb.Push(ResultSuccess);
|
|
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(HLERequestContext& ctx) {
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
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(HLERequestContext& ctx) {
|
|
ctx.WriteBuffer(message);
|
|
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
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(HLERequestContext& ctx) {
|
|
ctx.WriteBuffer(message, 0);
|
|
if (!addr.empty()) {
|
|
ctx.WriteBuffer(addr, 1);
|
|
}
|
|
|
|
IPC::ResponseBuilder rb{ctx, 5};
|
|
rb.Push(ResultSuccess);
|
|
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(HLERequestContext& ctx) {
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
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(HLERequestContext& ctx) {
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
rb.Push<s32>(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<s32>(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<u32>();
|
|
const u32 type = rp.Pop<u32>();
|
|
const u32 protocol = rp.Pop<u32>();
|
|
|
|
LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
|
|
|
|
const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
|
|
static_cast<Protocol>(protocol));
|
|
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
rb.Push<s32>(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<u32>(0); // ret
|
|
rb.Push<u32>(0); // bsd errno
|
|
}
|
|
|
|
void BSD::Poll(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, PollWork{
|
|
.nfds = nfds,
|
|
.timeout = timeout,
|
|
.read_buffer = ctx.ReadBuffer(),
|
|
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
|
|
});
|
|
}
|
|
|
|
void BSD::Accept(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
const s32 fd = rp.Pop<s32>();
|
|
|
|
LOG_DEBUG(Service, "called. fd={}", fd);
|
|
|
|
ExecuteWork(ctx, AcceptWork{
|
|
.fd = fd,
|
|
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
|
|
});
|
|
}
|
|
|
|
void BSD::Bind(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
const s32 fd = rp.Pop<s32>();
|
|
|
|
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<s32>();
|
|
|
|
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<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(ResultSuccess);
|
|
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(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(ResultSuccess);
|
|
rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
|
|
rb.PushEnum(bsd_errno);
|
|
rb.Push<u32>(static_cast<u32>(write_buffer.size()));
|
|
}
|
|
|
|
void BSD::GetSockOpt(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
const s32 fd = rp.Pop<s32>();
|
|
const u32 level = rp.Pop<u32>();
|
|
const auto optname = static_cast<OptName>(rp.Pop<u32>());
|
|
|
|
std::vector<u8> 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<s32>(err == Errno::SUCCESS ? 0 : -1);
|
|
rb.PushEnum(err);
|
|
rb.Push<u32>(static_cast<u32>(optval.size()));
|
|
}
|
|
|
|
void BSD::Listen(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
const s32 fd = rp.Pop<s32>();
|
|
const s32 backlog = rp.Pop<s32>();
|
|
|
|
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<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(ResultSuccess);
|
|
rb.Push<s32>(ret);
|
|
rb.PushEnum(bsd_errno);
|
|
}
|
|
|
|
void BSD::SetSockOpt(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
|
|
const s32 fd = rp.Pop<s32>();
|
|
const u32 level = rp.Pop<u32>();
|
|
const OptName optname = static_cast<OptName>(rp.Pop<u32>());
|
|
const auto optval = ctx.ReadBuffer();
|
|
|
|
LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
|
|
static_cast<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<s32>();
|
|
const s32 how = rp.Pop<s32>();
|
|
|
|
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<s32>();
|
|
const u32 flags = rp.Pop<u32>();
|
|
|
|
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
|
|
|
|
ExecuteWork(ctx, RecvWork{
|
|
.fd = fd,
|
|
.flags = flags,
|
|
.message = std::vector<u8>(ctx.GetWriteBufferSize()),
|
|
});
|
|
}
|
|
|
|
void BSD::RecvFrom(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, RecvFromWork{
|
|
.fd = fd,
|
|
.flags = flags,
|
|
.message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
|
|
.addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
|
|
});
|
|
}
|
|
|
|
void BSD::Send(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, SendWork{
|
|
.fd = fd,
|
|
.flags = flags,
|
|
.message = ctx.ReadBuffer(),
|
|
});
|
|
}
|
|
|
|
void BSD::SendTo(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{} 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<s32>();
|
|
|
|
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<s32>();
|
|
|
|
LOG_WARNING(Service, "(STUBBED) called. fd={} len={}", fd, ctx.GetWriteBufferSize());
|
|
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
rb.Push<u32>(0); // ret
|
|
rb.Push<u32>(0); // bsd errno
|
|
}
|
|
|
|
void BSD::Close(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
const s32 fd = rp.Pop<s32>();
|
|
|
|
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;
|
|
Errno bsd_errno;
|
|
};
|
|
static_assert(sizeof(OutputParameters) == 0x8);
|
|
|
|
IPC::RequestParser rp{ctx};
|
|
auto input = rp.PopRaw<InputParameters>();
|
|
|
|
Expected<s32, Errno> res = DuplicateSocketImpl(input.fd);
|
|
IPC::ResponseBuilder rb{ctx, 4};
|
|
rb.Push(ResultSuccess);
|
|
rb.PushRaw(OutputParameters{
|
|
.ret = res.value_or(0),
|
|
.bsd_errno = res ? Errno::SUCCESS : res.error(),
|
|
});
|
|
}
|
|
|
|
void BSD::EventFd(HLERequestContext& ctx) {
|
|
IPC::RequestParser rp{ctx};
|
|
const u64 initval = rp.Pop<u64>();
|
|
const u32 flags = rp.Pop<u32>();
|
|
|
|
LOG_WARNING(Service, "(STUBBED) called. initval={}, flags={}", initval, flags);
|
|
|
|
BuildErrnoResponse(ctx, Errno::SUCCESS);
|
|
}
|
|
|
|
template <typename Work>
|
|
void BSD::ExecuteWork(HLERequestContext& ctx, Work work) {
|
|
work.Execute(this);
|
|
work.Response(ctx);
|
|
}
|
|
|
|
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};
|
|
}
|
|
|
|
file_descriptors[fd] = FileDescriptor{};
|
|
FileDescriptor& descriptor = *file_descriptors[fd];
|
|
// ENONMEM might be thrown here
|
|
|
|
LOG_INFO(Service, "New socket fd={}", fd);
|
|
|
|
auto room_member = Network::GetRoomMember().lock();
|
|
if (room_member && room_member->IsConnected()) {
|
|
descriptor.socket = std::make_shared<Network::ProxySocket>();
|
|
} else {
|
|
descriptor.socket = std::make_shared<Network::Socket>();
|
|
}
|
|
|
|
descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol));
|
|
descriptor.is_connection_based = IsConnectionBased(type);
|
|
|
|
if (Settings::values.airplane_mode.GetValue() && descriptor.is_connection_based) {
|
|
LOG_ERROR(Service, "Airplane mode is enabled, cannot create socket");
|
|
return {-1, Errno::NOTCONN};
|
|
}
|
|
|
|
return {fd, Errno::SUCCESS};
|
|
}
|
|
|
|
std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<const u8> read_buffer,
|
|
s32 nfds, s32 timeout) {
|
|
if (nfds <= 0) {
|
|
// When no entries are provided, -1 is returned with errno zero
|
|
return {-1, Errno::SUCCESS};
|
|
}
|
|
if (read_buffer.size() < nfds * sizeof(PollFD)) {
|
|
return {-1, Errno::INVAL};
|
|
}
|
|
if (write_buffer.size() < nfds * sizeof(PollFD)) {
|
|
return {-1, Errno::INVAL};
|
|
}
|
|
|
|
std::vector<PollFD> fds(nfds);
|
|
std::memcpy(fds.data(), read_buffer.data(), nfds * sizeof(PollFD));
|
|
|
|
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(False(pollfd.revents));
|
|
|
|
if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) {
|
|
LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
|
|
pollfd.revents = PollEvents{};
|
|
return {0, Errno::SUCCESS};
|
|
}
|
|
|
|
const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
|
|
if (!descriptor) {
|
|
LOG_TRACE(Service, "File descriptor handle={} is not allocated", pollfd.fd);
|
|
pollfd.revents = PollEvents::Nval;
|
|
return {0, Errno::SUCCESS};
|
|
}
|
|
}
|
|
|
|
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 = 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(), nfds * sizeof(PollFD));
|
|
|
|
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)};
|
|
}
|
|
|
|
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);
|
|
PutValue(write_buffer, guest_addr_in);
|
|
|
|
return {new_fd, Errno::SUCCESS};
|
|
}
|
|
|
|
Errno BSD::BindImpl(s32 fd, std::span<const u8> addr) {
|
|
if (!IsFileDescriptorValid(fd)) {
|
|
return Errno::BADF;
|
|
}
|
|
ASSERT(addr.size() == sizeof(SockAddrIn));
|
|
auto addr_in = GetValue<SockAddrIn>(addr);
|
|
|
|
return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
|
|
}
|
|
|
|
Errno BSD::ConnectImpl(s32 fd, std::span<const u8> addr) {
|
|
if (!IsFileDescriptorValid(fd)) {
|
|
return Errno::BADF;
|
|
}
|
|
|
|
UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
|
|
auto addr_in = GetValue<SockAddrIn>(addr);
|
|
|
|
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));
|
|
write_buffer.resize(sizeof(guest_addrin));
|
|
PutValue(write_buffer, 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));
|
|
write_buffer.resize(sizeof(guest_addrin));
|
|
PutValue(write_buffer, 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 & 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<u8>& optval) {
|
|
if (!IsFileDescriptorValid(fd)) {
|
|
return Errno::BADF;
|
|
}
|
|
|
|
if (level != static_cast<u32>(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));
|
|
PutValue(optval, translated_pending_err);
|
|
}
|
|
return Translate(getsockopt_err);
|
|
}
|
|
default:
|
|
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
|
|
return Errno::SUCCESS;
|
|
}
|
|
}
|
|
|
|
Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8> optval) {
|
|
if (!IsFileDescriptorValid(fd)) {
|
|
return Errno::BADF;
|
|
}
|
|
|
|
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
|
UNIMPLEMENTED_MSG("Unknown setsockopt level");
|
|
return Errno::SUCCESS;
|
|
}
|
|
|
|
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
|
|
|
if (optname == OptName::LINGER) {
|
|
ASSERT(optval.size() == sizeof(Linger));
|
|
auto linger = GetValue<Linger>(optval);
|
|
ASSERT(linger.onoff == 0 || linger.onoff == 1);
|
|
|
|
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
|
|
}
|
|
|
|
ASSERT(optval.size() == sizeof(u32));
|
|
auto value = GetValue<u32>(optval);
|
|
|
|
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<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};
|
|
}
|
|
|
|
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<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
|
|
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);
|
|
PutValue(addr, result);
|
|
}
|
|
}
|
|
|
|
return {ret, bsd_errno};
|
|
}
|
|
|
|
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const 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, std::span<const u8> message,
|
|
std::span<const 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));
|
|
auto guest_addr_in = GetValue<SockAddrIn>(addr);
|
|
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<s32, Errno> 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<std::shared_ptr<Network::SocketBase>> 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<s32>(file_descriptors.size()); ++fd) {
|
|
if (!file_descriptors[fd]) {
|
|
return fd;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
|
|
if (fd > static_cast<s32>(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<s32>(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} {
|
|
// 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 = 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<std::mutex> BSD::LockService() {
|
|
// Do not lock socket IClient instances.
|
|
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
|