mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-27 13:56:27 -05:00
360 lines
13 KiB
C++
360 lines
13 KiB
C++
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "common/string_util.h"
|
|
#include "common/swap.h"
|
|
#include "core/core.h"
|
|
#include "core/hle/service/ipc_helpers.h"
|
|
#include "core/hle/service/sockets/sfdnsres.h"
|
|
#include "core/hle/service/sockets/sockets.h"
|
|
#include "core/hle/service/sockets/sockets_translate.h"
|
|
#include "core/internal_network/network.h"
|
|
#include "core/memory.h"
|
|
|
|
namespace Service::Sockets {
|
|
|
|
SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} {
|
|
static const FunctionInfo functions[] = {
|
|
{0, nullptr, "SetDnsAddressesPrivateRequest"},
|
|
{1, nullptr, "GetDnsAddressPrivateRequest"},
|
|
{2, &SFDNSRES::GetHostByNameRequest, "GetHostByNameRequest"},
|
|
{3, nullptr, "GetHostByAddrRequest"},
|
|
{4, nullptr, "GetHostStringErrorRequest"},
|
|
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
|
|
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
|
|
{7, nullptr, "GetNameInfoRequest"},
|
|
{8, nullptr, "RequestCancelHandleRequest"},
|
|
{9, nullptr, "CancelRequest"},
|
|
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
|
|
{11, nullptr, "GetHostByAddrRequestWithOptions"},
|
|
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
|
|
{13, nullptr, "GetNameInfoRequestWithOptions"},
|
|
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
|
|
{15, nullptr, "ResolverGetOptionRequest"},
|
|
};
|
|
RegisterHandlers(functions);
|
|
}
|
|
|
|
SFDNSRES::~SFDNSRES() = default;
|
|
|
|
enum class NetDbError : s32 {
|
|
Internal = -1,
|
|
Success = 0,
|
|
HostNotFound = 1,
|
|
TryAgain = 2,
|
|
NoRecovery = 3,
|
|
NoData = 4,
|
|
};
|
|
|
|
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
|
|
// These combinations have been verified on console (but are not
|
|
// exhaustive).
|
|
switch (result) {
|
|
case GetAddrInfoError::SUCCESS:
|
|
return NetDbError::Success;
|
|
case GetAddrInfoError::AGAIN:
|
|
return NetDbError::TryAgain;
|
|
case GetAddrInfoError::NODATA:
|
|
return NetDbError::HostNotFound;
|
|
case GetAddrInfoError::SERVICE:
|
|
return NetDbError::Success;
|
|
default:
|
|
return NetDbError::HostNotFound;
|
|
}
|
|
}
|
|
|
|
static Errno GetAddrInfoErrorToErrno(GetAddrInfoError result) {
|
|
// These combinations have been verified on console (but are not
|
|
// exhaustive).
|
|
switch (result) {
|
|
case GetAddrInfoError::SUCCESS:
|
|
// Note: Sometimes a successful lookup sets errno to EADDRNOTAVAIL for
|
|
// some reason, but that doesn't seem useful to implement.
|
|
return Errno::SUCCESS;
|
|
case GetAddrInfoError::AGAIN:
|
|
return Errno::SUCCESS;
|
|
case GetAddrInfoError::NODATA:
|
|
return Errno::SUCCESS;
|
|
case GetAddrInfoError::SERVICE:
|
|
return Errno::INVAL;
|
|
default:
|
|
return Errno::SUCCESS;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
static void Append(std::vector<u8>& vec, T t) {
|
|
const size_t offset = vec.size();
|
|
vec.resize(offset + sizeof(T));
|
|
std::memcpy(vec.data() + offset, &t, sizeof(T));
|
|
}
|
|
|
|
static void AppendNulTerminated(std::vector<u8>& vec, std::string_view str) {
|
|
const size_t offset = vec.size();
|
|
vec.resize(offset + str.size() + 1);
|
|
std::memmove(vec.data() + offset, str.data(), str.size());
|
|
}
|
|
|
|
// We implement gethostbyname using the host's getaddrinfo rather than the
|
|
// host's gethostbyname, because it simplifies portability: e.g., getaddrinfo
|
|
// behaves the same on Unix and Windows, unlike gethostbyname where Windows
|
|
// doesn't implement h_errno.
|
|
static std::vector<u8> SerializeAddrInfoAsHostEnt(const std::vector<Network::AddrInfo>& vec,
|
|
std::string_view host) {
|
|
|
|
std::vector<u8> data;
|
|
// h_name: use the input hostname (append nul-terminated)
|
|
AppendNulTerminated(data, host);
|
|
// h_aliases: leave empty
|
|
|
|
Append<u32_be>(data, 0); // count of h_aliases
|
|
// (If the count were nonzero, the aliases would be appended as nul-terminated here.)
|
|
Append<u16_be>(data, static_cast<u16>(Domain::INET)); // h_addrtype
|
|
Append<u16_be>(data, sizeof(Network::IPv4Address)); // h_length
|
|
// h_addr_list:
|
|
size_t count = vec.size();
|
|
ASSERT(count <= UINT32_MAX);
|
|
Append<u32_be>(data, static_cast<uint32_t>(count));
|
|
for (const Network::AddrInfo& addrinfo : vec) {
|
|
// On the Switch, this is passed through htonl despite already being
|
|
// big-endian, so it ends up as little-endian.
|
|
Append<u32_le>(data, Network::IPv4AddressToInteger(addrinfo.addr.ip));
|
|
|
|
LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host,
|
|
Network::IPv4AddressToString(addrinfo.addr.ip));
|
|
}
|
|
return data;
|
|
}
|
|
|
|
static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestContext& ctx) {
|
|
struct InputParameters {
|
|
u8 use_nsd_resolve;
|
|
u32 cancel_handle;
|
|
u64 process_id;
|
|
};
|
|
static_assert(sizeof(InputParameters) == 0x10);
|
|
|
|
IPC::RequestParser rp{ctx};
|
|
const auto parameters = rp.PopRaw<InputParameters>();
|
|
|
|
LOG_WARNING(
|
|
Service,
|
|
"called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}",
|
|
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
|
|
|
const auto host_buffer = ctx.ReadBuffer(0);
|
|
const std::string host = Common::StringFromBuffer(host_buffer);
|
|
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
|
|
|
|
// Prevent resolution of Nintendo servers
|
|
if (host.find("srv.nintendo.net") != std::string::npos) {
|
|
LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
|
|
return {0, GetAddrInfoError::AGAIN};
|
|
}
|
|
|
|
auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt);
|
|
if (!res.has_value()) {
|
|
return {0, Translate(res.error())};
|
|
}
|
|
|
|
const std::vector<u8> data = SerializeAddrInfoAsHostEnt(res.value(), host);
|
|
const u32 data_size = static_cast<u32>(data.size());
|
|
ctx.WriteBuffer(data, 0);
|
|
|
|
return {data_size, GetAddrInfoError::SUCCESS};
|
|
}
|
|
|
|
void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) {
|
|
auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx);
|
|
|
|
struct OutputParameters {
|
|
NetDbError netdb_error;
|
|
Errno bsd_errno;
|
|
u32 data_size;
|
|
};
|
|
static_assert(sizeof(OutputParameters) == 0xc);
|
|
|
|
IPC::ResponseBuilder rb{ctx, 5};
|
|
rb.Push(ResultSuccess);
|
|
rb.PushRaw(OutputParameters{
|
|
.netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err),
|
|
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
|
.data_size = data_size,
|
|
});
|
|
}
|
|
|
|
void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) {
|
|
auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx);
|
|
|
|
struct OutputParameters {
|
|
u32 data_size;
|
|
NetDbError netdb_error;
|
|
Errno bsd_errno;
|
|
};
|
|
static_assert(sizeof(OutputParameters) == 0xc);
|
|
|
|
IPC::ResponseBuilder rb{ctx, 5};
|
|
rb.Push(ResultSuccess);
|
|
rb.PushRaw(OutputParameters{
|
|
.data_size = data_size,
|
|
.netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err),
|
|
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
|
});
|
|
}
|
|
|
|
static std::vector<u8> SerializeAddrInfo(const std::vector<Network::AddrInfo>& vec,
|
|
std::string_view host) {
|
|
// Adapted from
|
|
// https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190
|
|
std::vector<u8> data;
|
|
|
|
for (const Network::AddrInfo& addrinfo : vec) {
|
|
// serialized addrinfo:
|
|
Append<u32_be>(data, 0xBEEFCAFE); // magic
|
|
Append<u32_be>(data, 0); // ai_flags
|
|
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.family))); // ai_family
|
|
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.socket_type))); // ai_socktype
|
|
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.protocol))); // ai_protocol
|
|
Append<u32_be>(data, sizeof(SockAddrIn)); // ai_addrlen
|
|
// ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size
|
|
|
|
// ai_addr:
|
|
Append<u16_be>(data, static_cast<u16>(Translate(addrinfo.addr.family))); // sin_family
|
|
// On the Switch, the following fields are passed through htonl despite
|
|
// already being big-endian, so they end up as little-endian.
|
|
Append<u16_le>(data, addrinfo.addr.portno); // sin_port
|
|
Append<u32_le>(data, Network::IPv4AddressToInteger(addrinfo.addr.ip)); // sin_addr
|
|
data.resize(data.size() + 8, 0); // sin_zero
|
|
|
|
if (addrinfo.canon_name.has_value()) {
|
|
AppendNulTerminated(data, *addrinfo.canon_name);
|
|
} else {
|
|
data.push_back(0);
|
|
}
|
|
|
|
LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host,
|
|
Network::IPv4AddressToString(addrinfo.addr.ip));
|
|
}
|
|
|
|
data.resize(data.size() + 4, 0); // 4-byte sentinel value
|
|
|
|
return data;
|
|
}
|
|
|
|
static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext& ctx) {
|
|
struct InputParameters {
|
|
u8 use_nsd_resolve;
|
|
u32 cancel_handle;
|
|
u64 process_id;
|
|
};
|
|
static_assert(sizeof(InputParameters) == 0x10);
|
|
|
|
IPC::RequestParser rp{ctx};
|
|
const auto parameters = rp.PopRaw<InputParameters>();
|
|
|
|
LOG_WARNING(
|
|
Service,
|
|
"called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}",
|
|
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
|
|
|
// TODO: If use_nsd_resolve is true, pass the name through NSD::Resolve
|
|
// before looking up.
|
|
|
|
const auto host_buffer = ctx.ReadBuffer(0);
|
|
const std::string host = Common::StringFromBuffer(host_buffer);
|
|
|
|
// Prevent resolution of Nintendo servers
|
|
if (host.find("srv.nintendo.net") != std::string::npos) {
|
|
LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host);
|
|
return {0, GetAddrInfoError::AGAIN};
|
|
}
|
|
|
|
std::optional<std::string> service = std::nullopt;
|
|
if (ctx.CanReadBuffer(1)) {
|
|
const std::span<const u8> service_buffer = ctx.ReadBuffer(1);
|
|
service = Common::StringFromBuffer(service_buffer);
|
|
}
|
|
|
|
// Serialized hints are also passed in a buffer, but are ignored for now.
|
|
|
|
auto res = Network::GetAddressInfo(host, service);
|
|
if (!res.has_value()) {
|
|
return {0, Translate(res.error())};
|
|
}
|
|
|
|
const std::vector<u8> data = SerializeAddrInfo(res.value(), host);
|
|
const u32 data_size = static_cast<u32>(data.size());
|
|
ctx.WriteBuffer(data, 0);
|
|
|
|
return {data_size, GetAddrInfoError::SUCCESS};
|
|
}
|
|
|
|
void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) {
|
|
auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx);
|
|
|
|
struct OutputParameters {
|
|
Errno bsd_errno;
|
|
GetAddrInfoError gai_error;
|
|
u32 data_size;
|
|
};
|
|
static_assert(sizeof(OutputParameters) == 0xc);
|
|
|
|
IPC::ResponseBuilder rb{ctx, 5};
|
|
rb.Push(ResultSuccess);
|
|
rb.PushRaw(OutputParameters{
|
|
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
|
.gai_error = emu_gai_err,
|
|
.data_size = data_size,
|
|
});
|
|
}
|
|
|
|
void SFDNSRES::GetGaiStringErrorRequest(HLERequestContext& ctx) {
|
|
struct InputParameters {
|
|
GetAddrInfoError gai_errno;
|
|
};
|
|
IPC::RequestParser rp{ctx};
|
|
auto input = rp.PopRaw<InputParameters>();
|
|
|
|
const std::string result = Translate(input.gai_errno);
|
|
ctx.WriteBuffer(result);
|
|
|
|
IPC::ResponseBuilder rb{ctx, 2};
|
|
rb.Push(ResultSuccess);
|
|
}
|
|
|
|
void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
|
|
// Additional options are ignored
|
|
auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx);
|
|
|
|
struct OutputParameters {
|
|
u32 data_size;
|
|
GetAddrInfoError gai_error;
|
|
NetDbError netdb_error;
|
|
Errno bsd_errno;
|
|
};
|
|
static_assert(sizeof(OutputParameters) == 0x10);
|
|
|
|
IPC::ResponseBuilder rb{ctx, 6};
|
|
rb.Push(ResultSuccess);
|
|
rb.PushRaw(OutputParameters{
|
|
.data_size = data_size,
|
|
.gai_error = emu_gai_err,
|
|
.netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err),
|
|
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
|
});
|
|
}
|
|
|
|
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
|
LOG_WARNING(Service, "(STUBBED) called");
|
|
|
|
IPC::ResponseBuilder rb{ctx, 3};
|
|
|
|
rb.Push(ResultSuccess);
|
|
rb.Push<s32>(0); // bsd errno
|
|
}
|
|
|
|
} // namespace Service::Sockets
|