Major net rewrite (#5236)

* p2p crypto schema

* adding p2p keyring

* p2p framing and testing

* fixups

* test fix

* missing definations

* implementation of host functions

* tcp/transport of p2p packets

* improvements in binding

* guest_net ,improvements and  test cases

* all mighty clang

* fixing macOS tests failures

* misc

* net_rewrite : net_errors , types and translation schemas

* updated fixed chunks transfer on windows

* bye bye old net

* small updates for the new net

* settings and tests

* net lib first draft

* allow more than 1 P2P instances on from same pc

* improved shadnet replies

* possible fix for jojo

* cleanup

* move net files to libaries/net

* fixed macOS tests

* mighty clang

* fixed cmakelists

* don't cosume inviation event on client (test code)

* fix macOS tests

* Large multi-file rework for Signaling and Matching2 network handling / optimization.  Consolidates Matching2-signaling and signaling to one process (signaling_handler)

General cleanup and consolidation of matching2 context management > nphandler

Removal of a lot

SIgnaling overall structure cleanup.

Matching2: added get and set roommemberdatainternal and the proper type / shadnet call / request / reply.

Consolidated signaling/matching2 typedefs to np_types2

Utilized Codex as a reviewer and for general cleanup.

* all mighty clang

* fixed member attributes

* fixed MAPPED address for matching signaling

* Fixed sceNpMatching2SignalingGetLocalNetInfo

* fixups on net core/library based on fw

* improved errno

* spilt p2p to net_p2p

* some cleanups

* fix for localinfo

* clang

* fix up

* cleanups

* cleanups

* added KernelFstat

* fixed fstat

* one more fix for fstat...

* cleanup old deprecated code

* cleanup

* General logging cleanup across NP changes.  Thanks Stephen!

* cleanup based on stephen's suggestions

* fixup

* fixing tests

* blah

* fixup

* improve memory pools

* cleaunp

* force https disable option

* clang

* @stephen's fixes

* added network field in settings and ported old network ones

---------

Co-authored-by: Metr1k <[email protected]>
This commit is contained in:
georgemoralis
2026-10-06 19:14:56 +03:00
committed by GitHub
co-authored by Metr1k
parent 235dcabf94
commit 2a2f1003b0
96 changed files with 22110 additions and 7944 deletions
+42 -19
View File
@@ -376,32 +376,15 @@ set(NETWORK_LIBS src/core/libraries/network/http.cpp
src/core/libraries/network/http_error.h
src/core/libraries/network/http2.cpp
src/core/libraries/network/http2.h
src/core/libraries/network/net.cpp
src/core/libraries/network/netctl.cpp
src/core/libraries/network/netctl.h
src/core/libraries/network/net_ctl_obj.cpp
src/core/libraries/network/net_ctl_obj.h
src/core/libraries/network/net_ctl_codes.h
src/core/libraries/network/net_util.cpp
src/core/libraries/network/net_util.h
src/core/libraries/network/net_epoll.cpp
src/core/libraries/network/net_epoll.h
src/core/libraries/network/net_resolver.cpp
src/core/libraries/network/net_resolver.h
src/core/libraries/network/net_upnp.cpp
src/core/libraries/network/net_upnp.h
src/core/libraries/network/net_error.h
src/core/libraries/network/net.h
src/core/libraries/network/ssl.cpp
src/core/libraries/network/ssl.h
src/core/libraries/network/ssl2.cpp
src/core/libraries/network/ssl2.h
src/core/libraries/network/sys_net.cpp
src/core/libraries/network/sys_net.h
src/core/libraries/network/posix_sockets.cpp
src/core/libraries/network/p2p_sockets.cpp
src/core/libraries/network/unix_sockets.cpp
src/core/libraries/network/sockets.h
)
set(AVPLAYER_LIB src/core/libraries/avplayer/avplayer_common.cpp
@@ -666,6 +649,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/object_manager.h
src/core/libraries/np/np_handler.cpp
src/core/libraries/np/np_handler.h
src/core/libraries/np/signaling_handler.cpp
src/core/libraries/np/signaling_handler.h
src/core/libraries/np/np_commerce/np_commerce.cpp
src/core/libraries/np/np_commerce/np_commerce.h
src/core/libraries/np/np_commerce/np_commerce_ui.cpp
@@ -683,8 +668,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_signaling/np_signaling_state.h
src/core/libraries/np/np_signaling/np_signaling_helpers.cpp
src/core/libraries/np/np_signaling/np_signaling_helpers.h
src/core/libraries/np/np_signaling/np_signaling_stubs.cpp
src/core/libraries/np/np_signaling/np_signaling_stubs.h
src/core/libraries/np/np_signaling/np_signaling_transport.cpp
src/core/libraries/np/np_signaling/np_signaling_transport.h
src/core/libraries/np/np_web_api/np_web_api.cpp
src/core/libraries/np/np_web_api/np_web_api.h
src/core/libraries/np/np_web_api/np_web_api_internal.cpp
@@ -862,6 +847,42 @@ if (ENABLE_DISCORD_RPC)
list(APPEND COMMON src/common/discord_rpc_handler.cpp src/common/discord_rpc_handler.h)
endif()
set(NET_CORE src/core/net/guest_net.cpp
src/core/net/guest_net.h
src/core/net/host_net.cpp
src/core/net/host_net.h
src/core/net/p2p_codec.cpp
src/core/net/p2p_codec.h
src/core/net/p2p_crypto.cpp
src/core/net/p2p_crypto.h
src/core/net/p2p_tcp.cpp
src/core/net/p2p_tcp.h
src/core/net/p2p_transport.cpp
src/core/net/p2p_transport.h
)
set(NET_LIB src/core/libraries/net/net.cpp
src/core/libraries/net/net.h
src/core/libraries/net/net_error.h
src/core/libraries/net/net_kernel.cpp
src/core/libraries/net/net_kernel.h
src/core/libraries/net/net_log.cpp
src/core/libraries/net/net_log.h
src/core/libraries/net/net_p2p.cpp
src/core/libraries/net/net_p2p.h
src/core/libraries/net/net_p2p_ioctl.cpp
src/core/libraries/net/net_p2p_ioctl.h
src/core/libraries/net/net_resolver.cpp
src/core/libraries/net/net_resolver.h
src/core/libraries/net/net_translate.cpp
src/core/libraries/net/net_translate.h
src/core/libraries/net/net_types.h
src/core/libraries/net/net_util.cpp
src/core/libraries/net/net_util.h
src/core/libraries/net/net_upnp.cpp
src/core/libraries/net/net_upnp.h
)
set(CORE src/core/aerolib/stubs.cpp
src/core/aerolib/stubs.h
src/core/aerolib/aerolib.cpp
@@ -973,6 +994,8 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/user_manager.h
src/core/user_settings.cpp
src/core/user_settings.h
${NET_CORE}
${NET_LIB}
)
if (ARCHITECTURE STREQUAL "x86_64")
+55 -1
View File
@@ -223,6 +223,7 @@ void EmulatorSettingsImpl::SetAddonInstallDir(const std::filesystem::path& dir)
// ── Game-specific override management ────────────────────────────────
void EmulatorSettingsImpl::ClearGameSpecificOverrides() {
ClearGroupOverrides(m_general);
ClearGroupOverrides(m_network);
ClearGroupOverrides(m_log);
ClearGroupOverrides(m_debug);
ClearGroupOverrides(m_input);
@@ -242,6 +243,22 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) {
json generalObj = json::object();
SaveGroupGameSpecific(m_general, generalObj);
json networkObj = json::object();
SaveGroupGameSpecific(m_network, networkObj);
j["Network"] = networkObj;
json existing = json::object();
if (std::ifstream existingIn{path}; existingIn.good()) {
try {
existingIn >> existing;
} catch (...) {
existing = json::object();
}
}
if (existing.contains("General") && existing["General"].is_object()) {
SyncLegacyNetworkKeys(existing["General"], generalObj, networkObj);
}
j["General"] = generalObj;
json logObj = json::object();
@@ -285,6 +302,7 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) {
json j;
j["General"] = m_general;
j["Network"] = m_network;
j["Log"] = m_log;
j["Debug"] = m_debug;
j["Input"] = m_input;
@@ -309,6 +327,9 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) {
else
existing[section] = val;
}
if (existing["General"].is_object()) {
SyncLegacyNetworkKeys(existing["General"], existing["General"], j["Network"]);
}
std::ofstream out(path);
if (!out) {
@@ -324,6 +345,29 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) {
}
}
void EmulatorSettingsImpl::ApplyLegacyNetworkKeys(const json& general) {
json current = m_network;
bool found = false;
for (const auto& item : m_network.GetOverrideableFields()) {
if (general.contains(item.key)) {
current[item.key] = general.at(item.key);
found = true;
}
}
if (found) {
m_network = current.get<NetworkSettings>();
}
}
void EmulatorSettingsImpl::SyncLegacyNetworkKeys(const json& old_general, json& general,
const json& network) {
for (const auto& [key, value] : network.items()) {
if (old_general.contains(key)) {
general[key] = value;
}
}
}
// ── Load ──────────────────────────────────────────────────────────────
bool EmulatorSettingsImpl::Load(const std::string& serial) {
@@ -349,6 +393,10 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) {
};
mergeGroup(m_general, "General");
mergeGroup(m_network, "Network");
if (gj.contains("General") && gj["General"].is_object()) {
ApplyLegacyNetworkKeys(gj["General"]);
}
mergeGroup(m_log, "Log");
mergeGroup(m_debug, "Debug");
mergeGroup(m_input, "Input");
@@ -418,6 +466,10 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) {
if (gj.contains("General"))
ApplyGroupOverrides(m_general, gj.at("General"), changed);
if (gj.contains("Network"))
ApplyGroupOverrides(m_network, gj.at("Network"), changed);
if (gj.contains("General"))
ApplyGroupOverrides(m_network, gj.at("General"), changed);
if (gj.contains("Log"))
ApplyGroupOverrides(m_log, gj.at("Log"), changed);
if (gj.contains("Debug"))
@@ -452,6 +504,7 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) {
void EmulatorSettingsImpl::SetDefaultValues() {
m_general = GeneralSettings{};
m_network = NetworkSettings{};
m_log = LogSettings{};
m_debug = DebugSettings{};
m_input = InputSettings{};
@@ -488,7 +541,7 @@ bool EmulatorSettingsImpl::TransferSettings() {
setFromToml(s.discord_rpc_enabled, general, "enableDiscordRPC");
setFromToml(s.show_splash, general, "showSplash");
setFromToml(s.trophy_notification_side, general, "sideTrophy");
setFromToml(s.connected_to_network, general, "isConnectedToNetwork");
setFromToml(m_network.connected_to_network, general, "isConnectedToNetwork");
setFromToml(s.sys_modules_dir, general, "sysModulesPath");
setFromToml(s.font_dir, general, "fontsPath");
// setFromToml(, general, "userName");
@@ -710,6 +763,7 @@ std::vector<std::string> EmulatorSettingsImpl::GetAllOverrideableKeys() const {
keys.push_back(item.key);
};
addGroup(m_general.GetOverrideableFields());
addGroup(m_network.GetOverrideableFields());
addGroup(m_log.GetOverrideableFields());
addGroup(m_debug.GetOverrideableFields());
addGroup(m_input.GetOverrideableFields());
+55 -28
View File
@@ -189,23 +189,16 @@ struct GeneralSettings {
Setting<bool> dev_kit_mode{false};
Setting<int> extra_dmem_in_mbytes{0};
Setting<int> extra_fmem_in_mbytes{0};
Setting<bool> shad_net_enabled{false};
Setting<bool> trophy_popup_disabled{false};
Setting<double> trophy_notification_duration{6.0};
Setting<std::string> trophy_notification_side{"right"};
Setting<bool> show_splash{false};
Setting<bool> connected_to_network{false};
Setting<bool> discord_rpc_enabled{false};
Setting<bool> show_fps_counter{false};
Setting<int> console_language{1};
Setting<int> big_picture_scale{1000};
Setting<int> big_picture_folder_depth{2};
Setting<std::string> shadnet_server{"srv.shadps4.net:31313"};
Setting<std::string> shadnet_webapi_server{"http://srv.shadps4.net:31315"};
Setting<std::string> signaling_info{};
Setting<bool> enable_upnp{true};
Setting<bool> redzone_patches{false};
// return a vector of override descriptors (runtime, but tiny)
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
@@ -216,7 +209,6 @@ struct GeneralSettings {
&GeneralSettings::extra_dmem_in_mbytes),
make_override<GeneralSettings>("extra_fmem_in_mbytes",
&GeneralSettings::extra_fmem_in_mbytes),
make_override<GeneralSettings>("shad_net_enabled", &GeneralSettings::shad_net_enabled),
make_override<GeneralSettings>("trophy_popup_disabled",
&GeneralSettings::trophy_popup_disabled),
make_override<GeneralSettings>("trophy_notification_duration",
@@ -224,28 +216,52 @@ struct GeneralSettings {
make_override<GeneralSettings>("show_splash", &GeneralSettings::show_splash),
make_override<GeneralSettings>("trophy_notification_side",
&GeneralSettings::trophy_notification_side),
make_override<GeneralSettings>("connected_to_network",
&GeneralSettings::connected_to_network),
make_override<GeneralSettings>("console_language", &GeneralSettings::console_language),
make_override<GeneralSettings>("shadnet_server", &GeneralSettings::shadnet_server),
make_override<GeneralSettings>("shadnet_webapi_server",
&GeneralSettings::shadnet_webapi_server),
make_override<GeneralSettings>("signaling_info", &GeneralSettings::signaling_info),
make_override<GeneralSettings>("enable_upnp", &GeneralSettings::enable_upnp),
make_override<GeneralSettings>("redzone_patches", &GeneralSettings::redzone_patches)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir,
sys_modules_dir, font_dir, volume_slider, neo_mode, dev_kit_mode,
extra_dmem_in_mbytes, extra_fmem_in_mbytes, shad_net_enabled,
extra_dmem_in_mbytes, extra_fmem_in_mbytes,
trophy_popup_disabled, trophy_notification_duration, show_splash,
trophy_notification_side, connected_to_network,
discord_rpc_enabled, show_fps_counter, console_language,
big_picture_scale, big_picture_folder_depth, shadnet_server,
shadnet_webapi_server, signaling_info, enable_upnp,
trophy_notification_side, discord_rpc_enabled, show_fps_counter,
console_language, big_picture_scale, big_picture_folder_depth,
redzone_patches)
// -------------------------------
// Network settings
// -------------------------------
struct NetworkSettings {
Setting<bool> connected_to_network{false};
Setting<bool> shad_net_enabled{false};
Setting<std::string> shadnet_server{"srv.shadps4.net:31313"};
Setting<std::string> shadnet_webapi_server{"http://srv.shadps4.net:31315"};
Setting<std::string> signaling_info{};
Setting<bool> enable_upnp{true};
Setting<int> p2p_port{3658}; // 0 is any free port, 3658 is the default PS4 port. With UPnP
// enabled it is forwarded on the router.
Setting<bool> disable_https{false};
std::vector<OverrideItem> GetOverrideableFields() const {
return std::vector<OverrideItem>{
make_override<NetworkSettings>("connected_to_network",
&NetworkSettings::connected_to_network),
make_override<NetworkSettings>("shad_net_enabled", &NetworkSettings::shad_net_enabled),
make_override<NetworkSettings>("shadnet_server", &NetworkSettings::shadnet_server),
make_override<NetworkSettings>("shadnet_webapi_server",
&NetworkSettings::shadnet_webapi_server),
make_override<NetworkSettings>("signaling_info", &NetworkSettings::signaling_info),
make_override<NetworkSettings>("enable_upnp", &NetworkSettings::enable_upnp),
make_override<NetworkSettings>("p2p_port", &NetworkSettings::p2p_port),
make_override<NetworkSettings>("disable_https", &NetworkSettings::disable_https)};
}
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NetworkSettings, connected_to_network, shad_net_enabled,
shadnet_server, shadnet_webapi_server, signaling_info,
enable_upnp, p2p_port, disable_https)
// -------------------------------
// Log settings
// -------------------------------
@@ -547,6 +563,7 @@ public:
private:
GeneralSettings m_general{};
NetworkSettings m_network{};
LogSettings m_log{};
DebugSettings m_debug{};
InputSettings m_input{};
@@ -588,12 +605,18 @@ private:
}
static void PrintChangedSummary(const std::vector<std::string>& changed);
void ApplyLegacyNetworkKeys(const nlohmann::json& general);
static void SyncLegacyNetworkKeys(const nlohmann::json& old_general, nlohmann::json& general,
const nlohmann::json& network);
public:
// Add these getters to access overrideable fields
std::vector<OverrideItem> GetGeneralOverrideableFields() const {
return m_general.GetOverrideableFields();
}
std::vector<OverrideItem> GetNetworkOverrideableFields() const {
return m_network.GetOverrideableFields();
}
std::vector<OverrideItem> GetDebugOverrideableFields() const {
return m_debug.GetOverrideableFields();
}
@@ -637,14 +660,14 @@ public:
SETTING_FORWARD(m_general, ExtraDmemInMBytes, extra_dmem_in_mbytes)
SETTING_FORWARD(m_general, ExtraFmemInMBytes, extra_fmem_in_mbytes)
bool IsShadNetEnabled() const {
return m_general.shad_net_enabled.get(m_configMode) &&
return m_network.shad_net_enabled.get(m_configMode) &&
!m_shadnet_session_disabled.load(std::memory_order_relaxed);
}
void SetShadNetEnabled(bool v, bool specific = false) {
m_general.shad_net_enabled.set(v, specific);
m_network.shad_net_enabled.set(v, specific);
}
bool IsShadNetEnabledSetting() const {
return m_general.shad_net_enabled.get(m_configMode);
return m_network.shad_net_enabled.get(m_configMode);
}
void SetShadNetSessionDisabled(bool v) {
m_shadnet_session_disabled.store(v, std::memory_order_relaxed);
@@ -656,18 +679,22 @@ public:
SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration)
SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side)
SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash)
SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network)
SETTING_FORWARD_BOOL(m_general, DiscordRPCEnabled, discord_rpc_enabled)
SETTING_FORWARD_BOOL(m_general, ShowFpsCounter, show_fps_counter)
SETTING_FORWARD(m_general, ConsoleLanguage, console_language)
SETTING_FORWARD(m_general, BigPictureScale, big_picture_scale)
SETTING_FORWARD(m_general, BigPictureFolderDepth, big_picture_folder_depth)
SETTING_FORWARD(m_general, ShadNetServer, shadnet_server)
SETTING_FORWARD(m_general, ShadNetWebApiServer, shadnet_webapi_server)
SETTING_FORWARD(m_general, SignalingInfo, signaling_info)
SETTING_FORWARD_BOOL(m_general, UPnPEnabled, enable_upnp)
SETTING_FORWARD_BOOL(m_general, RedZonePatchingEnabled, redzone_patches)
// Network settings
SETTING_FORWARD_BOOL(m_network, ConnectedToNetwork, connected_to_network)
SETTING_FORWARD(m_network, ShadNetServer, shadnet_server)
SETTING_FORWARD(m_network, ShadNetWebApiServer, shadnet_webapi_server)
SETTING_FORWARD(m_network, SignalingInfo, signaling_info)
SETTING_FORWARD_BOOL(m_network, UPnPEnabled, enable_upnp)
SETTING_FORWARD(m_network, P2PPort, p2p_port)
SETTING_FORWARD_BOOL(m_network, ForcedHttpsDisabled, disable_https)
// Log settings
SETTING_FORWARD_BOOL(m_log, LogAppend, append)
SETTING_FORWARD_BOOL(m_log, LogEnable, enable)
-24
View File
@@ -602,30 +602,6 @@ std::vector<int> HandleTable::GetSocketHandles() {
return handles;
}
File* HandleTable::GetEpoll(int d) {
std::scoped_lock lock{m_mutex};
if (d < 0 || d >= m_files.size()) {
return nullptr;
}
auto file = m_files.at(d);
if (file->type != Core::FileSys::FileType::Epoll) {
return nullptr;
}
return file;
}
File* HandleTable::GetResolver(int d) {
std::scoped_lock lock{m_mutex};
if (d < 0 || d >= m_files.size()) {
return nullptr;
}
auto file = m_files.at(d);
if (file->type != Core::FileSys::FileType::Resolver) {
return nullptr;
}
return file;
}
File* HandleTable::GetFile(const std::filesystem::path& host_name) {
std::scoped_lock lock{m_mutex};
for (auto* file : m_files) {
-13
View File
@@ -16,12 +16,6 @@
#include "core/file_sys/directories/base_directory.h"
#include "core/file_sys/ifile.h"
namespace Libraries::Net {
struct Socket;
struct Epoll;
struct Resolver;
} // namespace Libraries::Net
namespace Core::FileSys {
/// Builds the path of an overlay that sits next to a game
@@ -131,8 +125,6 @@ enum class FileType {
Directory,
Device,
Socket,
Epoll,
Resolver,
Equeue
};
@@ -145,9 +137,6 @@ struct File {
std::mutex m_mutex;
std::shared_ptr<Directories::BaseDirectory> directory; // only valid for type == Directory
std::shared_ptr<Devices::BaseDevice> device; // only valid for type == Device
std::shared_ptr<Libraries::Net::Socket> socket; // only valid for type == Socket
std::shared_ptr<Libraries::Net::Epoll> epoll; // only valid for type == Epoll
std::shared_ptr<Libraries::Net::Resolver> resolver; // only valid for type == Resolver
bool IsBackendOpen() const {
return handle && handle->IsOpen();
@@ -196,8 +185,6 @@ public:
File* GetFile(int d);
File* GetSocket(int d);
std::vector<int> GetSocketHandles();
File* GetEpoll(int d);
File* GetResolver(int d);
File* GetFile(const std::filesystem::path& host_name);
int GetFileDescriptor(File* file);
@@ -4,7 +4,7 @@
#pragma once
#include "common/types.h"
#include "core/libraries/network/net.h"
#include "core/libraries/net/net.h"
#include "core/libraries/system/userservice.h"
namespace Core::Loader {
@@ -11,8 +11,8 @@
#include "common/logging/log.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/invitation_dialog/invitation_dialog_ui.h"
#include "core/libraries/net/net.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/ssl.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
+88 -275
View File
@@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <climits>
#include <cstdint>
#include <map>
#include <ranges>
#include <thread>
#include <vector>
#include <magic_enum/magic_enum.hpp>
#include "common/assert.h"
@@ -26,7 +31,7 @@
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/posix_error.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/sockets.h"
#include "core/libraries/net/net_kernel.h"
#include "core/memory.h"
#include "kernel.h"
@@ -279,10 +284,11 @@ s32 PS4_SYSV_ABI close(s32 fd) {
*__Error() = POSIX_EPERM;
return -1;
}
if (file->type == Core::FileSys::FileType::Socket) {
return Libraries::Net::KernelClose(fd);
}
if (file->type == Core::FileSys::FileType::Regular) {
file->handle.reset();
} else if (file->type == Core::FileSys::FileType::Socket) {
file->socket->Close();
}
file->is_opened = false;
LOG_INFO(Kernel_Fs, "Closing {}", file->m_guest_name);
@@ -312,6 +318,9 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) {
return -1;
}
if (file->type == Core::FileSys::FileType::Socket) {
return Libraries::Net::KernelWrite(fd, buf, nbytes);
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->write(buf, nbytes);
@@ -320,9 +329,6 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) {
return -1;
}
return result;
} else if (file->type == Core::FileSys::FileType::Socket) {
// Socket functions handle errnos internally.
return file->socket->SendPacket(buf, nbytes, 0, nullptr, 0);
} else if (file->type == Core::FileSys::FileType::Directory) {
*__Error() = POSIX_EBADF;
return -1;
@@ -532,6 +538,9 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) {
return -1;
}
if (file->type == Core::FileSys::FileType::Socket) {
return Libraries::Net::KernelRead(fd, buf, nbytes);
}
std::scoped_lock lk{file->m_mutex};
if (file->type == Core::FileSys::FileType::Device) {
s64 result = file->device->read(buf, nbytes);
@@ -547,9 +556,6 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) {
return -1;
}
return result;
} else if (file->type == Core::FileSys::FileType::Socket) {
// Socket functions handle errnos internally.
return file->socket->ReceivePacket(buf, nbytes, 0, nullptr, 0);
}
if (file->IsWriteOnly()) {
@@ -840,11 +846,8 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) {
return result;
}
case Core::FileSys::FileType::Socket: {
// Socket functions handle errnos internally
return file->socket->fstat(sb);
return Libraries::Net::KernelFstat(fd, sb);
}
case Core::FileSys::FileType::Epoll:
case Core::FileSys::FileType::Resolver:
case Core::FileSys::FileType::Equeue: {
LOG_ERROR(Kernel_Fs, "(STUBBED) file type {}", magic_enum::enum_name(file->type.load()));
break;
@@ -1271,56 +1274,52 @@ s32 PS4_SYSV_ABI sceKernelUnlink(const char* path) {
return result;
}
#ifdef _WIN32
namespace {
typedef struct {
u64 fds_bits[16];
} fd_set_posix;
struct GuestFdSet {
u64 bits[1024 / 64];
};
constexpr s32 kGuestFdSetSize = 1024;
static void FD_SET_POSIX(s32 fd, fd_set_posix* set) {
set->fds_bits[fd / (8 * sizeof(u64))] |= (1ULL << (fd % (8 * sizeof(u64))));
bool GuestFdIsSet(const GuestFdSet* set, s32 fd) {
return set != nullptr && (set->bits[fd / 64] >> (fd % 64) & 1) != 0;
}
static void FD_CLR_POSIX(s32 fd, fd_set_posix* set) {
set->fds_bits[fd / (8 * sizeof(u64))] &= ~(1ULL << (fd % (8 * sizeof(u64))));
void GuestFdSetBit(GuestFdSet* set, s32 fd) {
set->bits[fd / 64] |= 1ull << (fd % 64);
}
static bool FD_ISSET_POSIX(s32 fd, fd_set_posix* set) {
return (set->fds_bits[fd / (8 * sizeof(u64))] & (1ULL << (fd % (8 * sizeof(u64))))) != 0;
}
} // namespace
static void FD_ZERO_POSIX(fd_set_posix* set) {
std::memset(set, 0, sizeof(fd_set_posix));
}
s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* writefds,
fd_set_posix* exceptfds, OrbisKernelTimeval* timeout) {
s32 PS4_SYSV_ABI posix_select(s32 nfds, GuestFdSet* readfds, GuestFdSet* writefds,
GuestFdSet* exceptfds, OrbisKernelTimeval* timeout) {
LOG_DEBUG(Kernel_Fs, "nfds = {}, readfds = {}, writefds = {}, exceptfds = {}, timeout = {}",
nfds, fmt::ptr(readfds), fmt::ptr(writefds), fmt::ptr(exceptfds), fmt::ptr(timeout));
if (nfds < 0 || nfds > kGuestFdSetSize) {
*__Error() = POSIX_EINVAL;
return -1;
}
s64 timeout_us = -1; // no limit
if (timeout != nullptr) {
if (timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec >= 1'000'000) {
*__Error() = POSIX_EINVAL;
return -1;
}
timeout_us = timeout->tv_sec > (INT64_MAX / 1'000'000 - 1)
? INT64_MAX
: timeout->tv_sec * 1'000'000 + timeout->tv_usec;
}
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
fd_set read_host = {}, write_host = {}, except_host = {};
FD_ZERO(&read_host);
FD_ZERO(&write_host);
FD_ZERO(&except_host);
fd_set_posix read_ready, write_ready, except_ready;
FD_ZERO_POSIX(&read_ready);
FD_ZERO_POSIX(&write_ready);
FD_ZERO_POSIX(&except_ready);
std::map<s32, s32> host_to_guest;
s32 socket_max_fd = -1;
GuestFdSet read_ready{}, write_ready{};
s32 ready = 0;
std::vector<Core::Net::SelectEntry> sockets;
for (s32 i = 0; i < nfds; ++i) {
bool want_read = readfds && FD_ISSET_POSIX(i, readfds);
bool want_write = writefds && FD_ISSET_POSIX(i, writefds);
bool want_except = exceptfds && FD_ISSET_POSIX(i, exceptfds);
if (!(want_read || want_write || want_except)) {
const bool want_read = GuestFdIsSet(readfds, i);
const bool want_write = GuestFdIsSet(writefds, i);
if (!want_read && !want_write && !GuestFdIsSet(exceptfds, i)) {
continue;
}
auto* file = h->GetFile(i);
if (!file || ((file->type == Core::FileSys::FileType::Regular && !file->IsBackendOpen()) ||
(file->type == Core::FileSys::FileType::Socket && !file->is_opened))) {
@@ -1328,246 +1327,60 @@ s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* wri
*__Error() = POSIX_EBADF;
return -1;
}
s32 native_fd = -1;
switch (file->type) {
case Core::FileSys::FileType::Regular:
if (auto* host = file->GetHostFile()) {
native_fd = static_cast<s32>(host->GetFileMapping());
if (file->type == Core::FileSys::FileType::Socket) {
const u32 interest = (want_read ? Core::Net::Host::EvIn : 0u) |
(want_write ? Core::Net::Host::EvOut : 0u);
if (interest != 0) {
sockets.push_back({i, interest, 0});
}
break;
case Core::FileSys::FileType::Socket: {
auto sock = file->socket->Native();
native_fd = sock ? static_cast<s32>(*sock) : -1;
break;
}
case Core::FileSys::FileType::Device:
native_fd = -1;
break;
default:
UNREACHABLE();
break;
}
if (file->type == Core::FileSys::FileType::Regular ||
file->type == Core::FileSys::FileType::Device) {
// Disk files always ready
// For devices, stdin (fd 0) is never read-ready.
if (want_read && i != 0) {
FD_SET_POSIX(i, &read_ready);
}
if (want_write) {
FD_SET_POSIX(i, &write_ready);
}
// exceptfds not supported on regular files
} else if (file->type == Core::FileSys::FileType::Socket) {
if (want_read) {
FD_SET(native_fd, &read_host);
}
if (want_write) {
FD_SET(native_fd, &write_host);
}
if (want_except) {
FD_SET(native_fd, &except_host);
}
socket_max_fd = std::max(socket_max_fd, native_fd);
}
if (native_fd == -1) {
LOG_WARNING(Kernel_Fs, "Unsupported fd {}", i);
continue;
}
host_to_guest[native_fd] = i;
if (want_read && i != 0) {
GuestFdSetBit(&read_ready, i);
++ready;
}
if (want_write) {
GuestFdSetBit(&write_ready, i);
++ready;
}
}
LOG_DEBUG(Kernel_Fs,
"Before select(): read_host.fd_count = {}, write_host.fd_count = {}, "
"except_host.fd_count = {}",
read_host.fd_count, write_host.fd_count, except_host.fd_count);
if (read_host.fd_count == 0 && write_host.fd_count == 0 && except_host.fd_count == 0) {
LOG_WARNING(Kernel_Fs, "No sockets in fd_sets, select() will return immediately");
if (ready > 0) {
timeout_us = 0;
}
if (!sockets.empty()) {
const s64 n = Libraries::Net::KernelSelect(sockets, timeout_us);
if (n < 0) {
return -1; // errno set
}
ready += static_cast<s32>(n);
} else if (ready == 0 && timeout_us != 0) {
// Nothing that can become ready: select() is a sleep (forever without a timeout).
do {
std::this_thread::sleep_for(
std::chrono::microseconds{timeout_us < 0 ? 1'000'000 : timeout_us});
} while (timeout_us < 0);
}
for (const auto& e : sockets) {
if (e.ready & Core::Net::Host::EvIn) {
GuestFdSetBit(&read_ready, e.id);
}
if (e.ready & Core::Net::Host::EvOut) {
GuestFdSetBit(&write_ready, e.id);
}
}
if (readfds) {
FD_ZERO_POSIX(readfds);
*readfds = read_ready;
}
if (writefds) {
FD_ZERO_POSIX(writefds);
*writefds = write_ready;
}
if (exceptfds) {
FD_ZERO_POSIX(exceptfds);
*exceptfds = {};
}
s32 result = 0;
if (socket_max_fd != -1) {
timeval tv = {};
timeval* tv_ptr = nullptr;
if (timeout) {
tv.tv_sec = timeout->tv_sec;
tv.tv_usec = timeout->tv_usec;
tv_ptr = &tv;
}
result = select(0, read_host.fd_count > 0 ? &read_host : nullptr,
write_host.fd_count > 0 ? &write_host : nullptr,
except_host.fd_count > 0 ? &except_host : nullptr, tv_ptr);
if (result == SOCKET_ERROR) {
s32 err = WSAGetLastError();
LOG_ERROR(Kernel_Fs, "select() failed with error {}", err);
switch (err) {
case WSAEFAULT:
*__Error() = POSIX_EFAULT;
break;
case WSAEINVAL:
*__Error() = POSIX_EINVAL;
break;
case WSAENOBUFS:
*__Error() = POSIX_ENOBUFS;
break;
default:
LOG_ERROR(Kernel_Fs, "Unhandled error case {}", err);
break;
}
return -1;
}
for (s32 i = 0; i < read_host.fd_count; ++i) {
s32 fd = static_cast<s32>(read_host.fd_array[i]);
FD_SET_POSIX(host_to_guest[fd], readfds);
}
for (s32 i = 0; i < write_host.fd_count; ++i) {
s32 fd = static_cast<s32>(write_host.fd_array[i]);
FD_SET_POSIX(host_to_guest[fd], writefds);
}
for (s32 i = 0; i < except_host.fd_count; ++i) {
s32 fd = static_cast<s32>(except_host.fd_array[i]);
FD_SET_POSIX(host_to_guest[fd], exceptfds);
}
}
// Add regular/device files ready count
s32 disk_ready = 0;
for (s32 i = 0; i < nfds; ++i) {
if (FD_ISSET_POSIX(i, &read_ready)) {
FD_SET_POSIX(i, readfds);
disk_ready++;
}
if (FD_ISSET_POSIX(i, &write_ready)) {
FD_SET_POSIX(i, writefds);
disk_ready++;
}
}
return result + disk_ready;
return ready;
}
#else
s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
OrbisKernelTimeval* timeout) {
LOG_DEBUG(Kernel_Fs, "nfds = {}, readfds = {}, writefds = {}, exceptfds = {}, timeout = {}",
nfds, fmt::ptr(readfds), fmt::ptr(writefds), fmt::ptr(exceptfds), fmt::ptr(timeout));
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
fd_set read_host, write_host, except_host;
FD_ZERO(&read_host);
FD_ZERO(&write_host);
FD_ZERO(&except_host);
std::map<s32, s32> host_to_guest;
s32 max_fd = -1;
for (s32 i = 0; i < nfds; ++i) {
auto read = readfds && FD_ISSET(i, readfds);
auto write = writefds && FD_ISSET(i, writefds);
auto except = exceptfds && FD_ISSET(i, exceptfds);
if (read || write || except) {
auto* file = h->GetFile(i);
if (file == nullptr ||
((file->type == Core::FileSys::FileType::Regular && !file->IsBackendOpen()) ||
(file->type == Core::FileSys::FileType::Socket && !file->is_opened))) {
LOG_ERROR(Kernel_Fs, "fd {} is null or not opened", i);
*__Error() = POSIX_EBADF;
return -1;
}
s32 native_fd = [&] {
switch (file->type) {
case Core::FileSys::FileType::Regular:
if (auto* host = file->GetHostFile()) {
return static_cast<s32>(host->GetFileMapping());
}
return -1;
case Core::FileSys::FileType::Device:
return -1;
case Core::FileSys::FileType::Socket: {
auto sock = file->socket->Native();
// until P2P sockets contain a proper socket
return sock ? static_cast<s32>(*sock) : -1;
}
default:
UNREACHABLE();
}
}();
if (native_fd == -1) {
LOG_WARNING(Kernel_Fs, "Unsupported fd {}", i);
continue;
}
host_to_guest.emplace(native_fd, i);
max_fd = std::max(max_fd, native_fd);
if (read) {
FD_SET(native_fd, &read_host);
}
if (write) {
FD_SET(native_fd, &write_host);
}
if (except) {
FD_SET(native_fd, &except_host);
}
}
}
if (max_fd == -1) {
LOG_WARNING(Kernel_Fs, "all requested file descriptors are unsupported");
return 0;
}
s32 ret = select(max_fd + 1, &read_host, &write_host, &except_host, (timeval*)timeout);
if (ret > 0) {
if (readfds) {
FD_ZERO(readfds);
}
if (writefds) {
FD_ZERO(writefds);
}
if (exceptfds) {
FD_ZERO(exceptfds);
}
for (s32 i = 0; i < max_fd + 1; ++i) {
if (readfds && FD_ISSET(i, &read_host)) {
FD_SET(host_to_guest[i], readfds);
}
if (writefds && FD_ISSET(i, &write_host)) {
FD_SET(host_to_guest[i], writefds);
}
if (exceptfds && FD_ISSET(i, &except_host)) {
FD_SET(host_to_guest[i], exceptfds);
}
}
}
if (ret < 0) {
s32 error = errno;
LOG_ERROR(Kernel_Fs, "native select call failed with {} ({})", error,
Common::NativeErrorToString(error));
SetPosixErrno(error);
}
return ret;
}
#endif
void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", open);
+5 -3
View File
@@ -27,7 +27,8 @@
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/sys_net.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_kernel.h"
#ifdef _WIN64
#include <Rpc.h>
@@ -35,8 +36,6 @@
#include <uuid/uuid.h>
#endif
#include <common/singleton.h>
#include <core/libraries/network/net_error.h>
#include <core/libraries/network/sockets.h>
#include <core/linker.h>
#include "aio.h"
@@ -448,6 +447,9 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
static char const** kernel_environ = g_environment;
// Before any socket exists: net ids are file descriptors from the kernel's table.
Libraries::Net::InstallKernelIntegration();
Libraries::Kernel::RegisterFileSystem(sym);
Libraries::Kernel::RegisterTime(sym);
Libraries::Kernel::RegisterThreads(sym);
+1 -1
View File
@@ -31,9 +31,9 @@
#include "core/libraries/libs.h"
#include "core/libraries/mouse/mouse.h"
#include "core/libraries/move/move.h"
#include "core/libraries/net/net.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/http2.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/network/ssl.h"
#include "core/libraries/network/ssl2.h"
File diff suppressed because it is too large Load Diff
+255
View File
@@ -0,0 +1,255 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <functional>
#include <memory>
#include "common/types.h"
#include "core/libraries/net/net_types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Net {
OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
s32 PS4_SYSV_ABI sceNetAddrConfig6GetInfo();
s32 PS4_SYSV_ABI sceNetAddrConfig6Start();
s32 PS4_SYSV_ABI sceNetAddrConfig6Stop();
s32 PS4_SYSV_ABI sceNetAllocateAllRouteInfo();
s32 PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic();
s32 PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam();
s32 PS4_SYSV_ABI sceNetBandwidthControlGetIfParam();
s32 PS4_SYSV_ABI sceNetBandwidthControlGetPolicy();
s32 PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam();
s32 PS4_SYSV_ABI sceNetBandwidthControlSetIfParam();
s32 PS4_SYSV_ABI sceNetBandwidthControlSetPolicy();
s32 PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
s32 PS4_SYSV_ABI sceNetClearDnsCache();
s32 PS4_SYSV_ABI sceNetConfigAddArp();
s32 PS4_SYSV_ABI sceNetConfigAddArpWithInterface();
s32 PS4_SYSV_ABI sceNetConfigAddIfaddr();
s32 PS4_SYSV_ABI sceNetConfigAddMRoute();
s32 PS4_SYSV_ABI sceNetConfigAddRoute();
s32 PS4_SYSV_ABI sceNetConfigAddRoute6();
s32 PS4_SYSV_ABI sceNetConfigAddRouteWithInterface();
s32 PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces();
s32 PS4_SYSV_ABI sceNetConfigDelArp();
s32 PS4_SYSV_ABI sceNetConfigDelArpWithInterface();
s32 PS4_SYSV_ABI sceNetConfigDelDefaultRoute();
s32 PS4_SYSV_ABI sceNetConfigDelDefaultRoute6();
s32 PS4_SYSV_ABI sceNetConfigDelIfaddr();
s32 PS4_SYSV_ABI sceNetConfigDelIfaddr6();
s32 PS4_SYSV_ABI sceNetConfigDelMRoute();
s32 PS4_SYSV_ABI sceNetConfigDelRoute();
s32 PS4_SYSV_ABI sceNetConfigDelRoute6();
s32 PS4_SYSV_ABI sceNetConfigDownInterface();
s32 PS4_SYSV_ABI sceNetConfigEtherGetLinkMode();
s32 PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent();
s32 PS4_SYSV_ABI sceNetConfigEtherSetLinkMode();
s32 PS4_SYSV_ABI sceNetConfigFlushRoute();
s32 PS4_SYSV_ABI sceNetConfigGetDefaultRoute();
s32 PS4_SYSV_ABI sceNetConfigGetDefaultRoute6();
s32 PS4_SYSV_ABI sceNetConfigGetIfaddr();
s32 PS4_SYSV_ABI sceNetConfigGetIfaddr6();
s32 PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig();
s32 PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar();
s32 PS4_SYSV_ABI sceNetConfigRoutingStart();
s32 PS4_SYSV_ABI sceNetConfigRoutingStop();
s32 PS4_SYSV_ABI sceNetConfigSetDefaultRoute();
s32 PS4_SYSV_ABI sceNetConfigSetDefaultRoute6();
s32 PS4_SYSV_ABI sceNetConfigSetDefaultScope();
s32 PS4_SYSV_ABI sceNetConfigSetIfaddr();
s32 PS4_SYSV_ABI sceNetConfigSetIfaddr6();
s32 PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags();
s32 PS4_SYSV_ABI sceNetConfigSetIfFlags();
s32 PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6();
s32 PS4_SYSV_ABI sceNetConfigSetIfmtu();
s32 PS4_SYSV_ABI sceNetConfigUnsetIfFlags();
s32 PS4_SYSV_ABI sceNetConfigUpInterface();
s32 PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocCreate();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocJoin();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocLeave();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement();
s32 PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan();
s32 PS4_SYSV_ABI sceNetConfigWlanApStart();
s32 PS4_SYSV_ABI sceNetConfigWlanApStop();
s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery();
s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart();
s32 PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop();
s32 PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo();
s32 PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna();
s32 PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate();
s32 PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig();
s32 PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo();
s32 PS4_SYSV_ABI sceNetConfigWlanInfraLeave();
s32 PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin();
s32 PS4_SYSV_ABI sceNetConfigWlanScan();
s32 PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig();
s32 PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
s32 PS4_SYSV_ABI sceNetControl();
s32 PS4_SYSV_ABI sceNetDhcpdStart();
s32 PS4_SYSV_ABI sceNetDhcpdStop();
s32 PS4_SYSV_ABI sceNetDhcpGetAutoipInfo();
s32 PS4_SYSV_ABI sceNetDhcpGetInfo();
s32 PS4_SYSV_ABI sceNetDhcpGetInfoEx();
s32 PS4_SYSV_ABI sceNetDhcpStart();
s32 PS4_SYSV_ABI sceNetDhcpStop();
s32 PS4_SYSV_ABI sceNetDumpAbort();
s32 PS4_SYSV_ABI sceNetDumpCreate();
s32 PS4_SYSV_ABI sceNetDumpDestroy();
s32 PS4_SYSV_ABI sceNetDumpRead();
s32 PS4_SYSV_ABI sceNetDuplicateIpStart();
s32 PS4_SYSV_ABI sceNetDuplicateIpStop();
s32 PS4_SYSV_ABI sceNetEpollAbort(OrbisNetId eid, s32 flags);
s32 PS4_SYSV_ABI sceNetEpollControl(OrbisNetId eid, s32 op, OrbisNetId id,
OrbisNetEpollEvent* event);
OrbisNetId PS4_SYSV_ABI sceNetEpollCreate(const char* name, s32 flags);
s32 PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId eid);
s32 PS4_SYSV_ABI sceNetEpollWait(OrbisNetId eid, OrbisNetEpollEvent* events, s32 maxevents,
s32 timeout_us);
s32* PS4_SYSV_ABI sceNetErrnoLoc();
s32 PS4_SYSV_ABI sceNetEtherNtostr(const OrbisNetEtherAddr* n, char* str, u64 len);
s32 PS4_SYSV_ABI sceNetEtherStrton(const char* str, OrbisNetEtherAddr* n);
s32 PS4_SYSV_ABI sceNetEventCallbackCreate();
s32 PS4_SYSV_ABI sceNetEventCallbackDestroy();
s32 PS4_SYSV_ABI sceNetEventCallbackGetError();
s32 PS4_SYSV_ABI sceNetEventCallbackWaitCB();
s32 PS4_SYSV_ABI sceNetFreeAllRouteInfo();
s32 PS4_SYSV_ABI sceNetGetArpInfo();
s32 PS4_SYSV_ABI sceNetGetDns6Info(u8* info, s32 flags);
s32 PS4_SYSV_ABI sceNetGetDnsInfo(u32* info, s32 flags);
s32 PS4_SYSV_ABI sceNetGetIfList();
s32 PS4_SYSV_ABI sceNetGetIfListOnce();
const char* PS4_SYSV_ABI sceNetGetIfName(u32 index);
s32 PS4_SYSV_ABI sceNetGetIfnameNumList();
s32 PS4_SYSV_ABI sceNetGetMacAddress(OrbisNetEtherAddr* addr, s32 flags);
s32 PS4_SYSV_ABI sceNetGetMemoryPoolStats(s32 memid, OrbisNetMemoryPoolStats* stat);
s32 PS4_SYSV_ABI sceNetGetNameToIndex();
s32 PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
s32 PS4_SYSV_ABI sceNetGetRandom(u32* out);
s32 PS4_SYSV_ABI sceNetGetRouteInfo();
s32 PS4_SYSV_ABI sceNetGetSockInfo(OrbisNetId s, OrbisNetSockInfo* info, s32 n, s32 flags);
s32 PS4_SYSV_ABI sceNetGetSockInfo6();
s32 PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
s32 PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, s32 level, s32 optname, void* optval, u32* optlen);
s32 PS4_SYSV_ABI sceNetGetStatisticsInfo(OrbisNetStatisticsInfo* info, s32 flags);
s32 PS4_SYSV_ABI sceNetGetStatisticsInfoInternal();
void PS4_SYSV_ABI sceNetGetSystemTime(u64* out);
u32 PS4_SYSV_ABI sceNetHtonl(u32 host32);
u64 PS4_SYSV_ABI sceNetHtonll(u64 host64);
u16 PS4_SYSV_ABI sceNetHtons(u16 host16);
const char* PS4_SYSV_ABI sceNetInetNtop(s32 af, const void* src, char* dst, u32 size);
s32 PS4_SYSV_ABI sceNetInetNtopWithScopeId();
s32 PS4_SYSV_ABI sceNetInetPton(s32 af, const char* src, void* dst);
s32 PS4_SYSV_ABI sceNetInetPtonEx(s32 af, const char* src, void* dst, s32 flags);
s32 PS4_SYSV_ABI sceNetInetPtonWithScopeId();
s32 PS4_SYSV_ABI sceNetInfoDumpStart();
s32 PS4_SYSV_ABI sceNetInfoDumpStop();
s32 PS4_SYSV_ABI sceNetInit();
s32 PS4_SYSV_ABI sceNetInitParam();
s32 PS4_SYSV_ABI sceNetIoctl(OrbisNetId s, u64 cmd, void* data);
s32 PS4_SYSV_ABI sceNetListen(OrbisNetId s, s32 backlog);
void* PS4_SYSV_ABI sceNetMemoryAllocate(s64 size, s32 flags);
void PS4_SYSV_ABI sceNetMemoryFree(void* ptr);
u32 PS4_SYSV_ABI sceNetNtohl(u32 net32);
u64 PS4_SYSV_ABI sceNetNtohll(u64 net64);
u16 PS4_SYSV_ABI sceNetNtohs(u16 net16);
s32 PS4_SYSV_ABI sceNetPoolCreate(const char* name, s32 size, s32 flags);
s32 PS4_SYSV_ABI sceNetPoolDestroy(s32 pool_id);
s32 PS4_SYSV_ABI sceNetPppoeStart();
s32 PS4_SYSV_ABI sceNetPppoeStop();
s32 PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, s32 flags);
s32 PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, s32 flags, OrbisNetSockaddr* addr,
u32* paddrlen);
s32 PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, s32 flags);
s32 PS4_SYSV_ABI sceNetResolverAbort(OrbisNetId rid, s32 flags);
s32 PS4_SYSV_ABI sceNetResolverConnect();
s32 PS4_SYSV_ABI sceNetResolverConnectAbort();
s32 PS4_SYSV_ABI sceNetResolverConnectCreate();
s32 PS4_SYSV_ABI sceNetResolverConnectDestroy();
OrbisNetId PS4_SYSV_ABI sceNetResolverCreate(const char* name, s32 poolid, s32 flags);
s32 PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId rid);
s32 PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId rid, s32* status);
s32 PS4_SYSV_ABI sceNetResolverStartAton(OrbisNetId rid, const OrbisNetInAddr* addr, char* hostname,
s32 len, s32 timeout, s32 retry, s32 flags);
s32 PS4_SYSV_ABI sceNetResolverStartAton6();
s32 PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId rid, const char* hostname, OrbisNetInAddr* addr,
s32 timeout, s32 retry, s32 flags);
s32 PS4_SYSV_ABI sceNetResolverStartNtoa6();
s32 PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId rid, const char* hostname,
OrbisNetResolverInfo* info, s32 timeout,
s32 retry, s32 flags);
s32 PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx(OrbisNetId rid, const char* hostname,
OrbisNetResolverInfo* info, s32 timeout,
s32 retry, s32 flags);
s32 PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, s32 flags);
s32 PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, s32 flags);
s32 PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, s32 flags,
const OrbisNetSockaddr* addr, u32 addrlen);
s32 PS4_SYSV_ABI sceNetSetDns6Info(const u8* info, s32 flags);
s32 PS4_SYSV_ABI sceNetSetDns6InfoToKernel();
s32 PS4_SYSV_ABI sceNetSetDnsInfo(const u32* info, s32 flags);
s32 PS4_SYSV_ABI sceNetSetDnsInfoToKernel();
s32 PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, s32 level, s32 optname, const void* optval,
u32 optlen);
s32 PS4_SYSV_ABI sceNetShowIfconfig();
s32 PS4_SYSV_ABI sceNetShowIfconfigForBuffer();
s32 PS4_SYSV_ABI sceNetShowIfconfigWithMemory(s32 memid);
s32 PS4_SYSV_ABI sceNetShowNetstat();
s32 PS4_SYSV_ABI sceNetShowNetstatEx();
s32 PS4_SYSV_ABI sceNetShowNetstatExForBuffer();
s32 PS4_SYSV_ABI sceNetShowNetstatForBuffer();
s32 PS4_SYSV_ABI sceNetShowNetstatWithMemory(s32 memid);
s32 PS4_SYSV_ABI sceNetShowPolicy();
s32 PS4_SYSV_ABI sceNetShowPolicyWithMemory(s32 memid);
s32 PS4_SYSV_ABI sceNetShowRoute();
s32 PS4_SYSV_ABI sceNetShowRoute6();
s32 PS4_SYSV_ABI sceNetShowRoute6ForBuffer();
s32 PS4_SYSV_ABI sceNetShowRoute6WithMemory(s32 memid);
s32 PS4_SYSV_ABI sceNetShowRouteForBuffer();
s32 PS4_SYSV_ABI sceNetShowRouteWithMemory(s32 memid);
s32 PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, s32 how);
OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, s32 family, s32 type, s32 protocol);
s32 PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, s32 flags);
s32 PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s);
s32 PS4_SYSV_ABI sceNetSyncCreate();
s32 PS4_SYSV_ABI sceNetSyncDestroy();
s32 PS4_SYSV_ABI sceNetSyncGet();
s32 PS4_SYSV_ABI sceNetSyncSignal();
s32 PS4_SYSV_ABI sceNetSyncWait();
s32 PS4_SYSV_ABI sceNetSysctl();
s32 PS4_SYSV_ABI sceNetTerm();
void PS4_SYSV_ABI sceNetThreadExit();
s32 PS4_SYSV_ABI sceNetUsleep(s32 microseconds);
s32 PS4_SYSV_ABI Func_0E707A589F751C68();
s32 PS4_SYSV_ABI sceNetEmulationGet();
s32 PS4_SYSV_ABI sceNetEmulationSet();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
struct SystemHooks {
std::function<bool()> is_online;
std::function<bool(std::array<u8, 6>* mac)> mac_address;
std::function<u16()> p2p_port;
std::function<void(u16 bound_port)> p2p_started;
std::function<void(u16 bound_port)> p2p_stopped;
std::function<u32()> public_addr;
};
void SetSystemHooks(SystemHooks hooks);
SystemHooks GetSystemHooks();
void SetKernelErrnoHook(void (*hook)(int orbis_errno));
void ReleaseResolverPool(OrbisNetId rid);
} // namespace Libraries::Net
@@ -6,22 +6,40 @@
// net errno codes
constexpr int ORBIS_NET_EPERM = 1;
constexpr int ORBIS_NET_ENOENT = 2;
constexpr int ORBIS_NET_ESRCH = 3;
constexpr int ORBIS_NET_EINTR = 4;
constexpr int ORBIS_NET_EIO = 5;
constexpr int ORBIS_NET_ENXIO = 6;
constexpr int ORBIS_NET_E2BIG = 7;
constexpr int ORBIS_NET_ENOEXEC = 8;
constexpr int ORBIS_NET_EBADF = 9;
constexpr int ORBIS_NET_ECHILD = 10;
constexpr int ORBIS_NET_EDEADLK = 11;
constexpr int ORBIS_NET_ENOMEM = 12;
constexpr int ORBIS_NET_EACCES = 13;
constexpr int ORBIS_NET_EFAULT = 14;
constexpr int ORBIS_NET_ENOTBLK = 15;
constexpr int ORBIS_NET_EBUSY = 16;
constexpr int ORBIS_NET_EEXIST = 17;
constexpr int ORBIS_NET_EXDEV = 18;
constexpr int ORBIS_NET_ENODEV = 19;
constexpr int ORBIS_NET_ENOTDIR = 20;
constexpr int ORBIS_NET_EISDIR = 21;
constexpr int ORBIS_NET_EINVAL = 22;
constexpr int ORBIS_NET_ENFILE = 23;
constexpr int ORBIS_NET_EMFILE = 24;
constexpr int ORBIS_NET_ENOTTY = 25;
constexpr int ORBIS_NET_ETXTBSY = 26;
constexpr int ORBIS_NET_EFBIG = 27;
constexpr int ORBIS_NET_ENOSPC = 28;
constexpr int ORBIS_NET_ESPIPE = 29;
constexpr int ORBIS_NET_EROFS = 30;
constexpr int ORBIS_NET_EMLINK = 31;
constexpr int ORBIS_NET_EPIPE = 32;
constexpr int ORBIS_NET_EDOM = 33;
constexpr int ORBIS_NET_ERANGE = 34;
constexpr int ORBIS_NET_EAGAIN = 35;
constexpr int ORBIS_NET_EWOULDBLOCK = 35;
constexpr int ORBIS_NET_EWOULDBLOCK = ORBIS_NET_EAGAIN;
constexpr int ORBIS_NET_EINPROGRESS = 36;
constexpr int ORBIS_NET_EALREADY = 37;
constexpr int ORBIS_NET_ENOTSOCK = 38;
@@ -30,7 +48,10 @@ constexpr int ORBIS_NET_EMSGSIZE = 40;
constexpr int ORBIS_NET_EPROTOTYPE = 41;
constexpr int ORBIS_NET_ENOPROTOOPT = 42;
constexpr int ORBIS_NET_EPROTONOSUPPORT = 43;
constexpr int ORBIS_NET_ESOCKTNOSUPPORT = 44;
constexpr int ORBIS_NET_EOPNOTSUPP = 45;
constexpr int ORBIS_NET_ENOTSUP = ORBIS_NET_EOPNOTSUPP;
constexpr int ORBIS_NET_EPFNOSUPPORT = 46;
constexpr int ORBIS_NET_EAFNOSUPPORT = 47;
constexpr int ORBIS_NET_EADDRINUSE = 48;
constexpr int ORBIS_NET_EADDRNOTAVAIL = 49;
@@ -42,6 +63,7 @@ constexpr int ORBIS_NET_ECONNRESET = 54;
constexpr int ORBIS_NET_ENOBUFS = 55;
constexpr int ORBIS_NET_EISCONN = 56;
constexpr int ORBIS_NET_ENOTCONN = 57;
constexpr int ORBIS_NET_ESHUTDOWN = 58;
constexpr int ORBIS_NET_ETOOMANYREFS = 59;
constexpr int ORBIS_NET_ETIMEDOUT = 60;
constexpr int ORBIS_NET_ECONNREFUSED = 61;
@@ -50,13 +72,29 @@ constexpr int ORBIS_NET_ENAMETOOLONG = 63;
constexpr int ORBIS_NET_EHOSTDOWN = 64;
constexpr int ORBIS_NET_EHOSTUNREACH = 65;
constexpr int ORBIS_NET_ENOTEMPTY = 66;
constexpr int ORBIS_NET_EPROCLIM = 67;
constexpr int ORBIS_NET_EUSERS = 68;
constexpr int ORBIS_NET_EDQUOT = 69;
constexpr int ORBIS_NET_ESTALE = 70;
constexpr int ORBIS_NET_EREMOTE = 71;
constexpr int ORBIS_NET_EBADRPC = 72;
constexpr int ORBIS_NET_ERPCMISMATCH = 73;
constexpr int ORBIS_NET_EPROGUNAVAIL = 74;
constexpr int ORBIS_NET_EPROGMISMATCH = 75;
constexpr int ORBIS_NET_EPROCUNAVAIL = 76;
constexpr int ORBIS_NET_ENOLCK = 77;
constexpr int ORBIS_NET_ENOSYS = 78;
constexpr int ORBIS_NET_EFTYPE = 79;
constexpr int ORBIS_NET_EAUTH = 80;
constexpr int ORBIS_NET_ENEEDAUTH = 81;
constexpr int ORBIS_NET_EIDRM = 82;
constexpr int ORBIS_NET_ENOMSG = 83;
constexpr int ORBIS_NET_EOVERFLOW = 84;
constexpr int ORBIS_NET_ECANCELED = 85;
constexpr int ORBIS_NET_EPROTO = 92;
constexpr int ORBIS_NET_EADHOC = 160;
constexpr int ORBIS_NET_ERESERVED161 = 161;
constexpr int ORBIS_NET_ERESERVED162 = 162;
constexpr int ORBIS_NET_EINACTIVEDISABLED = 163;
constexpr int ORBIS_NET_ENODATA = 164;
constexpr int ORBIS_NET_EDESC = 165;
@@ -64,6 +102,7 @@ constexpr int ORBIS_NET_EDESCTIMEDOUT = 166;
constexpr int ORBIS_NET_ENETINTR = 167;
constexpr int ORBIS_NET_ENOTINIT = 200;
constexpr int ORBIS_NET_ENOLIBMEM = 201;
constexpr int ORBIS_NET_ETLS = 202;
constexpr int ORBIS_NET_ECALLBACK = 203;
constexpr int ORBIS_NET_EINTERNAL = 204;
constexpr int ORBIS_NET_ERETURN = 205;
@@ -74,6 +113,7 @@ constexpr int ORBIS_NET_RESOLVER_EINTERNAL = 220;
constexpr int ORBIS_NET_RESOLVER_EBUSY = 221;
constexpr int ORBIS_NET_RESOLVER_ENOSPACE = 222;
constexpr int ORBIS_NET_RESOLVER_EPACKET = 223;
constexpr int ORBIS_NET_RESOLVER_ERESERVED224 = 224;
constexpr int ORBIS_NET_RESOLVER_ENODNS = 225;
constexpr int ORBIS_NET_RESOLVER_ETIMEDOUT = 226;
constexpr int ORBIS_NET_RESOLVER_ENOSUPPORT = 227;
@@ -84,24 +124,45 @@ constexpr int ORBIS_NET_RESOLVER_ENOTIMPLEMENTED = 231;
constexpr int ORBIS_NET_RESOLVER_ESERVERREFUSED = 232;
constexpr int ORBIS_NET_RESOLVER_ENORECORD = 233;
constexpr int ORBIS_NET_RESOLVER_EALIGNMENT = 234;
constexpr int ORBIS_NET_RESOLVER_ENOTFOUND = 235;
constexpr int ORBIS_NET_RESOLVER_ENOTINIT = 236;
// error codes
constexpr int ORBIS_NET_ERROR_BASE = 0x80410100; // not existed used for calculation
constexpr int ORBIS_NET_ERROR_EPERM = 0x80410101;
constexpr int ORBIS_NET_ERROR_ENOENT = 0x80410102;
constexpr int ORBIS_NET_ERROR_ESRCH = 0x80410103;
constexpr int ORBIS_NET_ERROR_EINTR = 0x80410104;
constexpr int ORBIS_NET_ERROR_EIO = 0x80410105;
constexpr int ORBIS_NET_ERROR_ENXIO = 0x80410106;
constexpr int ORBIS_NET_ERROR_E2BIG = 0x80410107;
constexpr int ORBIS_NET_ERROR_ENOEXEC = 0x80410108;
constexpr int ORBIS_NET_ERROR_EBADF = 0x80410109;
constexpr int ORBIS_NET_ERROR_ECHILD = 0x8041010a;
constexpr int ORBIS_NET_ERROR_EDEADLK = 0x8041010b;
constexpr int ORBIS_NET_ERROR_ENOMEM = 0x8041010c;
constexpr int ORBIS_NET_ERROR_EACCES = 0x8041010d;
constexpr int ORBIS_NET_ERROR_EFAULT = 0x8041010e;
constexpr int ORBIS_NET_ERROR_ENOTBLK = 0x8041010f;
constexpr int ORBIS_NET_ERROR_EBUSY = 0x80410110;
constexpr int ORBIS_NET_ERROR_EEXIST = 0x80410111;
constexpr int ORBIS_NET_ERROR_EXDEV = 0x80410112;
constexpr int ORBIS_NET_ERROR_ENODEV = 0x80410113;
constexpr int ORBIS_NET_ERROR_ENOTDIR = 0x80410114;
constexpr int ORBIS_NET_ERROR_EISDIR = 0x80410115;
constexpr int ORBIS_NET_ERROR_EINVAL = 0x80410116;
constexpr int ORBIS_NET_ERROR_ENFILE = 0x80410117;
constexpr int ORBIS_NET_ERROR_EMFILE = 0x80410118;
constexpr int ORBIS_NET_ERROR_ENOTTY = 0x80410119;
constexpr int ORBIS_NET_ERROR_ETXTBSY = 0x8041011a;
constexpr int ORBIS_NET_ERROR_EFBIG = 0x8041011b;
constexpr int ORBIS_NET_ERROR_ENOSPC = 0x8041011c;
constexpr int ORBIS_NET_ERROR_ESPIPE = 0x8041011d;
constexpr int ORBIS_NET_ERROR_EROFS = 0x8041011e;
constexpr int ORBIS_NET_ERROR_EMLINK = 0x8041011f;
constexpr int ORBIS_NET_ERROR_EPIPE = 0x80410120;
constexpr int ORBIS_NET_ERROR_EDOM = 0x80410121;
constexpr int ORBIS_NET_ERROR_ERANGE = 0x80410122;
constexpr int ORBIS_NET_ERROR_EAGAIN = 0x80410123;
constexpr int ORBIS_NET_ERROR_EWOULDBLOCK = 0x80410123;
constexpr int ORBIS_NET_ERROR_EINPROGRESS = 0x80410124;
@@ -112,7 +173,9 @@ constexpr int ORBIS_NET_ERROR_EMSGSIZE = 0x80410128;
constexpr int ORBIS_NET_ERROR_EPROTOTYPE = 0x80410129;
constexpr int ORBIS_NET_ERROR_ENOPROTOOPT = 0x8041012a;
constexpr int ORBIS_NET_ERROR_EPROTONOSUPPORT = 0x8041012b;
constexpr int ORBIS_NET_ERROR_ESOCKTNOSUPPORT = 0x8041012c;
constexpr int ORBIS_NET_ERROR_EOPNOTSUPP = 0x8041012d;
constexpr int ORBIS_NET_ERROR_ENOTSUP = 0x8041012d;
constexpr int ORBIS_NET_ERROR_EPFNOSUPPORT = 0x8041012e;
constexpr int ORBIS_NET_ERROR_EAFNOSUPPORT = 0x8041012f;
constexpr int ORBIS_NET_ERROR_EADDRINUSE = 0x80410130;
@@ -134,12 +197,26 @@ constexpr int ORBIS_NET_ERROR_ENAMETOOLONG = 0x8041013f;
constexpr int ORBIS_NET_ERROR_EHOSTDOWN = 0x80410140;
constexpr int ORBIS_NET_ERROR_EHOSTUNREACH = 0x80410141;
constexpr int ORBIS_NET_ERROR_ENOTEMPTY = 0x80410142;
constexpr int ORBIS_NET_ERROR_EPROCUNAVAIL = 0x8041014C;
constexpr int ORBIS_NET_ERROR_EPROCLIM = 0x80410143;
constexpr int ORBIS_NET_ERROR_EUSERS = 0x80410144;
constexpr int ORBIS_NET_ERROR_EDQUOT = 0x80410145;
constexpr int ORBIS_NET_ERROR_ESTALE = 0x80410146;
constexpr int ORBIS_NET_ERROR_EREMOTE = 0x80410147;
constexpr int ORBIS_NET_ERROR_EBADRPC = 0x80410148;
constexpr int ORBIS_NET_ERROR_ERPCMISMATCH = 0x80410149;
constexpr int ORBIS_NET_ERROR_EPROGUNAVAIL = 0x8041014a;
constexpr int ORBIS_NET_ERROR_EPROGMISMATCH = 0x8041014b;
constexpr int ORBIS_NET_ERROR_EPROCUNAVAIL = 0x8041014c;
constexpr int ORBIS_NET_ERROR_ENOLCK = 0x8041014d;
constexpr int ORBIS_NET_ERROR_ENOSYS = 0x8041014e;
constexpr int ORBIS_NET_ERROR_EFTYPE = 0x8041014f;
constexpr int ORBIS_NET_ERROR_EAUTH = 0x80410150;
constexpr int ORBIS_NET_ERROR_ENEEDAUTH = 0x80410151;
constexpr int ORBIS_NET_ERROR_EIDRM = 0x80410152;
constexpr int ORBIS_NET_ERROR_ENOMSG = 0x80410153;
constexpr int ORBIS_NET_ERROR_ENOMS = 0x80410153;
constexpr int ORBIS_NET_ERROR_EOVERFLOW = 0x80410154;
constexpr int ORBIS_NET_ERROR_ECANCELED = 0x80410155;
constexpr int ORBIS_NET_ERROR_EPROTO = 0x8041015C;
constexpr int ORBIS_NET_ERROR_EPROTO = 0x8041015c;
constexpr int ORBIS_NET_ERROR_EADHOC = 0x804101a0;
constexpr int ORBIS_NET_ERROR_ERESERVED161 = 0x804101a1;
constexpr int ORBIS_NET_ERROR_ERESERVED162 = 0x804101a2;
@@ -147,8 +224,10 @@ constexpr int ORBIS_NET_ERROR_EINACTIVEDISABLED = 0x804101a3;
constexpr int ORBIS_NET_ERROR_ENODATA = 0x804101a4;
constexpr int ORBIS_NET_ERROR_EDESC = 0x804101a5;
constexpr int ORBIS_NET_ERROR_EDESCTIMEDOUT = 0x804101a6;
constexpr int ORBIS_NET_ERROR_ENETINTR = 0x804101a7;
constexpr int ORBIS_NET_ERROR_ENOTINIT = 0x804101c8;
constexpr int ORBIS_NET_ERROR_ENOLIBMEM = 0x804101c9;
constexpr int ORBIS_NET_ERROR_ERESERVED202 = 0x804101ca;
constexpr int ORBIS_NET_ERROR_ECALLBACK = 0x804101cb;
constexpr int ORBIS_NET_ERROR_EINTERNAL = 0x804101cc;
constexpr int ORBIS_NET_ERROR_ERETURN = 0x804101cd;
@@ -157,6 +236,7 @@ constexpr int ORBIS_NET_ERROR_RESOLVER_EINTERNAL = 0x804101dc;
constexpr int ORBIS_NET_ERROR_RESOLVER_EBUSY = 0x804101dd;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSPACE = 0x804101de;
constexpr int ORBIS_NET_ERROR_RESOLVER_EPACKET = 0x804101df;
constexpr int ORBIS_NET_ERROR_RESOLVER_ERESERVED224 = 0x804101e0;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENODNS = 0x804101e1;
constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOSUPPORT = 0x804101e3;
@@ -167,3 +247,5 @@ constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTIMPLEMENTED = 0x804101e7;
constexpr int ORBIS_NET_ERROR_RESOLVER_ESERVERREFUSED = 0x804101e8;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENORECORD = 0x804101e9;
constexpr int ORBIS_NET_ERROR_RESOLVER_EALIGNMENT = 0x804101ea;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTFOUND = 0x804101eb;
constexpr int ORBIS_NET_ERROR_RESOLVER_ENOTINIT = 0x804101ec;
+339
View File
@@ -0,0 +1,339 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <source_location>
#include <vector>
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_kernel.h"
#include "core/libraries/net/net_log.h"
#include "core/libraries/net/net_resolver.h"
#include "core/libraries/net/net_translate.h"
#include "core/libraries/net/net_upnp.h"
#include "core/libraries/net/net_util.h"
#include "core/net/guest_net.h"
namespace Libraries::Net {
namespace {
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
using Core::Net::Host::Error;
s64 Posix(s64 result) {
if (result < 0) {
*Libraries::Kernel::__Error() = *sceNetErrnoLoc();
return -1;
}
return result;
}
class KeepNetErrno {
public:
KeepNetErrno() : saved_{*sceNetErrnoLoc()} {}
~KeepNetErrno() {
*sceNetErrnoLoc() = saved_;
}
KeepNetErrno(const KeepNetErrno&) = delete;
KeepNetErrno& operator=(const KeepNetErrno&) = delete;
private:
s32 saved_;
};
s32 PosixError(int orbis_errno,
const std::source_location where = std::source_location::current()) {
*Libraries::Kernel::__Error() = orbis_errno;
LogFailure(orbis_errno, where);
return -1;
}
bool IsNetObject(s32 fd) {
return Core::Net::GetObjectKind(fd).has_value();
}
} // namespace
void InstallKernelIntegration() {
// Every net object is a Socket entry, Core::Net knows the real kind.
Core::Net::SetIdAllocator({
.allocate =
[] {
auto* table = FDTable::Instance();
const int fd = table->CreateHandle();
auto* file = table->GetFile(fd);
file->type = Core::FileSys::FileType::Socket;
file->m_guest_name = "net";
file->is_opened = true;
return fd;
},
.release = [](s32 fd) { FDTable::Instance()->DeleteHandle(fd); },
});
SetKernelErrnoHook([](int orbis_errno) { *Libraries::Kernel::__Error() = orbis_errno; });
SetSystemHooks({
.is_online = [] { return EmulatorSettings.IsConnectedToNetwork(); },
.mac_address =
[](std::array<u8, 6>* mac) {
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
if (!netinfo->RetrieveEthernetAddr()) {
return false;
}
*mac = netinfo->GetEthernetAddr();
return true;
},
.p2p_port =
[] {
const int port = EmulatorSettings.GetP2PPort();
if (port < 0 || port > 0xffff) {
LOG_WARNING(Lib_Net, "p2p_port {} is not a UDP port, using 3658", port);
return u16{3658};
}
return static_cast<u16>(port);
},
.p2p_started =
[](u16 port) {
if (!EmulatorSettings.IsConnectedToNetwork() || !EmulatorSettings.IsUPnPEnabled()) {
LOG_INFO(Lib_Net, "P2P on UDP port {}: UPnP forwarding off", port);
return;
}
LOG_INFO(Lib_Net, "P2P on UDP port {}: asking the router to forward it (UPnP)",
port);
auto& upnp = UPnPClient::Instance();
upnp.Start(); // no-op if NP already started it
upnp.MapPortAsync(port);
},
.p2p_stopped =
[](u16 port) {
LOG_DEBUG(Lib_Net, "P2P on UDP port {} stopped: removing any UPnP mapping", port);
UPnPClient::Instance().UnmapPort(port);
},
.public_addr =
[] {
// Prefer the router's answer over the NP server's.
if (const u32 upnp = UPnPClient::Instance().GetExternalIp(); upnp != 0) {
return upnp;
}
return Common::Singleton<NetUtil::NetUtilInternal>::Instance()->GetExternalIp();
},
});
}
s32 KernelClose(s32 fd) {
const auto kind = Core::Net::GetObjectKind(fd);
LOG_DEBUG(Lib_Net, "close({}): {}", fd,
!kind ? "not a net object"
: *kind == Core::Net::NetObjectKind::Socket ? "socket"
: *kind == Core::Net::NetObjectKind::Epoll ? "epoll"
: "resolver");
if (!kind) {
return PosixError(ORBIS_NET_EBADF);
}
if (*kind == Core::Net::NetObjectKind::External) {
const int e = DestroyResolver(fd);
if (e != 0) {
return PosixError(e);
}
ReleaseResolverPool(fd);
return 0;
}
const auto r = Core::Net::CloseObject(fd);
if (r.error == Error::Ok && *kind == Core::Net::NetObjectKind::Socket) {
LogSocketClosed(fd);
}
return r.error == Error::Ok ? 0 : PosixError(ToOrbisErrno(r.error));
}
s64 KernelRead(s32 fd, void* buf, u64 nbytes) {
const KeepNetErrno keep;
if (Core::Net::GetObjectKind(fd) != Core::Net::NetObjectKind::Socket) {
return PosixError(ORBIS_NET_EBADF);
}
return Posix(sceNetRecvfrom(fd, buf, nbytes, 0, nullptr, nullptr));
}
s64 KernelWrite(s32 fd, const void* buf, u64 nbytes) {
const KeepNetErrno keep;
if (Core::Net::GetObjectKind(fd) != Core::Net::NetObjectKind::Socket) {
return PosixError(ORBIS_NET_EBADF);
}
return Posix(sceNetSendto(fd, buf, nbytes, 0, nullptr, 0));
}
s32 KernelFstat(s32 fd, Libraries::Kernel::OrbisKernelStat* sb) {
if (!sb) {
return PosixError(ORBIS_NET_EFAULT);
}
if (!IsNetObject(fd)) {
return PosixError(ORBIS_NET_EBADF);
}
std::memset(sb, 0, sizeof(*sb));
sb->st_mode = 0140000u | 0777u;
sb->st_nlink = 1;
sb->st_blksize = 4096;
sb->st_size = 0;
return 0;
}
s64 KernelSelect(std::span<Core::Net::SelectEntry> entries, s64 timeout_us) {
std::vector<Core::Net::SelectEntry> sockets;
sockets.reserve(entries.size());
for (auto& e : entries) {
e.ready = 0;
if (Core::Net::GetObjectKind(e.id) == Core::Net::NetObjectKind::Socket) {
sockets.push_back(e);
}
}
const auto r = Core::Net::SocketSelect(sockets, timeout_us);
if (r.error != Error::Ok) {
return PosixError(static_cast<int>(r.error));
}
for (const auto& s : sockets) {
for (auto& e : entries) {
if (e.id == s.id) {
e.ready = s.ready;
}
}
}
return r.value;
}
// libkernel / libScePosix socket calls
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetConnect(s, addr, addrlen)));
}
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetBind(s, addr, addrlen)));
}
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetAccept(s, addr, paddrlen)));
}
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetGetpeername(s, addr, paddrlen)));
}
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetGetsockname(s, addr, paddrlen)));
}
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetGetsockopt(s, level, optname, optval, optlen)));
}
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetListen(s, backlog)));
}
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
u32 optlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSetsockopt(s, level, optname, optval, optlen)));
}
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetShutdown(s, how)));
}
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSocket(name, family, type, protocol)));
}
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) {
const KeepNetErrno keep;
return sys_socketex(nullptr, family, type, protocol);
}
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) {
LOG_DEBUG(Lib_Net, "family = {}, type = {}, protocol = {}", family, type, protocol);
if (sv == nullptr) {
return PosixError(ORBIS_NET_EFAULT);
}
if (family != ORBIS_NET_AF_UNIX) {
return PosixError(ORBIS_NET_EAFNOSUPPORT); // AF_UNIX only on FreeBSD
}
int error = 0;
const auto kind = ToHostSocketKind(family, type, protocol, &error);
if (!kind) {
return PosixError(error);
}
s32 ids[2];
const auto r = Core::Net::SocketCreatePair(kind->family, kind->type, kind->protocol, ids);
if (r.error != Error::Ok) {
return PosixError(ToOrbisErrno(r.error));
}
sv[0] = ids[0];
sv[1] = ids[1];
return 0;
}
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSocketAbort(s, flags)));
}
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSocketClose(s)));
}
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSend(s, buf, len, flags)));
}
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
const OrbisNetSockaddr* addr, u32 addrlen) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSendto(s, buf, len, flags, addr, addrlen)));
}
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) {
const KeepNetErrno keep;
return static_cast<int>(Posix(sceNetSendmsg(s, msg, flags)));
}
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) {
const KeepNetErrno keep;
return Posix(sceNetRecv(s, buf, len, flags));
}
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
u32* paddrlen) {
const KeepNetErrno keep;
return Posix(sceNetRecvfrom(s, buf, len, flags, addr, paddrlen));
}
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) {
const KeepNetErrno keep;
return Posix(sceNetRecvmsg(s, msg, flags));
}
int PS4_SYSV_ABI sys_htons(u16 v) {
return sceNetHtons(v);
}
int PS4_SYSV_ABI sys_htonl(u32 v) {
return static_cast<int>(sceNetHtonl(v));
}
} // namespace Libraries::Net
@@ -1,13 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "common/types.h"
#include "net.h"
#include "core/libraries/net/net_types.h"
#include "core/net/guest_net.h"
namespace Libraries::Kernel {
struct OrbisKernelStat;
}
namespace Libraries::Net {
// Called from the kernel's RegisterLib, before any net object exists.
void InstallKernelIntegration();
s32 KernelClose(s32 fd);
s64 KernelRead(s32 fd, void* buf, u64 nbytes);
s64 KernelWrite(s32 fd, const void* buf, u64 nbytes);
s32 KernelFstat(s32 fd, Libraries::Kernel::OrbisKernelStat* sb);
// Returns the number of ready (fd, direction) pairs. Epolls and resolvers are never ready.
s64 KernelSelect(std::span<Core::Net::SelectEntry> entries, s64 timeout_us);
// libkernel / libScePosix socket calls
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
+526
View File
@@ -0,0 +1,526 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <map>
#include <mutex>
#include <set>
#include <string>
#include <utility>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_log.h"
namespace Libraries::Net {
namespace {
struct ErrnoEntry {
int value;
const char* name;
};
// ORBIS_NET_ERROR_<name> is 0x80410100 | value.
constexpr ErrnoEntry ErrnoNames[] = {
{1, "EPERM"},
{2, "ENOENT"},
{3, "ESRCH"},
{4, "EINTR"},
{5, "EIO"},
{6, "ENXIO"},
{7, "E2BIG"},
{8, "ENOEXEC"},
{9, "EBADF"},
{10, "ECHILD"},
{11, "EDEADLK"},
{12, "ENOMEM"},
{13, "EACCES"},
{14, "EFAULT"},
{15, "ENOTBLK"},
{16, "EBUSY"},
{17, "EEXIST"},
{18, "EXDEV"},
{19, "ENODEV"},
{20, "ENOTDIR"},
{21, "EISDIR"},
{22, "EINVAL"},
{23, "ENFILE"},
{24, "EMFILE"},
{25, "ENOTTY"},
{26, "ETXTBSY"},
{27, "EFBIG"},
{28, "ENOSPC"},
{29, "ESPIPE"},
{30, "EROFS"},
{31, "EMLINK"},
{32, "EPIPE"},
{33, "EDOM"},
{34, "ERANGE"},
{35, "EWOULDBLOCK"},
{36, "EINPROGRESS"},
{37, "EALREADY"},
{38, "ENOTSOCK"},
{39, "EDESTADDRREQ"},
{40, "EMSGSIZE"},
{41, "EPROTOTYPE"},
{42, "ENOPROTOOPT"},
{43, "EPROTONOSUPPORT"},
{44, "ESOCKTNOSUPPORT"},
{45, "EOPNOTSUPP"},
{46, "EPFNOSUPPORT"},
{47, "EAFNOSUPPORT"},
{48, "EADDRINUSE"},
{49, "EADDRNOTAVAIL"},
{50, "ENETDOWN"},
{51, "ENETUNREACH"},
{52, "ENETRESET"},
{53, "ECONNABORTED"},
{54, "ECONNRESET"},
{55, "ENOBUFS"},
{56, "EISCONN"},
{57, "ENOTCONN"},
{58, "ESHUTDOWN"},
{59, "ETOOMANYREFS"},
{60, "ETIMEDOUT"},
{61, "ECONNREFUSED"},
{62, "ELOOP"},
{63, "ENAMETOOLONG"},
{64, "EHOSTDOWN"},
{65, "EHOSTUNREACH"},
{66, "ENOTEMPTY"},
{67, "EPROCLIM"},
{68, "EUSERS"},
{69, "EDQUOT"},
{70, "ESTALE"},
{71, "EREMOTE"},
{72, "EBADRPC"},
{73, "ERPCMISMATCH"},
{74, "EPROGUNAVAIL"},
{75, "EPROGMISMATCH"},
{76, "EPROCUNAVAIL"},
{77, "ENOLCK"},
{78, "ENOSYS"},
{79, "EFTYPE"},
{80, "EAUTH"},
{81, "ENEEDAUTH"},
{82, "EIDRM"},
{83, "ENOMS"},
{84, "EOVERFLOW"},
{85, "ECANCELED"},
{92, "EPROTO"},
{160, "EADHOC"},
{161, "ERESERVED161"},
{162, "ERESERVED162"},
{163, "EINACTIVEDISABLED"},
{164, "ENODATA"},
{165, "EDESC"},
{166, "EDESCTIMEDOUT"},
{167, "ENETINTR"},
{200, "ENOTINIT"},
{201, "ENOLIBMEM"},
{202, "ERESERVED202"},
{203, "ECALLBACK"},
{204, "EINTERNAL"},
{205, "ERETURN"},
{206, "ENOALLOCMEM"},
{220, "RESOLVER_EINTERNAL"},
{221, "RESOLVER_EBUSY"},
{222, "RESOLVER_ENOSPACE"},
{223, "RESOLVER_EPACKET"},
{224, "RESOLVER_ERESERVED224"},
{225, "RESOLVER_ENODNS"},
{226, "RESOLVER_ETIMEDOUT"},
{227, "RESOLVER_ENOSUPPORT"},
{228, "RESOLVER_EFORMAT"},
{229, "RESOLVER_ESERVERFAILURE"},
{230, "RESOLVER_ENOHOST"},
{231, "RESOLVER_ENOTIMPLEMENTED"},
{232, "RESOLVER_ESERVERREFUSED"},
{233, "RESOLVER_ENORECORD"},
{234, "RESOLVER_EALIGNMENT"},
{235, "RESOLVER_ENOTFOUND"},
{236, "RESOLVER_ENOTINIT"},
};
enum class Severity { Routine, Network, Suspicious };
Severity SeverityOf(int e) {
switch (e) {
case ORBIS_NET_EWOULDBLOCK:
case ORBIS_NET_EINPROGRESS:
case ORBIS_NET_EALREADY:
case ORBIS_NET_EISCONN:
case ORBIS_NET_EINTR:
case ORBIS_NET_ENOTBLK:
return Severity::Routine;
case ORBIS_NET_ECONNREFUSED:
case ORBIS_NET_ECONNRESET:
case ORBIS_NET_ECONNABORTED:
case ORBIS_NET_ENOTCONN:
case ORBIS_NET_EPIPE:
case ORBIS_NET_ETIMEDOUT:
case ORBIS_NET_EHOSTUNREACH:
case ORBIS_NET_EHOSTDOWN:
case ORBIS_NET_ENETUNREACH:
case ORBIS_NET_ENETDOWN:
case ORBIS_NET_ENETRESET:
case ORBIS_NET_EADDRINUSE:
case ORBIS_NET_EADDRNOTAVAIL:
case ORBIS_NET_ENOBUFS:
case ORBIS_NET_EACCES:
case ORBIS_NET_ESHUTDOWN:
return Severity::Network;
default:
return e >= 0xdc && e <= 0xec ? Severity::Network : Severity::Suspicious;
}
}
std::string_view ShortName(std::string_view signature) {
const size_t paren = signature.find('(');
std::string_view head = signature.substr(0, paren);
const size_t start = head.find_last_of(": ");
head = start == std::string_view::npos ? head : head.substr(start + 1);
// Wrapped exports have their body in <name>Impl.
if (head.ends_with("Impl")) {
head.remove_suffix(4);
}
return head;
}
u64 CountFailure(std::string_view function, int e) {
static std::mutex mutex;
static std::map<std::pair<std::string, int>, u64> counts;
std::scoped_lock lock{mutex};
return ++counts[{std::string{function}, e}];
}
} // namespace
std::string ErrnoName(int orbis_errno) {
const auto* it =
std::find_if(std::begin(ErrnoNames), std::end(ErrnoNames),
[&](const ErrnoEntry& entry) { return entry.value == orbis_errno; });
return it != std::end(ErrnoNames) ? it->name : fmt::format("E{}", orbis_errno);
}
std::string ErrorCodeName(s32 code) {
const u32 u = static_cast<u32>(code);
if ((u & 0xffffff00u) == static_cast<u32>(ORBIS_NET_ERROR_BASE)) {
return ErrnoName(static_cast<int>(u & 0xff));
}
return fmt::format("{:#x}", u);
}
void LogFailure(int orbis_errno, const std::source_location& where) {
const std::string_view function = ShortName(where.function_name());
const Severity severity = SeverityOf(orbis_errno);
if (severity == Severity::Routine) {
LOG_TRACE(Lib_Net, "{} failed: {}", function, ErrnoName(orbis_errno));
return;
}
const u64 count = CountFailure(function, orbis_errno);
if (count > 16 && count % 1000 != 0) {
return;
}
const std::string repeat = count > 16 ? fmt::format(", {} times so far", count) : "";
std::string_view file = where.file_name();
file.remove_prefix(std::min(file.size(), file.find_last_of("/\\") + 1));
if (severity == Severity::Network) {
LOG_DEBUG(Lib_Net, "{} failed: {} ({}:{}{})", function, ErrnoName(orbis_errno), file,
where.line(), repeat);
} else {
LOG_WARNING(Lib_Net, "{} failed: {} ({}:{}{})", function, ErrnoName(orbis_errno), file,
where.line(), repeat);
}
}
namespace {
struct Traffic {
u64 sent_bytes = 0;
u64 sent_calls = 0;
u64 received_bytes = 0;
u64 received_calls = 0;
std::set<std::string> peers;
};
std::mutex g_traffic_mutex;
std::map<OrbisNetId, Traffic> g_traffic; // guarded by g_traffic_mutex
} // namespace
void CountTraffic(OrbisNetId s, bool sent, s64 bytes) {
std::scoped_lock lock{g_traffic_mutex};
auto& t = g_traffic[s];
if (sent) {
t.sent_bytes += static_cast<u64>(bytes);
++t.sent_calls;
} else {
t.received_bytes += static_cast<u64>(bytes);
++t.received_calls;
}
}
void NoteP2PPeer(OrbisNetId s, bool sent, const OrbisNetSockaddr* addr, u32 len) {
if (addr == nullptr || addr->sa_family != ORBIS_NET_AF_INET ||
len < sizeof(OrbisNetSockaddrIn)) {
return;
}
OrbisNetSockaddrIn in;
std::memcpy(&in, addr, sizeof(in));
if (in.sin_vport == 0) {
return; // not P2P
}
constexpr size_t MaxPeers = 16;
const std::string text = FormatSockaddr(addr, len);
{
std::scoped_lock lock{g_traffic_mutex};
auto& peers = g_traffic[s].peers;
if (peers.size() >= MaxPeers || !peers.insert((sent ? "> " : "< ") + text).second) {
return;
}
}
LOG_INFO(Lib_Net, "socket {}: first P2P packet {} {}", s, sent ? "to" : "from", text);
}
void LogSocketClosed(OrbisNetId s) {
Traffic t;
{
std::scoped_lock lock{g_traffic_mutex};
const auto it = g_traffic.find(s);
if (it != g_traffic.end()) {
t = it->second;
g_traffic.erase(it);
}
}
LOG_DEBUG(Lib_Net, "socket {} closed: sent {} bytes in {} calls, received {} bytes in {} calls",
s, t.sent_bytes, t.sent_calls, t.received_bytes, t.received_calls);
}
std::string FormatSockaddr(const OrbisNetSockaddr* addr, u32 len) {
if (addr == nullptr) {
return "null";
}
if (len < 2) {
return fmt::format("(length {})", len);
}
if (addr->sa_family == ORBIS_NET_AF_INET && len >= sizeof(OrbisNetSockaddrIn)) {
OrbisNetSockaddrIn in;
std::memcpy(&in, addr, sizeof(in));
const u32 ip = sceNetNtohl(in.sin_addr);
std::string text = fmt::format("{}.{}.{}.{}:{}", ip >> 24, (ip >> 16) & 0xff,
(ip >> 8) & 0xff, ip & 0xff, sceNetNtohs(in.sin_port));
if (in.sin_vport != 0) {
text += fmt::format(" vport {}", sceNetNtohs(in.sin_vport));
}
return text;
}
if (addr->sa_family == ORBIS_NET_AF_UNIX) {
const char* path = reinterpret_cast<const char*>(addr) + 2;
return fmt::format("unix:{}", std::string_view{path, strnlen(path, len - 2)});
}
return fmt::format("family {} (length {})", addr->sa_family, len);
}
std::string_view SocketTypeName(s32 type) {
switch (type) {
case ORBIS_NET_SOCK_STREAM:
return "STREAM";
case ORBIS_NET_SOCK_DGRAM:
return "DGRAM";
case ORBIS_NET_SOCK_RAW:
return "RAW";
case ORBIS_NET_SOCK_DGRAM_P2P:
return "DGRAM_P2P";
case ORBIS_NET_SOCK_STREAM_P2P:
return "STREAM_P2P";
default:
return "unknown";
}
}
std::string OptionName(s32 level, s32 name) {
const char* option = nullptr;
const char* level_name = nullptr;
switch (level) {
case ORBIS_NET_SOL_SOCKET:
level_name = "SOL_SOCKET";
switch (name) {
case ORBIS_NET_SO_REUSEADDR:
option = "SO_REUSEADDR";
break;
case ORBIS_NET_SO_KEEPALIVE:
option = "SO_KEEPALIVE";
break;
case ORBIS_NET_SO_BROADCAST:
option = "SO_BROADCAST";
break;
case ORBIS_NET_SO_LINGER:
option = "SO_LINGER";
break;
case ORBIS_NET_SO_REUSEPORT:
option = "SO_REUSEPORT";
break;
case ORBIS_NET_SO_ONESBCAST:
option = "SO_ONESBCAST";
break;
case ORBIS_NET_SO_USECRYPTO:
option = "SO_USECRYPTO";
break;
case ORBIS_NET_SO_USESIGNATURE:
option = "SO_USESIGNATURE";
break;
case ORBIS_NET_SO_SNDBUF:
option = "SO_SNDBUF";
break;
case ORBIS_NET_SO_RCVBUF:
option = "SO_RCVBUF";
break;
case ORBIS_NET_SO_ERROR:
option = "SO_ERROR";
break;
case ORBIS_NET_SO_TYPE:
option = "SO_TYPE";
break;
case ORBIS_NET_SO_SNDTIMEO:
option = "SO_SNDTIMEO";
break;
case ORBIS_NET_SO_RCVTIMEO:
option = "SO_RCVTIMEO";
break;
case ORBIS_NET_SO_ERROR_EX:
option = "SO_ERROR_EX";
break;
case ORBIS_NET_SO_ACCEPTTIMEO:
option = "SO_ACCEPTTIMEO";
break;
case ORBIS_NET_SO_CONNECTTIMEO:
option = "SO_CONNECTTIMEO";
break;
case ORBIS_NET_SO_NBIO:
option = "SO_NBIO";
break;
case ORBIS_NET_SO_POLICY:
option = "SO_POLICY";
break;
case ORBIS_NET_SO_NAME:
option = "SO_NAME";
break;
case ORBIS_NET_SO_PRIORITY:
option = "SO_PRIORITY";
break;
case 0x1005:
option = "SO_SNDTIMEO (BSD)";
break;
case 0x1006:
option = "SO_RCVTIMEO (BSD)";
break;
default:
break;
}
break;
case ORBIS_NET_IPPROTO_IP:
level_name = "IPPROTO_IP";
switch (name) {
case ORBIS_NET_IP_HDRINCL:
option = "IP_HDRINCL";
break;
case ORBIS_NET_IP_TOS:
option = "IP_TOS";
break;
case ORBIS_NET_IP_TTL:
option = "IP_TTL";
break;
case ORBIS_NET_IP_MULTICAST_IF:
option = "IP_MULTICAST_IF";
break;
case ORBIS_NET_IP_MULTICAST_TTL:
option = "IP_MULTICAST_TTL";
break;
case ORBIS_NET_IP_MULTICAST_LOOP:
option = "IP_MULTICAST_LOOP";
break;
case ORBIS_NET_IP_ADD_MEMBERSHIP:
option = "IP_ADD_MEMBERSHIP";
break;
case ORBIS_NET_IP_DROP_MEMBERSHIP:
option = "IP_DROP_MEMBERSHIP";
break;
case ORBIS_NET_IP_TTLCHK:
option = "IP_TTLCHK";
break;
case ORBIS_NET_IP_MAXTTL:
option = "IP_MAXTTL";
break;
case ORBIS_NET_IP_DONTFRAG:
option = "IP_DONTFRAG";
break;
default:
break;
}
break;
case ORBIS_NET_IPPROTO_TCP:
level_name = "IPPROTO_TCP";
switch (name) {
case ORBIS_NET_TCP_NODELAY:
option = "TCP_NODELAY";
break;
case ORBIS_NET_TCP_MAXSEG:
option = "TCP_MAXSEG";
break;
case ORBIS_NET_TCP_MSS_TO_ADVERTISE:
option = "TCP_MSS_TO_ADVERTISE";
break;
case ORBIS_NET_TCP_KEEPLISTEN:
option = "TCP_KEEPLISTEN";
break;
default:
break;
}
break;
case ORBIS_NET_IPPROTO_UDP:
level_name = "IPPROTO_UDP";
if (name == ORBIS_NET_UDP_SND_ON_SUSPEND) {
option = "UDP_SND_ON_SUSPEND";
}
break;
default:
break;
}
if (level_name == nullptr) {
return fmt::format("level {:#x}/option {:#x}", level, name);
}
if (option == nullptr) {
return fmt::format("{}/option {:#x}", level_name, name);
}
return fmt::format("{}/{}", level_name, option);
}
std::string EpollEventsName(u32 events) {
static constexpr std::pair<u32, const char*> Bits[] = {
{ORBIS_NET_EPOLLIN, "IN"}, {ORBIS_NET_EPOLLOUT, "OUT"}, {ORBIS_NET_EPOLLERR, "ERR"},
{ORBIS_NET_EPOLLHUP, "HUP"}, {ORBIS_NET_EPOLLDESCID, "DESCID"},
};
std::string out;
u32 rest = events;
for (const auto& [bit, name] : Bits) {
if (events & bit) {
out += out.empty() ? "" : "|";
out += name;
rest &= ~bit;
}
}
if (rest != 0) {
out += fmt::format("{}{:#x}", out.empty() ? "" : "|", rest);
}
return out.empty() ? "0" : out;
}
} // namespace Libraries::Net
+26
View File
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <source_location>
#include <string>
#include <string_view>
#include "common/types.h"
#include "core/libraries/net/net_types.h"
namespace Libraries::Net {
std::string ErrnoName(int orbis_errno);
std::string ErrorCodeName(s32 code);
void LogFailure(int orbis_errno, const std::source_location& where);
std::string FormatSockaddr(const OrbisNetSockaddr* addr, u32 len);
std::string_view SocketTypeName(s32 type);
std::string OptionName(s32 level, s32 name);
std::string EpollEventsName(u32 events);
void CountTraffic(OrbisNetId s, bool sent, s64 bytes);
void NoteP2PPeer(OrbisNetId s, bool sent, const OrbisNetSockaddr* addr, u32 len);
void LogSocketClosed(OrbisNetId s);
} // namespace Libraries::Net
+314
View File
@@ -0,0 +1,314 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <chrono>
#include <cstring>
#include <iterator>
#include <memory>
#include <mutex>
#include <span>
#include <string>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_log.h"
#include "core/libraries/net/net_p2p.h"
#include "core/libraries/net/net_translate.h"
#include "core/net/guest_net.h"
#include "core/net/p2p_codec.h"
namespace Libraries::Net {
namespace {
using Host::Error;
enum SignalingChannel : size_t { Signaling = 0, Control = 1, Matching2 = 2, ChannelCount };
size_t ClassifySignaling(std::span<const u8> packet) {
if (packet.size() < 5 || std::memcmp(packet.data(), "SHAD", 4) != 0) {
return Signaling;
}
switch (packet[4]) {
case 0x10:
return Control;
case 0x21:
return Matching2;
default:
return Signaling;
}
}
struct P2PState {
std::mutex mutex;
std::shared_ptr<P2P::Transport> transport;
std::shared_ptr<P2P::Keyring> keyring = std::make_shared<P2P::Keyring>();
u16 configured_port = 3658; // host order
u32 advertised_addr = 0; // network order
std::chrono::steady_clock::time_point retry_after{};
std::pair<u16, Error> last_failure{0, Error::Ok};
};
P2PState& P2PStateInstance() {
static P2PState state;
return state;
}
void LogBroadcast(std::span<const u8> packet, const P2P::Endpoint& from) {
char address[INET6_ADDRSTRLEN] = "?";
const auto* sa = from.Sockaddr();
if (sa->sa_family == AF_INET) {
inet_ntop(AF_INET, &reinterpret_cast<const sockaddr_in*>(sa)->sin_addr, address,
sizeof(address));
} else if (sa->sa_family == AF_INET6) {
inet_ntop(AF_INET6, &reinterpret_cast<const sockaddr_in6*>(sa)->sin6_addr, address,
sizeof(address));
}
std::string hex;
const size_t shown = std::min<size_t>(packet.size(), 64);
hex.reserve(shown * 3);
for (size_t i = 0; i < shown; ++i) {
fmt::format_to(std::back_inserter(hex), "{}{:02x}", i == 0 ? "" : " ", packet[i]);
}
LOG_WARNING(Lib_Net,
"P2P broadcast packet received (layout assumed; please report this line): "
"from {}:{}, {} bytes: {}{}",
address, from.Port(), packet.size(), hex, packet.size() > shown ? " ..." : "");
}
constexpr u16 P2PPortFallbacks = 16;
u16 StartLocked(P2PState& state, u16 udp_port, bool allow_fallback) {
if (!Host::Initialize()) {
return 0;
}
P2P::TransportConfig config;
config.signaling_channels = ChannelCount;
config.classify_signaling = ClassifySignaling;
const auto try_port = [&](u16 port, Error* error) {
auto codec = std::make_unique<P2P::FramingCodec>(state.keyring, LogBroadcast);
return P2P::Transport::Create(AF_INET, P2P::Endpoint::IPv4("0.0.0.0", port),
std::move(codec), config, error);
};
Error error;
auto transport = try_port(udp_port, &error);
for (u16 i = 1; !transport && allow_fallback && error == Error::AddrInUse && udp_port != 0 &&
i <= P2PPortFallbacks && udp_port + i <= 0xffff;
++i) {
transport = try_port(static_cast<u16>(udp_port + i), &error);
}
if (!transport) {
if (state.last_failure != std::pair{udp_port, error}) {
state.last_failure = {udp_port, error};
LOG_ERROR(Lib_Net, "cannot start P2P on UDP port {}: {}", udp_port,
ErrnoName(static_cast<int>(error)));
} else {
LOG_DEBUG(Lib_Net, "cannot start P2P on UDP port {}: {}", udp_port,
ErrnoName(static_cast<int>(error)));
}
return 0;
}
state.last_failure = {0, Error::Ok};
const u16 port = transport->BoundPort();
state.transport = transport;
Core::Net::SetP2PTransport(AF_INET, std::move(transport));
if (udp_port != 0 && port != udp_port) {
LOG_WARNING(Lib_Net,
"UDP port {} is in use (another instance?), P2P transport on UDP port {} "
"instead. Set p2p_port to choose one.",
udp_port, port);
} else {
LOG_INFO(Lib_Net, "P2P transport on UDP port {}", port);
}
return port;
}
u16 PortToBind(const P2PState& state, const SystemHooks& hooks) {
return hooks.p2p_port ? hooks.p2p_port() : state.configured_port;
}
std::shared_ptr<P2P::Transport> RunningTransport() {
auto& state = P2PStateInstance();
const auto hooks = GetSystemHooks();
std::shared_ptr<P2P::Transport> transport;
u16 started = 0;
{
std::scoped_lock lock{state.mutex};
const auto now = std::chrono::steady_clock::now();
if (!state.transport && now >= state.retry_after) {
started = StartLocked(state, PortToBind(state, hooks), true);
if (started == 0) {
state.retry_after = now + std::chrono::seconds{2};
}
}
transport = state.transport;
}
if (started != 0 && hooks.p2p_started) {
hooks.p2p_started(started);
}
return transport;
}
int ChannelSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
const auto transport = RunningTransport();
if (!transport || (data == nullptr && len != 0)) {
return -1;
}
sockaddr_in to{};
to.sin_family = AF_INET;
to.sin_addr.s_addr = dest_addr;
to.sin_port = dest_port;
const auto endpoint =
P2P::Endpoint::FromSockaddr(reinterpret_cast<const sockaddr*>(&to), sizeof(to));
const Error e = transport->SendSignaling(
{static_cast<const u8*>(data), static_cast<size_t>(len)}, endpoint);
return e == Error::Ok ? static_cast<int>(len) : -1;
}
int ChannelRecvFrom(size_t channel, void* buf, u32 len, u32* from_addr, u16* from_port) {
const auto transport = RunningTransport();
std::vector<u8> packet;
P2P::Endpoint from;
if (!transport || !transport->RecvSignaling(channel, &packet, &from)) {
return -1;
}
const u32 n = std::min<u32>(len, static_cast<u32>(packet.size()));
if (buf != nullptr) {
std::memcpy(buf, packet.data(), n);
}
const auto* in = reinterpret_cast<const sockaddr_in*>(&from.addr);
if (from_addr != nullptr) {
*from_addr = in->sin_addr.s_addr;
}
if (from_port != nullptr) {
*from_port = in->sin_port;
}
return static_cast<int>(n);
}
} // namespace
void ConfigureP2P(u16 udp_port, u32 advertised_addr) {
auto& state = P2PStateInstance();
std::scoped_lock lock{state.mutex};
state.configured_port = udp_port;
state.advertised_addr = advertised_addr;
const u32 ip = sceNetNtohl(advertised_addr);
LOG_INFO(Lib_Net, "P2P configured: UDP port {}, advertised address {}.{}.{}.{}", udp_port,
ip >> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
}
u16 StartP2P(u16 udp_port) {
StopP2P();
auto& state = P2PStateInstance();
u16 started = 0;
{
std::scoped_lock lock{state.mutex};
started = StartLocked(state, udp_port, false);
state.retry_after = {};
}
const auto hooks = GetSystemHooks();
if (started != 0 && hooks.p2p_started) {
hooks.p2p_started(started);
}
return started;
}
void StopP2P() {
auto& state = P2PStateInstance();
u16 stopped = 0;
{
std::scoped_lock lock{state.mutex};
if (state.transport) {
stopped = state.transport->BoundPort();
}
state.transport.reset();
state.retry_after = {};
Core::Net::SetP2PTransport(AF_INET, nullptr);
}
if (stopped != 0) {
LOG_INFO(Lib_Net, "P2P transport on UDP port {} stopped", stopped);
}
const auto hooks = GetSystemHooks();
if (stopped != 0 && hooks.p2p_stopped) {
hooks.p2p_stopped(stopped);
}
}
std::shared_ptr<P2P::Keyring> GetP2PKeyring() {
auto& state = P2PStateInstance();
std::scoped_lock lock{state.mutex};
return state.keyring;
}
bool EnsureP2PTransport() {
return RunningTransport() != nullptr;
}
bool P2PTransportIsReady() {
auto& state = P2PStateInstance();
std::scoped_lock lock{state.mutex};
return state.transport != nullptr;
}
u16 GetP2PConfiguredPort() {
const auto hooks = GetSystemHooks();
auto& state = P2PStateInstance();
std::scoped_lock lock{state.mutex};
return PortToBind(state, hooks);
}
u16 GetP2PBoundPort() {
auto& state = P2PStateInstance();
std::scoped_lock lock{state.mutex};
return state.transport ? state.transport->BoundPort() : 0;
}
u32 GetP2PAdvertisedAddr() {
u32 configured = 0;
{
auto& state = P2PStateInstance();
std::scoped_lock lock{state.mutex};
configured = state.advertised_addr;
}
if (configured != 0) {
return configured;
}
const auto hooks = GetSystemHooks();
return hooks.public_addr ? hooks.public_addr() : 0;
}
u16 GetP2PAdvertisedPort() {
const u16 bound = GetP2PBoundPort();
return bound != 0 ? bound : GetP2PConfiguredPort();
}
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return ChannelSendTo(data, len, dest_addr, dest_port);
}
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return ChannelRecvFrom(Signaling, buf, len, from_addr, from_port);
}
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return ChannelSendTo(data, len, dest_addr, dest_port);
}
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return ChannelRecvFrom(Control, buf, len, from_addr, from_port);
}
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return ChannelSendTo(data, len, dest_addr, dest_port);
}
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return ChannelRecvFrom(Matching2, buf, len, from_addr, from_port);
}
} // namespace Libraries::Net
+34
View File
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include "common/types.h"
namespace Core::Net::P2P {
class Keyring;
}
namespace Libraries::Net {
void ConfigureP2P(u16 udp_port, u32 advertised_addr);
u16 StartP2P(u16 udp_port);
void StopP2P();
std::shared_ptr<Core::Net::P2P::Keyring> GetP2PKeyring();
// Used by NP signaling/matching.
bool EnsureP2PTransport();
bool P2PTransportIsReady();
u16 GetP2PConfiguredPort(); // host order
u16 GetP2PBoundPort(); // host order, 0 when not running
u32 GetP2PAdvertisedAddr(); // Network order, 0 if unknown.
u16 GetP2PAdvertisedPort();
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
} // namespace Libraries::Net
+145
View File
@@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "common/logging/log.h"
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_log.h"
#include "core/libraries/net/net_p2p_ioctl.h"
#include "core/libraries/net/net_types.h"
#include "core/net/p2p_codec.h"
namespace Libraries::Net {
namespace P2P = Core::Net::P2P;
namespace {
constexpr size_t AddressSize = sizeof(OrbisNetSockaddrIn); // IPv4 only
constexpr size_t RecordSize = AddressSize + 20;
constexpr size_t KeyOffset = AddressSize;
constexpr size_t LocalPortOffset = RecordSize - 4;
constexpr size_t FlagsOffset = RecordSize - 2;
constexpr size_t IdOffset = RecordSize - 1;
constexpr u16 DefaultKeyPort = 3658; // loopback port that also gets the default key
P2P::KeyEntry ReadEntry(const u8* bytes) {
P2P::KeyEntry entry;
std::memcpy(entry.key.value.data(), bytes + KeyOffset, entry.key.value.size());
std::memcpy(&entry.local_port, bytes + LocalPortOffset, sizeof(entry.local_port));
entry.flags = bytes[FlagsOffset];
entry.id = bytes[IdOffset];
return entry;
}
bool ReadPeer(const u8* bytes, P2P::Endpoint* peer) {
OrbisNetSockaddrIn in;
std::memcpy(&in, bytes, sizeof(in));
if (in.sin_family != ORBIS_NET_AF_INET) {
return false;
}
sockaddr_in host{};
host.sin_family = AF_INET;
host.sin_port = in.sin_port;
std::memcpy(&host.sin_addr, &in.sin_addr, sizeof(in.sin_addr));
*peer = P2P::Endpoint::FromSockaddr(reinterpret_cast<const sockaddr*>(&host), sizeof(host));
return true;
}
void WriteKey(u8* bytes, const std::optional<P2P::P2PKey>& key) {
if (key) {
std::memcpy(bytes + KeyOffset, key->value.data(), key->value.size());
} else {
std::memset(bytes + KeyOffset, 0, sizeof(P2P::P2PKey::value));
}
}
} // namespace
int P2PKeyIoctl(P2P::Keyring& keyring, u16 np_port, u64 cmd, void* data) {
const u32 control = static_cast<u32>(cmd);
if ((control & 0xff00) != 0x5000) {
return ORBIS_NET_EINVAL;
}
if (control == 0x200050c8) {
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: enable (no-op)", control);
return 0;
}
if ((control & 0xff) == 0xd9) {
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: communication ID cleared", control);
keyring.SetCommunicationId(std::nullopt);
return 0;
}
if (data == nullptr) {
return ORBIS_NET_EFAULT;
}
auto* bytes = static_cast<u8*>(data);
if (control == 0x802450c8 || control == 0x803450c8) {
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: keys with id {} removed", control, bytes[IdOffset]);
keyring.RemoveKeysWithId(bytes[IdOffset]);
return 0;
}
// Never log key values.
const auto peer_text = [&] {
return FormatSockaddr(reinterpret_cast<const OrbisNetSockaddr*>(bytes), AddressSize);
};
switch (control & 0xff) {
case 0xc9: {
const auto entry = ReadEntry(bytes);
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: default key set (id {})", control, entry.id);
keyring.SetDefaultKey(entry.key);
keyring.AddPeerKey(P2P::Endpoint::IPv4("127.0.0.1", DefaultKeyPort), entry);
return 0;
}
case 0xca:
WriteKey(bytes, keyring.GetDefaultKey());
return 0;
case 0xcb: {
P2P::Endpoint peer;
if (!ReadPeer(bytes, &peer)) {
return ORBIS_NET_EINVAL;
}
auto entry = ReadEntry(bytes);
if (entry.local_port == 0) {
entry.local_port = np_port;
}
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: key for peer {} added (id {}, flags {:#x})", control,
peer_text(), entry.id, entry.flags);
keyring.AddPeerKey(peer, entry);
return 0;
}
case 0xcc: {
P2P::Endpoint peer;
if (!ReadPeer(bytes, &peer)) {
return ORBIS_NET_EINVAL;
}
const auto entry = ReadEntry(bytes);
const bool released = keyring.ReleasePeerKey(peer, entry.key, entry.id);
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: key for peer {} (id {}) {}", control, peer_text(),
entry.id, released ? "released" : "not installed");
return released ? 0 : ORBIS_NET_ENOENT;
}
case 0xcd:
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: wildcard key set", control);
keyring.SetWildcardKey(ReadEntry(bytes).key);
return 0;
case 0xce:
WriteKey(bytes, keyring.GetWildcardKey());
return 0;
case 0xcf:
case 0xd0:
std::memcpy(bytes, &np_port, sizeof(np_port));
return 0;
case 0xd8:
LOG_DEBUG(Lib_Net, "P2P ioctl {:#x}: communication ID set", control);
keyring.SetCommunicationId(P2P::DeriveCommunicationId(std::span<const u8, 16>(bytes, 16)));
return 0;
default:
LOG_WARNING(Lib_Net, "P2P ioctl {:#x}: unknown command", control);
return ORBIS_NET_EINVAL;
}
}
} // namespace Libraries::Net
+17
View File
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Net::P2P {
class Keyring;
}
namespace Libraries::Net {
// np_port is in network byte order. Returns 0 or a guest errno.
int P2PKeyIoctl(Core::Net::P2P::Keyring& keyring, u16 np_port, u64 cmd, void* data);
} // namespace Libraries::Net
+350
View File
@@ -0,0 +1,350 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_log.h"
#include "core/libraries/net/net_resolver.h"
#include "core/net/guest_net.h"
#include "core/net/host_net.h"
#ifndef _WIN32
#include <netdb.h>
#endif
namespace Libraries::Net {
namespace {
using Core::Net::Host::Error;
// Each kind has its own preserved abort.
enum class LookupKind { Ntoa = 0, Aton = 1 };
struct Resolver {
std::string name;
std::mutex mutex; // guards everything below
bool running = false;
bool destroyed = false;
u64 lookup = 0; // bumped to orphan a running async lookup
s32 status = 0;
std::array<bool, 2> pending_abort{}; // indexed by LookupKind
};
std::mutex g_mutex;
std::unordered_map<s32, std::shared_ptr<Resolver>> g_resolvers;
std::function<bool()> g_is_online;
std::shared_ptr<Resolver> Find(s32 id) {
std::scoped_lock lock{g_mutex};
const auto it = g_resolvers.find(id);
return it != g_resolvers.end() ? it->second : nullptr;
}
bool IsOnline() {
std::function<bool()> check;
{
std::scoped_lock lock{g_mutex};
check = g_is_online;
}
return !check || check();
}
// IPv4 only, at most 10 records.
s32 Lookup(std::string hostname, bool allow_literal, std::vector<u32>* addrs) {
if (!hostname.empty() && hostname.back() == '.') {
hostname.pop_back();
}
if (hostname.compare(0, 9, "localhost") == 0) {
addrs->push_back(htonl(INADDR_LOOPBACK));
return 0;
}
if (allow_literal) {
in_addr literal{};
if (inet_pton(AF_INET, hostname.c_str(), &literal) == 1) {
addrs->push_back(literal.s_addr);
return 0;
}
}
if (!IsOnline()) {
return ORBIS_NET_ERROR_RESOLVER_ENODNS;
}
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; // one entry per address
addrinfo* result = nullptr;
const int gai = getaddrinfo(hostname.c_str(), nullptr, &hints, &result);
if (gai != 0) {
LOG_WARNING(Lib_Net, "lookup of {} failed: {}", hostname, gai);
switch (gai) {
case EAI_NONAME:
#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
case EAI_NODATA:
#endif
return ORBIS_NET_ERROR_RESOLVER_ENOHOST;
case EAI_AGAIN:
return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT;
case EAI_FAIL:
return ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE;
default:
return ORBIS_NET_ERROR_RESOLVER_EINTERNAL;
}
}
for (const addrinfo* ai = result; ai != nullptr && addrs->size() < 10; ai = ai->ai_next) {
if (ai->ai_family != AF_INET || ai->ai_addr == nullptr) {
continue;
}
const u32 addr = reinterpret_cast<const sockaddr_in*>(ai->ai_addr)->sin_addr.s_addr;
if (std::find(addrs->begin(), addrs->end(), addr) == addrs->end()) {
addrs->push_back(addr);
}
}
freeaddrinfo(result);
return addrs->empty() ? ORBIS_NET_ERROR_RESOLVER_ENORECORD : 0;
}
void Deliver(const ResolverOutput& output, const std::vector<u32>& addrs) {
if (output.addr != nullptr) {
output.addr->inaddr_addr = addrs.front();
}
if (output.info != nullptr) {
std::memset(output.info, 0, sizeof(*output.info));
for (size_t i = 0; i < addrs.size(); ++i) {
output.info->addrs[i].u.addr.inaddr_addr = addrs[i];
output.info->addrs[i].af = ORBIS_NET_AF_INET;
}
output.info->records = static_cast<u32>(addrs.size());
output.info->recordsv4 = static_cast<u32>(addrs.size());
}
}
} // namespace
s32 CreateResolver(std::string name) {
const auto r = Core::Net::ReserveExternalId();
if (r.error != Error::Ok) {
return -static_cast<s32>(r.error);
}
const auto id = static_cast<s32>(r.value);
auto resolver = std::make_shared<Resolver>();
resolver->name = std::move(name);
std::scoped_lock lock{g_mutex};
g_resolvers[id] = std::move(resolver);
return id;
}
int DestroyResolver(s32 id) {
std::shared_ptr<Resolver> resolver;
{
std::scoped_lock lock{g_mutex};
const auto it = g_resolvers.find(id);
if (it == g_resolvers.end()) {
return ORBIS_NET_EBADF;
}
resolver = std::move(it->second);
g_resolvers.erase(it);
}
{
std::scoped_lock lock{resolver->mutex};
resolver->destroyed = true; // keeps a running lookup from writing guest memory
}
Core::Net::ReleaseExternalId(id);
return 0;
}
int AbortResolver(s32 id, u32 flags) {
const auto resolver = Find(id);
if (!resolver) {
return ORBIS_NET_EBADF;
}
std::scoped_lock lock{resolver->mutex};
if (resolver->running) {
++resolver->lookup; // orphan the running thread
resolver->running = false;
resolver->status = ORBIS_NET_ERROR_EINTR;
Core::Net::SignalExternal(id, true);
LOG_DEBUG(Lib_Net, "resolver {}: running lookup aborted", id);
return 0;
}
LOG_DEBUG(Lib_Net, "resolver {}: abort with nothing running, flags {:#x}", id, flags);
if (flags & ORBIS_NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION) {
resolver->pending_abort[static_cast<size_t>(LookupKind::Ntoa)] = true;
}
if (flags & ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION) {
resolver->pending_abort[static_cast<size_t>(LookupKind::Aton)] = true;
}
return 0;
}
namespace {
template <typename Work, typename Deliver>
s32 RunLookup(s32 id, LookupKind kind, bool async, Work work, Deliver deliver) {
const auto resolver = Find(id);
if (!resolver) {
return ORBIS_NET_ERROR_EBADF;
}
u64 lookup = 0;
{
std::scoped_lock lock{resolver->mutex};
if (resolver->running) {
return ORBIS_NET_ERROR_RESOLVER_EBUSY;
}
if (std::exchange(resolver->pending_abort[static_cast<size_t>(kind)], false)) {
LOG_DEBUG(Lib_Net, "resolver {}: lookup fails with a preserved abort", id);
resolver->status = ORBIS_NET_ERROR_EINTR;
return ORBIS_NET_ERROR_EINTR;
}
if (async) {
resolver->running = true;
resolver->status = 0;
lookup = ++resolver->lookup;
}
}
if (!async) {
auto [status, result] = work();
std::scoped_lock lock{resolver->mutex};
if (status == 0) {
status = deliver(result);
}
resolver->status = status;
return status;
}
std::thread([resolver, id, lookup, work = std::move(work), deliver = std::move(deliver)] {
auto [status, result] = work();
std::scoped_lock lock{resolver->mutex};
if (resolver->destroyed || resolver->lookup != lookup) {
return; // stale, the id may have been reused
}
if (status == 0) {
status = deliver(result);
}
resolver->status = status;
resolver->running = false;
Core::Net::SignalExternal(id);
}).detach();
return 0;
}
s32 ReverseLookup(u32 addr, std::string* name) {
if (addr == htonl(INADDR_LOOPBACK)) {
*name = "localhost";
return 0;
}
if (!IsOnline()) {
return ORBIS_NET_ERROR_RESOLVER_ENODNS;
}
sockaddr_in in{};
in.sin_family = AF_INET;
in.sin_addr.s_addr = addr;
char host[1025]{}; // NI_MAXHOST
const int gai = getnameinfo(reinterpret_cast<const sockaddr*>(&in), sizeof(in), host,
sizeof(host), nullptr, 0, NI_NAMEREQD);
switch (gai) {
case 0:
*name = host;
return 0;
case EAI_NONAME:
return ORBIS_NET_ERROR_RESOLVER_ENOHOST;
case EAI_AGAIN:
return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT;
case EAI_FAIL:
return ORBIS_NET_ERROR_RESOLVER_ESERVERFAILURE;
default:
return ORBIS_NET_ERROR_RESOLVER_EINTERNAL;
}
}
} // namespace
s32 ResolverStartNtoa(s32 id, const char* hostname, ResolverOutput output, bool async,
bool disable_ipaddress) {
if (!Find(id)) {
return ORBIS_NET_ERROR_EBADF;
}
if (hostname == nullptr || (output.addr == nullptr && output.info == nullptr)) {
return ORBIS_NET_ERROR_EINVAL;
}
const size_t length = strnlen(hostname, ORBIS_NET_RESOLVER_HOSTNAME_LEN_MAX + 1);
if (length == 0 || length > static_cast<size_t>(ORBIS_NET_RESOLVER_HOSTNAME_LEN_MAX)) {
return ORBIS_NET_ERROR_EINVAL;
}
return RunLookup(
id, LookupKind::Ntoa, async,
[host = std::string{hostname}, allow_literal = !disable_ipaddress] {
std::vector<u32> addrs;
const s32 status = Lookup(host, allow_literal, &addrs);
if (status == 0) {
std::string list;
for (const u32 a : addrs) {
const u32 ip = ntohl(a);
list += fmt::format("{}{}.{}.{}.{}", list.empty() ? "" : ", ", ip >> 24,
(ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
}
LOG_INFO(Lib_Net, "{} resolved to {}", host, list);
} else {
LOG_INFO(Lib_Net, "{} not resolved: {}", host, ErrorCodeName(status));
}
return std::pair{status, std::move(addrs)};
},
[output](const std::vector<u32>& addrs) {
Deliver(output, addrs);
return s32{0};
});
}
s32 ResolverStartAton(s32 id, const OrbisNetInAddr* addr, char* hostname, s32 len, bool async) {
if (!Find(id)) {
return ORBIS_NET_ERROR_EBADF;
}
if (addr == nullptr || hostname == nullptr || len <= 0) {
return ORBIS_NET_ERROR_EINVAL;
}
return RunLookup(
id, LookupKind::Aton, async,
[address = addr->inaddr_addr] {
std::string name;
const s32 status = ReverseLookup(address, &name);
const u32 ip = ntohl(address);
LOG_INFO(Lib_Net, "reverse lookup of {}.{}.{}.{}: {}", ip >> 24, (ip >> 16) & 0xff,
(ip >> 8) & 0xff, ip & 0xff, status == 0 ? name : ErrorCodeName(status));
return std::pair{status, std::move(name)};
},
[hostname, len](const std::string& name) {
if (name.size() + 1 > static_cast<size_t>(len)) {
return s32{ORBIS_NET_ERROR_RESOLVER_ENOSPACE};
}
std::memcpy(hostname, name.c_str(), name.size() + 1);
return s32{0};
});
}
int ResolverGetError(s32 id, s32* status) {
const auto resolver = Find(id);
if (!resolver) {
return ORBIS_NET_EBADF;
}
std::scoped_lock lock{resolver->mutex};
*status = resolver->status;
return 0;
}
void SetResolverOnlineCheck(std::function<bool()> is_online) {
std::scoped_lock lock{g_mutex};
g_is_online = std::move(is_online);
}
} // namespace Libraries::Net
+27
View File
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include <string>
#include "common/types.h"
#include "core/libraries/net/net_types.h"
namespace Libraries::Net {
s32 CreateResolver(std::string name);
int DestroyResolver(s32 id);
int AbortResolver(s32 id, u32 flags);
struct ResolverOutput {
OrbisNetInAddr* addr = nullptr;
OrbisNetResolverInfo* info = nullptr;
};
s32 ResolverStartNtoa(s32 id, const char* hostname, ResolverOutput output, bool async,
bool disable_ipaddress);
s32 ResolverStartAton(s32 id, const OrbisNetInAddr* addr, char* hostname, s32 len, bool async);
int ResolverGetError(s32 id, s32* status);
void SetResolverOnlineCheck(std::function<bool()> is_online);
} // namespace Libraries::Net
+392
View File
@@ -0,0 +1,392 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstddef>
#include <cstring>
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_translate.h"
#ifdef _WIN32
#include <afunix.h>
#else
#include <netinet/tcp.h>
#include <sys/un.h>
#endif
namespace Libraries::Net {
std::optional<int> ToHostFamily(int orbis_family) {
switch (orbis_family) {
case ORBIS_NET_AF_UNIX:
return AF_UNIX;
case ORBIS_NET_AF_INET:
return AF_INET;
case ORBIS_NET_AF_INET6:
return AF_INET6;
default:
return std::nullopt;
}
}
int ToOrbisFamily(int host_family) {
switch (host_family) {
case AF_UNIX:
return ORBIS_NET_AF_UNIX;
case AF_INET:
return ORBIS_NET_AF_INET;
case AF_INET6:
return ORBIS_NET_AF_INET6;
default:
return 0;
}
}
std::optional<HostSocketKind> ToHostSocketKind(int family, int type, int protocol,
int* orbis_errno) {
if (family == ORBIS_NET_AF_UNIX) {
if (type != ORBIS_NET_SOCK_STREAM && type != ORBIS_NET_SOCK_DGRAM) {
*orbis_errno = ORBIS_NET_EPROTONOSUPPORT;
return std::nullopt;
}
return HostSocketKind{AF_UNIX, type == ORBIS_NET_SOCK_STREAM ? SOCK_STREAM : SOCK_DGRAM, 0,
false};
}
// PS4 returns EPROTONOSUPPORT here, not EAFNOSUPPORT.
if (family != ORBIS_NET_AF_INET) {
*orbis_errno = ORBIS_NET_EPROTONOSUPPORT;
return std::nullopt;
}
const auto host_family = ToHostFamily(family);
HostSocketKind kind{*host_family, 0, protocol, false};
switch (type) {
case ORBIS_NET_SOCK_STREAM:
kind.type = SOCK_STREAM;
break;
case ORBIS_NET_SOCK_DGRAM:
kind.type = SOCK_DGRAM;
break;
case ORBIS_NET_SOCK_RAW:
kind.type = SOCK_RAW;
break;
case ORBIS_NET_SOCK_DGRAM_P2P:
kind.type = SOCK_DGRAM;
kind.p2p = true;
break;
case ORBIS_NET_SOCK_STREAM_P2P:
kind.type = SOCK_STREAM;
kind.p2p = true;
break;
default:
*orbis_errno = ORBIS_NET_EPROTONOSUPPORT;
return std::nullopt;
}
if (type != ORBIS_NET_SOCK_RAW && protocol != 0) {
const int natural = kind.p2p ? -1
: kind.type == SOCK_STREAM ? ORBIS_NET_IPPROTO_TCP
: ORBIS_NET_IPPROTO_UDP;
if (protocol != natural) {
*orbis_errno = ORBIS_NET_EPROTOTYPE;
return std::nullopt;
}
}
return kind;
}
int ToOrbisSocketType(int host_type, bool p2p) {
if (host_type == SOCK_STREAM) {
return p2p ? ORBIS_NET_SOCK_STREAM_P2P : ORBIS_NET_SOCK_STREAM;
}
if (host_type == SOCK_DGRAM) {
return p2p ? ORBIS_NET_SOCK_DGRAM_P2P : ORBIS_NET_SOCK_DGRAM;
}
return ORBIS_NET_SOCK_RAW;
}
int ToHostSockaddr(const OrbisNetSockaddr* addr, u32 addrlen, sockaddr_storage* out,
socklen_t* out_len) {
if (addr == nullptr) {
return ORBIS_NET_EFAULT;
}
if (addrlen < 2) {
return ORBIS_NET_EINVAL;
}
*out = {};
switch (addr->sa_family) {
case ORBIS_NET_AF_INET: {
if (addrlen < sizeof(OrbisNetSockaddrIn)) {
return ORBIS_NET_EINVAL;
}
OrbisNetSockaddrIn in;
std::memcpy(&in, addr, sizeof(in)); // may be unaligned
auto* host = reinterpret_cast<sockaddr_in*>(out);
host->sin_family = AF_INET;
host->sin_port = in.sin_port;
std::memcpy(&host->sin_addr, &in.sin_addr, sizeof(in.sin_addr));
*out_len = sizeof(sockaddr_in);
return 0;
}
case ORBIS_NET_AF_INET6: {
if (addrlen < sizeof(OrbisNetSockaddrIn6)) {
return ORBIS_NET_EINVAL;
}
OrbisNetSockaddrIn6 in6;
std::memcpy(&in6, addr, sizeof(in6));
auto* host = reinterpret_cast<sockaddr_in6*>(out);
host->sin6_family = AF_INET6;
host->sin6_port = in6.sin6_port;
host->sin6_flowinfo = in6.sin6_flowinfo;
std::memcpy(&host->sin6_addr, in6.sin6_addr, sizeof(in6.sin6_addr));
host->sin6_scope_id = in6.sin6_scope_id;
*out_len = sizeof(sockaddr_in6);
return 0;
}
case ORBIS_NET_AF_UNIX: {
const u32 size = std::min<u32>(addrlen, sizeof(OrbisNetSockaddrUn));
const char* path = reinterpret_cast<const char*>(addr) + 2;
const size_t path_len = strnlen(path, size - 2);
auto* host = reinterpret_cast<sockaddr_un*>(out);
if (path_len >= sizeof(host->sun_path)) {
return ORBIS_NET_ENAMETOOLONG;
}
host->sun_family = AF_UNIX;
std::memcpy(host->sun_path, path, path_len);
host->sun_path[path_len] = '\0';
*out_len = static_cast<socklen_t>(offsetof(sockaddr_un, sun_path) + path_len + 1);
return 0;
}
default:
return ORBIS_NET_EAFNOSUPPORT;
}
}
namespace {
void CopyOut(const void* full, u32 full_len, OrbisNetSockaddr* out, u32* addrlen) {
if (out != nullptr) {
std::memcpy(out, full, std::min(*addrlen, full_len));
}
*addrlen = full_len;
}
} // namespace
void ToOrbisSockaddr(const sockaddr* addr, socklen_t len, OrbisNetSockaddr* out, u32* addrlen) {
if (addr->sa_family == AF_INET && len >= static_cast<socklen_t>(sizeof(sockaddr_in))) {
const auto* host = reinterpret_cast<const sockaddr_in*>(addr);
OrbisNetSockaddrIn in{};
in.sin_len = sizeof(in);
in.sin_family = ORBIS_NET_AF_INET;
in.sin_port = host->sin_port;
std::memcpy(&in.sin_addr, &host->sin_addr, sizeof(in.sin_addr));
CopyOut(&in, sizeof(in), out, addrlen);
} else if (addr->sa_family == AF_INET6 && len >= static_cast<socklen_t>(sizeof(sockaddr_in6))) {
const auto* host = reinterpret_cast<const sockaddr_in6*>(addr);
OrbisNetSockaddrIn6 in6{};
in6.sin6_len = sizeof(in6);
in6.sin6_family = ORBIS_NET_AF_INET6;
in6.sin6_port = host->sin6_port;
in6.sin6_flowinfo = host->sin6_flowinfo;
std::memcpy(in6.sin6_addr, &host->sin6_addr, sizeof(in6.sin6_addr));
in6.sin6_scope_id = host->sin6_scope_id;
CopyOut(&in6, sizeof(in6), out, addrlen);
} else if (addr->sa_family == AF_UNIX) {
// Unnamed sockets get an empty path.
const auto* host = reinterpret_cast<const sockaddr_un*>(addr);
OrbisNetSockaddrUn un{};
un.sun_len = sizeof(un);
un.sun_family = ORBIS_NET_AF_UNIX;
const auto offset = static_cast<socklen_t>(offsetof(sockaddr_un, sun_path));
if (len > offset) {
const size_t max = std::min<size_t>(len - offset, sizeof(un.sun_path) - 1);
std::memcpy(un.sun_path, host->sun_path, strnlen(host->sun_path, max));
}
CopyOut(&un, sizeof(un), out, addrlen);
} else {
*addrlen = 0;
}
}
int ToP2PAddress(const OrbisNetSockaddr* addr, u32 addrlen, P2PKind kind, P2P::Endpoint* endpoint,
u16* port) {
if (addr == nullptr) {
return ORBIS_NET_EFAULT;
}
if (addrlen != sizeof(OrbisNetSockaddrIn)) {
return ORBIS_NET_EINVAL;
}
if (addr->sa_family != ORBIS_NET_AF_INET) {
return ORBIS_NET_EAFNOSUPPORT;
}
OrbisNetSockaddrIn in;
std::memcpy(&in, addr, sizeof(in));
sockaddr_in host{};
host.sin_family = AF_INET;
std::memcpy(&host.sin_addr, &in.sin_addr, sizeof(in.sin_addr));
if (kind == P2PKind::Datagram) {
host.sin_port = in.sin_port;
*port = ntohs(in.sin_vport);
} else {
host.sin_port = in.sin_vport != 0 ? in.sin_vport : htons(DefaultP2PUdpPort);
*port = ntohs(in.sin_port);
}
*endpoint = P2P::Endpoint::FromSockaddr(reinterpret_cast<const sockaddr*>(&host), sizeof(host));
return 0;
}
void ToOrbisP2PAddress(const P2P::Endpoint& endpoint, u16 port, P2PKind kind, OrbisNetSockaddr* out,
u32* addrlen) {
OrbisNetSockaddrIn in{};
in.sin_len = sizeof(in);
in.sin_family = ORBIS_NET_AF_INET;
u16 udp_port = 0; // network byte order
if (endpoint.Family() == AF_INET) {
const auto* host = reinterpret_cast<const sockaddr_in*>(&endpoint.addr);
udp_port = host->sin_port;
std::memcpy(&in.sin_addr, &host->sin_addr, sizeof(in.sin_addr));
}
if (kind == P2PKind::Datagram) {
in.sin_port = udp_port;
in.sin_vport = htons(port);
} else {
in.sin_port = htons(port);
in.sin_vport = udp_port;
}
CopyOut(&in, sizeof(in), out, addrlen);
}
HostMsgFlags ToHostMsgFlags(int orbis_flags) {
constexpr int kNoSignal = 0x20000; // FreeBSD MSG_NOSIGNAL, always implied
HostMsgFlags out{};
int rest = orbis_flags;
if ((rest & ORBIS_NET_MSG_PEEKLEN) == ORBIS_NET_MSG_PEEKLEN) {
out.peeklen = true;
rest &= ~(ORBIS_NET_MSG_PEEKLEN & ~ORBIS_NET_MSG_PEEK);
}
if (rest & ORBIS_NET_MSG_PEEK) {
out.host |= MSG_PEEK;
out.peek = true;
rest &= ~ORBIS_NET_MSG_PEEK;
}
if (rest & ORBIS_NET_MSG_WAITALL) {
out.host |= MSG_WAITALL; // host socket is non-blocking, we loop
out.waitall = true;
rest &= ~ORBIS_NET_MSG_WAITALL;
}
if (rest & ORBIS_NET_MSG_DONTWAIT) {
out.dontwait = true;
rest &= ~ORBIS_NET_MSG_DONTWAIT;
}
if (rest & ORBIS_NET_MSG_USECRYPTO) {
out.crypto = true;
rest &= ~ORBIS_NET_MSG_USECRYPTO;
}
if (rest & ORBIS_NET_MSG_USESIGNATURE) {
out.signature = true;
rest &= ~ORBIS_NET_MSG_USESIGNATURE;
}
rest &= ~kNoSignal;
out.unsupported = rest; // e.g. MSG_OOB
return out;
}
std::optional<HostOption> ToHostOption(int level, int name) {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
switch (name) {
case ORBIS_NET_SO_KEEPALIVE:
return HostOption{SOL_SOCKET, SO_KEEPALIVE, OptionValue::Int};
case ORBIS_NET_SO_BROADCAST:
return HostOption{SOL_SOCKET, SO_BROADCAST, OptionValue::Int};
case ORBIS_NET_SO_SNDBUF:
return HostOption{SOL_SOCKET, SO_SNDBUF, OptionValue::Int};
case ORBIS_NET_SO_RCVBUF:
return HostOption{SOL_SOCKET, SO_RCVBUF, OptionValue::Int};
case ORBIS_NET_SO_LINGER:
return HostOption{SOL_SOCKET, Core::Net::Host::LingerOption, OptionValue::Linger};
default:
return std::nullopt;
}
case ORBIS_NET_IPPROTO_IP:
// Values differ per host (IP_TTL is 2 on Linux), map by name.
switch (name) {
case ORBIS_NET_IP_HDRINCL:
return HostOption{IPPROTO_IP, IP_HDRINCL, OptionValue::Int};
case ORBIS_NET_IP_TOS:
return HostOption{IPPROTO_IP, IP_TOS, OptionValue::Int};
case ORBIS_NET_IP_TTL:
return HostOption{IPPROTO_IP, IP_TTL, OptionValue::Int};
case ORBIS_NET_IP_MULTICAST_IF:
return HostOption{IPPROTO_IP, IP_MULTICAST_IF, OptionValue::Raw};
case ORBIS_NET_IP_MULTICAST_TTL:
return HostOption{IPPROTO_IP, IP_MULTICAST_TTL, OptionValue::Int};
case ORBIS_NET_IP_MULTICAST_LOOP:
return HostOption{IPPROTO_IP, IP_MULTICAST_LOOP, OptionValue::Int};
case ORBIS_NET_IP_ADD_MEMBERSHIP:
return HostOption{IPPROTO_IP, IP_ADD_MEMBERSHIP, OptionValue::Raw};
case ORBIS_NET_IP_DROP_MEMBERSHIP:
return HostOption{IPPROTO_IP, IP_DROP_MEMBERSHIP, OptionValue::Raw};
default:
return std::nullopt;
}
case ORBIS_NET_IPPROTO_TCP:
switch (name) {
case ORBIS_NET_TCP_NODELAY:
return HostOption{IPPROTO_TCP, TCP_NODELAY, OptionValue::Int};
#ifdef TCP_MAXSEG
case ORBIS_NET_TCP_MAXSEG:
return HostOption{IPPROTO_TCP, TCP_MAXSEG, OptionValue::Int};
#endif
default:
return std::nullopt;
}
default:
return std::nullopt;
}
}
bool IsReservedPort(u16 port) {
return (port >= 1 && port <= 1023) || port == 5353 || (port >= 8540 && port <= 8579) ||
(port >= 9293 && port <= 9310) || port >= 40000;
}
bool IsReservedP2PVport(u16 vport) {
return vport == 5353 || vport >= 32768;
}
s32 DefaultSendBuffer(int host_type) {
return host_type == SOCK_STREAM ? 32768 : 9216;
}
s32 DefaultReceiveBuffer(int host_type) {
return host_type == SOCK_STREAM ? 65536 : 40 * 1024;
}
u32 ToHostEpollEvents(u32 orbis_events) {
u32 out = 0;
out |= (orbis_events & ORBIS_NET_EPOLLIN) ? Host::EvIn : 0u;
out |= (orbis_events & ORBIS_NET_EPOLLOUT) ? Host::EvOut : 0u;
out |= (orbis_events & ORBIS_NET_EPOLLERR) ? Host::EvErr : 0u;
out |= (orbis_events & ORBIS_NET_EPOLLHUP) ? Host::EvHup : 0u;
return out;
}
u32 ToOrbisEpollEvents(u32 host_events) {
u32 out = 0;
out |= (host_events & Host::EvIn) ? ORBIS_NET_EPOLLIN : 0u;
out |= (host_events & Host::EvOut) ? ORBIS_NET_EPOLLOUT : 0u;
out |= (host_events & Host::EvErr) ? ORBIS_NET_EPOLLERR : 0u;
out |= (host_events & Host::EvHup) ? ORBIS_NET_EPOLLHUP : 0u;
out |= (host_events & Host::EvDescId) ? ORBIS_NET_EPOLLDESCID : 0u;
return out;
}
int ToOrbisErrno(Host::Error error) {
if (error == Host::Error::NotSock) {
return ORBIS_NET_EBADF;
}
return static_cast<int>(error); // already FreeBSD errno values
}
} // namespace Libraries::Net
+73
View File
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include "core/libraries/net/net_types.h"
#include "core/net/host_net.h"
#include "core/net/p2p_transport.h"
namespace Libraries::Net {
namespace Host = Core::Net::Host;
namespace P2P = Core::Net::P2P;
std::optional<int> ToHostFamily(int orbis_family);
int ToOrbisFamily(int host_family);
struct HostSocketKind {
int family;
int type;
int protocol;
bool p2p;
};
std::optional<HostSocketKind> ToHostSocketKind(int family, int type, int protocol,
int* orbis_errno);
int ToOrbisSocketType(int host_type, bool p2p);
int ToHostSockaddr(const OrbisNetSockaddr* addr, u32 addrlen, sockaddr_storage* out,
socklen_t* out_len);
void ToOrbisSockaddr(const sockaddr* addr, socklen_t len, OrbisNetSockaddr* out, u32* addrlen);
enum class P2PKind { Datagram, Stream };
inline constexpr u16 DefaultP2PUdpPort = 3658;
int ToP2PAddress(const OrbisNetSockaddr* addr, u32 addrlen, P2PKind kind, P2P::Endpoint* endpoint,
u16* port);
void ToOrbisP2PAddress(const P2P::Endpoint& endpoint, u16 port, P2PKind kind, OrbisNetSockaddr* out,
u32* addrlen);
struct HostMsgFlags {
int host = 0;
bool dontwait = false;
bool peek = false;
bool peeklen = false;
bool waitall = false;
bool crypto = false;
bool signature = false;
int unsupported = 0;
};
HostMsgFlags ToHostMsgFlags(int orbis_flags);
enum class OptionValue {
Int,
Linger,
Raw,
};
struct HostOption {
int level;
int name;
OptionValue value;
};
std::optional<HostOption> ToHostOption(int level, int name);
bool IsReservedPort(u16 port);
bool IsReservedP2PVport(u16 vport);
s32 DefaultSendBuffer(int host_type); // TCP 32768, UDP and RAW 9216
s32 DefaultReceiveBuffer(int host_type); // TCP 65536, UDP and RAW 40960
u32 ToHostEpollEvents(u32 orbis_events);
u32 ToOrbisEpollEvents(u32 host_events);
int ToOrbisErrno(Host::Error error);
} // namespace Libraries::Net
+337
View File
@@ -0,0 +1,337 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Libraries::Net {
using OrbisNetId = s32;
enum OrbisNetFamily : u32 {
ORBIS_NET_AF_UNIX = 1,
ORBIS_NET_AF_INET = 2,
ORBIS_NET_AF_INET6 = 28,
};
enum OrbisNetSocketType : u32 {
ORBIS_NET_SOCK_STREAM = 1,
ORBIS_NET_SOCK_DGRAM = 2,
ORBIS_NET_SOCK_RAW = 3,
ORBIS_NET_SOCK_DGRAM_P2P = 6,
ORBIS_NET_SOCK_STREAM_P2P = 10,
};
enum OrbisNetProtocol : u32 {
ORBIS_NET_IPPROTO_IP = 0,
ORBIS_NET_IPPROTO_ICMP = 1,
ORBIS_NET_IPPROTO_IGMP = 2,
ORBIS_NET_IPPROTO_TCP = 6,
ORBIS_NET_IPPROTO_UDP = 17,
ORBIS_NET_IPPROTO_IPV6 = 41,
ORBIS_NET_SOL_SOCKET = 0xFFFF,
};
enum OrbisNetSocketIpOption : u32 {
ORBIS_NET_IP_HDRINCL = 2,
ORBIS_NET_IP_TOS = 3,
ORBIS_NET_IP_TTL = 4,
ORBIS_NET_IP_MULTICAST_IF = 9,
ORBIS_NET_IP_MULTICAST_TTL = 10,
ORBIS_NET_IP_MULTICAST_LOOP = 11,
ORBIS_NET_IP_ADD_MEMBERSHIP = 12,
ORBIS_NET_IP_DROP_MEMBERSHIP = 13,
ORBIS_NET_IP_TTLCHK = 30,
ORBIS_NET_IP_MAXTTL = 31,
ORBIS_NET_IP_DONTFRAG = 67,
};
enum OrbisNetSocketTcpOption : u32 {
ORBIS_NET_TCP_NODELAY = 1,
ORBIS_NET_TCP_MAXSEG = 2,
ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3,
ORBIS_NET_TCP_KEEPLISTEN = 128,
};
enum OrbisNetSocketUdpOption : u32 {
ORBIS_NET_UDP_SND_ON_SUSPEND = 128,
};
// SO_POLICY / SO_PRIORITY ranges
inline constexpr s32 ORBIS_NET_SOCK_POLICY_NUM_MIN = 0;
inline constexpr s32 ORBIS_NET_SOCK_POLICY_NUM_MAX = 15;
inline constexpr s32 ORBIS_NET_SOCK_POLICY_NA = -1; // not applicable
inline constexpr s32 ORBIS_NET_SOCK_PRIORITY_NUM_USER_MIN = 8;
inline constexpr s32 ORBIS_NET_SOCK_PRIORITY_NUM_USER_MAX = 23;
// sceNetSocketAbort flags
inline constexpr u32 ORBIS_NET_SOCKET_ABORT_FLAG_RCV_PRESERVATION = 0x1;
inline constexpr u32 ORBIS_NET_SOCKET_ABORT_FLAG_SND_PRESERVATION = 0x2;
inline constexpr u32 ORBIS_NET_SOCKET_ABORT_FLAG_SND_PRESERVATION_AGAIN = 0x4;
inline constexpr u16 ORBIS_NET_ADHOC_PORT = 3658;
enum OrbisNetSocketSoOption : u32 {
ORBIS_NET_SO_REUSEADDR = 0x00000004,
ORBIS_NET_SO_KEEPALIVE = 0x00000008,
ORBIS_NET_SO_BROADCAST = 0x00000020,
ORBIS_NET_SO_LINGER = 0x00000080,
ORBIS_NET_SO_REUSEPORT = 0x00000200,
ORBIS_NET_SO_ONESBCAST = 0x00010000,
ORBIS_NET_SO_USECRYPTO = 0x00020000,
ORBIS_NET_SO_USESIGNATURE = 0x00040000,
ORBIS_NET_SO_SNDBUF = 0x1001,
ORBIS_NET_SO_RCVBUF = 0x1002,
ORBIS_NET_SO_ERROR = 0x1007,
ORBIS_NET_SO_TYPE = 0x1008,
ORBIS_NET_SO_SNDTIMEO = 0x1105,
ORBIS_NET_SO_RCVTIMEO = 0x1106,
ORBIS_NET_SO_ERROR_EX = 0x1107,
ORBIS_NET_SO_ACCEPTTIMEO = 0x1108,
ORBIS_NET_SO_CONNECTTIMEO = 0x1109,
ORBIS_NET_SO_NBIO = 0x1200,
ORBIS_NET_SO_POLICY = 0x1201,
ORBIS_NET_SO_NAME = 0x1202,
ORBIS_NET_SO_PRIORITY = 0x1203,
};
enum OrbisNetFlags : u32 {
ORBIS_NET_MSG_PEEK = 0x00000002,
ORBIS_NET_MSG_WAITALL = 0x00000040,
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK),
};
enum OrbisNetShutdown : s32 {
ORBIS_NET_SHUT_RD = 0,
ORBIS_NET_SHUT_WR = 1,
ORBIS_NET_SHUT_RDWR = 2,
};
enum OrbisNetEpollFlag : u32 {
ORBIS_NET_EPOLL_CTL_ADD = 1,
ORBIS_NET_EPOLL_CTL_MOD = 2,
ORBIS_NET_EPOLL_CTL_DEL = 3,
};
enum OrbisNetEpollEvents : u32 {
ORBIS_NET_EPOLLIN = 0x1,
ORBIS_NET_EPOLLOUT = 0x2,
ORBIS_NET_EPOLLERR = 0x8,
ORBIS_NET_EPOLLHUP = 0x10,
ORBIS_NET_EPOLLDESCID = 0x10000,
};
struct OrbisNetSockaddr {
u8 sa_len;
u8 sa_family;
char sa_data[14];
};
struct OrbisNetSockaddrIn {
u8 sin_len;
u8 sin_family;
u16 sin_port; // network byte order
u32 sin_addr; // network byte order
u16 sin_vport; // P2P virtual port, network byte order
char sin_zero[6];
};
struct OrbisNetSockaddrIn6 {
u8 sin6_len;
u8 sin6_family;
u16 sin6_port; // network byte order
u32 sin6_flowinfo;
u8 sin6_addr[16];
u32 sin6_scope_id;
};
struct OrbisNetLinger {
s32 l_onoff;
s32 l_linger;
};
union OrbisNetEpollData {
void* ptr;
u32 data_u32;
s32 fd;
u64 data_u64;
};
struct OrbisNetEpollEvent {
u32 events;
u32 pad;
u64 ident;
OrbisNetEpollData data;
};
struct OrbisNetInAddr {
u32 inaddr_addr; // network byte order
};
// Host byte order
inline constexpr u32 ORBIS_NET_INADDR_ANY = 0x00000000;
inline constexpr u32 ORBIS_NET_INADDR_LOOPBACK = 0x7f000001;
inline constexpr u32 ORBIS_NET_INADDR_BROADCAST = 0xffffffff;
inline constexpr u32 ORBIS_NET_INADDR_UNSPEC_GROUP = 0xe0000000;
inline constexpr u32 ORBIS_NET_INADDR_AUTOIP = 0xa9fe0000;
inline constexpr u32 ORBIS_NET_IN_CLASSD_NET = 0xf0000000;
inline constexpr u32 ORBIS_NET_IN_AUTOIP_NET = 0xffff0000;
inline constexpr int ORBIS_NET_INET_ADDRSTRLEN = 16;
inline constexpr int ORBIS_NET_ETHER_ADDRSTRLEN = 18;
// Option value for ORBIS_NET_UDP_SND_ON_SUSPEND.
struct OrbisNetUdpSndOnSuspend {
s32 onoff;
struct OrbisNetSockaddr* addr;
u32 addrlen;
void* data;
u32 datalen;
};
inline constexpr u32 ORBIS_NET_UDP_SND_ON_SUSPEND_DATALEN_MAX = 512;
struct OrbisNetMemoryPoolStats {
u64 pool_size;
u64 max_inuse_size;
u64 current_inuse_size;
s32 reserved;
};
struct OrbisNetStatisticsInfo {
s32 kernel_mem_free_size;
s32 kernel_mem_free_min;
s32 packet_count;
s32 packet_qos_count;
s32 libnet_mem_free_size;
s32 libnet_mem_free_min;
};
struct OrbisNetSockaddrUn {
u8 sun_len;
u8 sun_family;
char sun_path[104];
};
struct OrbisNetIovec {
void* iov_base;
u64 iov_len;
};
struct OrbisNetMsghdr {
void* msg_name;
u32 msg_namelen;
OrbisNetIovec* msg_iov;
s32 msg_iovlen;
void* msg_control;
u32 msg_controllen;
s32 msg_flags;
};
enum OrbisNetResolverFlag : u32 {
ORBIS_NET_RESOLVER_ASYNC = 0x1,
ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000,
};
inline constexpr u32 ORBIS_NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION = 0x1;
inline constexpr u32 ORBIS_NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION = 0x2;
inline constexpr int ORBIS_NET_RESOLVER_HOSTNAME_LEN_MAX = 255;
union OrbisNetAddrUnion {
OrbisNetInAddr addr;
u8 addr6[16];
};
struct OrbisNetResolverAddr {
OrbisNetAddrUnion u;
u32 af;
u32 pad[3];
};
struct OrbisNetResolverInfo {
OrbisNetResolverAddr addrs[10];
u32 records;
u32 recordsv4;
u32 pad[14];
};
inline constexpr int ORBIS_NET_DEBUG_NAME_LEN_MAX = 31;
enum OrbisNetSockInfoState : s32 {
ORBIS_NET_SOCKINFO_STATE_UNKNOWN = 0,
ORBIS_NET_SOCKINFO_STATE_CLOSED = 1,
ORBIS_NET_SOCKINFO_STATE_OPENED = 2,
ORBIS_NET_SOCKINFO_STATE_LISTEN = 3,
ORBIS_NET_SOCKINFO_STATE_SYN_SENT = 4,
ORBIS_NET_SOCKINFO_STATE_SYN_RECEIVED = 5,
ORBIS_NET_SOCKINFO_STATE_ESTABLISHED = 6,
ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_1 = 7,
ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_2 = 8,
ORBIS_NET_SOCKINFO_STATE_CLOSE_WAIT = 9,
ORBIS_NET_SOCKINFO_STATE_CLOSING = 10,
ORBIS_NET_SOCKINFO_STATE_LAST_ACK = 11,
ORBIS_NET_SOCKINFO_STATE_TIME_WAIT = 12,
};
enum OrbisNetSockInfoFlags : s32 {
ORBIS_NET_SOCKINFO_F_SELF = 0x00000001,
ORBIS_NET_SOCKINFO_F_KERNEL = 0x00000002,
ORBIS_NET_SOCKINFO_F_OTHERS = 0x00000004,
ORBIS_NET_SOCKINFO_F_RECV_WAIT = 0x00010000,
ORBIS_NET_SOCKINFO_F_SEND_WAIT = 0x00020000,
ORBIS_NET_SOCKINFO_F_RECV_EWAIT = 0x00040000,
ORBIS_NET_SOCKINFO_F_SEND_EWAIT = 0x00080000,
ORBIS_NET_SOCKINFO_F_NONBLOCK = 0x00200000,
ORBIS_NET_SOCKINFO_F_ALL = 0x006F0007,
};
struct OrbisNetSockInfo {
char name[ORBIS_NET_DEBUG_NAME_LEN_MAX + 1];
s32 pid;
OrbisNetId s;
s8 socket_type;
s8 policy;
s8 priority;
s8 reserved8;
s32 recv_queue_length;
s32 send_queue_length;
OrbisNetInAddr local_adr;
OrbisNetInAddr remote_adr;
u16 local_port; // network byte order
u16 remote_port;
u16 local_vport;
u16 remote_vport;
s32 state;
s32 flags;
s32 tx_bps;
s32 rx_bps;
s32 max_tx_bps;
s32 max_rx_bps;
s32 tx_vbps;
s32 rx_vbps;
s32 recv_buffer_size;
s32 send_buffer_size;
s32 reserved6[8];
s32 tx_drops;
s32 rx_drops;
s32 tx_wait;
s32 reserved[2];
};
struct OrbisNetEtherAddr {
u8 data[6];
};
static_assert(sizeof(OrbisNetSockaddr) == 16);
static_assert(sizeof(OrbisNetSockaddrIn) == 16);
static_assert(sizeof(OrbisNetSockaddrIn6) == 28);
static_assert(sizeof(OrbisNetEpollEvent) == 24);
static_assert(sizeof(OrbisNetSockaddrUn) == 106);
static_assert(sizeof(OrbisNetMsghdr) == 48);
static_assert(sizeof(OrbisNetResolverInfo) == 384);
static_assert(sizeof(OrbisNetSockInfo) == 160);
static_assert(sizeof(OrbisNetUdpSndOnSuspend) == 40);
static_assert(sizeof(OrbisNetMemoryPoolStats) == 32);
static_assert(sizeof(OrbisNetStatisticsInfo) == 24);
} // namespace Libraries::Net
@@ -25,6 +25,11 @@ UPnPClient& UPnPClient::Instance() {
UPnPClient::~UPnPClient() {
if (m_thread.joinable())
m_thread.join();
if (m_map_thread.joinable())
m_map_thread.join();
// Mappings have no lease time: leave nothing behind on the router.
if (const u16 port = m_mapped_port.exchange(0); port != 0)
RemoveMapping(port);
if (m_available.load())
FreeUPNPUrls(&m_urls);
}
@@ -136,6 +141,31 @@ void UPnPClient::AddMapping(u16 port) {
}
}
void UPnPClient::MapPortAsync(u16 port) {
std::scoped_lock lock{m_map_mutex};
if (m_map_thread.joinable())
m_map_thread.join();
m_map_thread = std::thread([this, port] {
if (!WaitReady(10'000)) {
LOG_INFO(Lib_Net, "UPNP: no IGD, UDP port {} is not forwarded", port);
return;
}
const u16 previous = m_mapped_port.exchange(port);
if (previous != 0 && previous != port)
RemoveMapping(previous);
AddMapping(port);
});
}
void UPnPClient::UnmapPort(u16 port) {
std::scoped_lock lock{m_map_mutex};
if (m_map_thread.joinable())
m_map_thread.join();
u16 expected = port;
if (m_mapped_port.compare_exchange_strong(expected, 0))
RemoveMapping(port);
}
void UPnPClient::RemoveMapping(u16 port) {
if (!m_available.load())
return;
@@ -40,6 +40,12 @@ public:
void AddMapping(u16 port);
void RemoveMapping(u16 port);
// Forwards `port` once discovery has finished (waiting up to 10 s for it), on a background
// thread so the caller never waits for the router. Replaces an earlier request's mapping.
void MapPortAsync(u16 port);
// Removes what MapPortAsync forwarded, if it is `port`.
void UnmapPort(u16 port);
private:
UPnPClient() = default;
~UPnPClient();
@@ -54,6 +60,7 @@ private:
std::atomic<bool> m_available{false};
std::atomic<u32> m_external_ip_nbo{0};
std::atomic<u16> m_external_port{0};
std::atomic<u16> m_mapped_port{0}; // forwarded by MapPortAsync, removed on exit
char m_external_ip_str[16]{};
char m_lan_addr[64]{};
@@ -63,6 +70,8 @@ private:
std::mutex m_mutex;
std::condition_variable m_cv;
std::thread m_thread;
std::mutex m_map_mutex; // serializes MapPortAsync / UnmapPort
std::thread m_map_thread;
};
} // namespace Libraries::Net
@@ -39,8 +39,8 @@ typedef int net_socket;
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "net.h"
#include "net_error.h"
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_types.h"
#include "net_util.h"
namespace NetUtil {
+4
View File
@@ -2583,6 +2583,10 @@ int PS4_SYSV_ABI sceHttpsDisableOptionPrivate(int id, u32 sslFlags) {
int PS4_SYSV_ABI sceHttpsEnableOption(int id, u32 sslFlags) {
LOG_INFO(Lib_Http, "called id={}, sslFlags={:#x}", id, sslFlags);
if (EmulatorSettings.IsForcedHttpsDisabled()) {
LOG_INFO(Lib_Http, "HTTPS is forced disabled, disabling options for id={}", id);
return sceHttpsDisableOption(id, sslFlags);
}
std::lock_guard<std::mutex> lock(g_state.m_mutex);
if (!g_state.inited) {
LOG_ERROR(Lib_Http, "Not initialized");
File diff suppressed because it is too large Load Diff
-560
View File
@@ -1,560 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "common/types.h"
#include "netctl.h"
namespace Core::Loader {
class SymbolsResolver;
}
// Define our own htonll and ntohll because its not available in some systems/platforms
#ifndef HTONLL
#define HTONLL(x) (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32))
#endif
#ifndef NTOHLL
#define NTOHLL(x) (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))
#endif
namespace Libraries::Net {
static int ConvertFamilies(int family);
enum OrbisNetFamily : u32 {
ORBIS_NET_AF_UNIX = 1,
ORBIS_NET_AF_INET = 2,
ORBIS_NET_AF_INET6 = 28,
};
enum OrbisNetSocketType : u32 {
ORBIS_NET_SOCK_STREAM = 1,
ORBIS_NET_SOCK_DGRAM = 2,
ORBIS_NET_SOCK_RAW = 3,
ORBIS_NET_SOCK_DGRAM_P2P = 6,
ORBIS_NET_SOCK_STREAM_P2P = 10
};
enum OrbisNetProtocol : u32 {
ORBIS_NET_IPPROTO_IP = 0,
ORBIS_NET_IPPROTO_ICMP = 1,
ORBIS_NET_IPPROTO_IGMP = 2,
ORBIS_NET_IPPROTO_TCP = 6,
ORBIS_NET_IPPROTO_UDP = 17,
ORBIS_NET_IPPROTO_IPV6 = 41,
ORBIS_NET_SOL_SOCKET = 0xFFFF
};
constexpr std::string_view NameOf(OrbisNetProtocol p) {
switch (p) {
case ORBIS_NET_IPPROTO_IP:
return "ORBIS_NET_IPPROTO_IP";
case ORBIS_NET_IPPROTO_ICMP:
return "ORBIS_NET_IPPROTO_ICMP";
case ORBIS_NET_IPPROTO_IGMP:
return "ORBIS_NET_IPPROTO_IGMP";
case ORBIS_NET_IPPROTO_TCP:
return "ORBIS_NET_IPPROTO_TCP";
case ORBIS_NET_IPPROTO_UDP:
return "ORBIS_NET_IPPROTO_UDP";
case ORBIS_NET_IPPROTO_IPV6:
return "ORBIS_NET_IPPROTO_IPV6";
case ORBIS_NET_SOL_SOCKET:
return "ORBIS_NET_SOL_SOCKET";
default:
UNREACHABLE_MSG("{}", (u32)p);
}
}
enum OrbisNetSocketIpOption : u32 {
ORBIS_NET_IP_HDRINCL = 2,
ORBIS_NET_IP_TOS = 3,
ORBIS_NET_IP_TTL = 4,
ORBIS_NET_IP_MULTICAST_IF = 9,
ORBIS_NET_IP_MULTICAST_TTL = 10,
ORBIS_NET_IP_MULTICAST_LOOP = 11,
ORBIS_NET_IP_ADD_MEMBERSHIP = 12,
ORBIS_NET_IP_DROP_MEMBERSHIP = 13,
ORBIS_NET_IP_TTLCHK = 30,
ORBIS_NET_IP_MAXTTL = 31,
};
enum OrbisNetSocketTcpOption : u32 {
ORBIS_NET_TCP_NODELAY = 1,
ORBIS_NET_TCP_MAXSEG = 2,
ORBIS_NET_TCP_MSS_TO_ADVERTISE = 3,
};
enum OrbisNetSocketSoOption : u32 {
ORBIS_NET_SO_REUSEADDR = 0x00000004,
ORBIS_NET_SO_KEEPALIVE = 0x00000008,
ORBIS_NET_SO_BROADCAST = 0x00000020,
ORBIS_NET_SO_LINGER = 0x00000080,
ORBIS_NET_SO_REUSEPORT = 0x00000200,
ORBIS_NET_SO_ONESBCAST = 0x00010000,
ORBIS_NET_SO_USECRYPTO = 0x00020000,
ORBIS_NET_SO_USESIGNATURE = 0x00040000,
ORBIS_NET_SO_SNDBUF = 0x1001,
ORBIS_NET_SO_RCVBUF = 0x1002,
ORBIS_NET_SO_ERROR = 0x1007,
ORBIS_NET_SO_TYPE = 0x1008,
ORBIS_NET_SO_SNDTIMEO = 0x1105,
ORBIS_NET_SO_RCVTIMEO = 0x1106,
ORBIS_NET_SO_ERROR_EX = 0x1107,
ORBIS_NET_SO_ACCEPTTIMEO = 0x1108,
ORBIS_NET_SO_CONNECTTIMEO = 0x1109,
ORBIS_NET_SO_NBIO = 0x1200,
ORBIS_NET_SO_POLICY = 0x1201,
ORBIS_NET_SO_NAME = 0x1202,
ORBIS_NET_SO_PRIORITY = 0x1203
};
enum OrbisNetFlags : u32 {
ORBIS_NET_MSG_PEEK = 0x00000002,
ORBIS_NET_MSG_WAITALL = 0x00000040,
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
};
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
switch (o) {
case ORBIS_NET_SO_REUSEADDR:
return "ORBIS_NET_SO_REUSEADDR";
case ORBIS_NET_SO_KEEPALIVE:
return "ORBIS_NET_SO_KEEPALIVE";
case ORBIS_NET_SO_BROADCAST:
return "ORBIS_NET_SO_BROADCAST";
case ORBIS_NET_SO_LINGER:
return "ORBIS_NET_SO_LINGER";
case ORBIS_NET_SO_REUSEPORT:
return "ORBIS_NET_SO_REUSEPORT";
case ORBIS_NET_SO_ONESBCAST:
return "ORBIS_NET_SO_ONESBCAST";
case ORBIS_NET_SO_USECRYPTO:
return "ORBIS_NET_SO_USECRYPTO";
case ORBIS_NET_SO_USESIGNATURE:
return "ORBIS_NET_SO_USESIGNATURE";
case ORBIS_NET_SO_SNDBUF:
return "ORBIS_NET_SO_SNDBUF";
case ORBIS_NET_SO_RCVBUF:
return "ORBIS_NET_SO_RCVBUF";
case ORBIS_NET_SO_ERROR:
return "ORBIS_NET_SO_ERROR";
case ORBIS_NET_SO_TYPE:
return "ORBIS_NET_SO_TYPE";
case ORBIS_NET_SO_SNDTIMEO:
return "ORBIS_NET_SO_SNDTIMEO";
case ORBIS_NET_SO_RCVTIMEO:
return "ORBIS_NET_SO_RCVTIMEO";
case ORBIS_NET_SO_ERROR_EX:
return "ORBIS_NET_SO_ERROR_EX";
case ORBIS_NET_SO_ACCEPTTIMEO:
return "ORBIS_NET_SO_ACCEPTTIMEO";
case ORBIS_NET_SO_CONNECTTIMEO:
return "ORBIS_NET_SO_CONNECTTIMEO";
case ORBIS_NET_SO_NBIO:
return "ORBIS_NET_SO_NBIO";
case ORBIS_NET_SO_POLICY:
return "ORBIS_NET_SO_POLICY";
case ORBIS_NET_SO_NAME:
return "ORBIS_NET_SO_NAME";
case ORBIS_NET_SO_PRIORITY:
return "ORBIS_NET_SO_PRIORITY";
default:
UNREACHABLE_MSG("{}", (u32)o);
}
}
enum OrbisNetEpollFlag : u32 {
ORBIS_NET_EPOLL_CTL_ADD = 1,
ORBIS_NET_EPOLL_CTL_MOD = 2,
ORBIS_NET_EPOLL_CTL_DEL = 3,
};
enum OrbisNetEpollEvents : u32 {
ORBIS_NET_EPOLLIN = 0x1,
ORBIS_NET_EPOLLOUT = 0x2,
ORBIS_NET_EPOLLERR = 0x8,
ORBIS_NET_EPOLLHUP = 0x10,
ORBIS_NET_EPOLLDESCID = 0x10000,
};
enum OrbisNetResolverFlag : u32 {
ORBIS_NET_RESOLVER_ASYNC = 0x1,
ORBIS_NET_RESOLVER_START_NTOA_DISABLE_IPADDRESS = 0x10000,
};
using OrbisNetId = s32;
struct OrbisNetSockaddr {
u8 sa_len;
u8 sa_family;
char sa_data[14];
};
struct OrbisNetSockaddrIn {
u8 sin_len;
u8 sin_family;
u16 sin_port;
u32 sin_addr;
u16 sin_vport;
char sin_zero[6];
};
using OrbisNetInAddr_t = u32;
struct OrbisNetInAddr {
OrbisNetInAddr_t inaddr_addr;
};
struct OrbisNetSockaddrUn {
u8 sun_len;
u8 sun_family; // AF_UNIX
char sun_path[104];
};
struct OrbisNetIovec {
void* iov_base;
u64 iov_len;
};
struct OrbisNetMsghdr {
void* msg_name;
u32 msg_namelen;
OrbisNetIovec* msg_iov;
int msg_iovlen;
void* msg_control;
u32 msg_controllen;
int msg_flags;
};
union OrbisNetEpollData {
void* ptr;
u32 data_u32;
int fd;
u64 data_u64;
};
struct OrbisNetEpollEvent {
u32 events;
u32 pad;
u64 ident;
OrbisNetEpollData data;
};
union OrbisNetAddrUnion {
OrbisNetInAddr addr;
u8 addr6[16];
};
struct OrbisNetResolverAddr {
OrbisNetAddrUnion u;
u32 af;
u32 pad[3];
};
struct OrbisNetResolverInfo {
OrbisNetResolverAddr addrs[10];
u32 records;
u32 recordsv4;
u32 pad[14];
};
constexpr int ORBIS_NET_DEBUG_NAME_LEN_MAX = 31;
enum OrbisNetSockInfoState : s32 {
ORBIS_NET_SOCKINFO_STATE_UNKNOWN = 0,
ORBIS_NET_SOCKINFO_STATE_CLOSED = 1,
ORBIS_NET_SOCKINFO_STATE_OPENED = 2,
ORBIS_NET_SOCKINFO_STATE_LISTEN = 3,
ORBIS_NET_SOCKINFO_STATE_SYN_SENT = 4,
ORBIS_NET_SOCKINFO_STATE_SYN_RECEIVED = 5,
ORBIS_NET_SOCKINFO_STATE_ESTABLISHED = 6,
ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_1 = 7,
ORBIS_NET_SOCKINFO_STATE_FIN_WAIT_2 = 8,
ORBIS_NET_SOCKINFO_STATE_CLOSE_WAIT = 9,
ORBIS_NET_SOCKINFO_STATE_CLOSING = 10,
ORBIS_NET_SOCKINFO_STATE_LAST_ACK = 11,
ORBIS_NET_SOCKINFO_STATE_TIME_WAIT = 12,
};
enum OrbisNetSockInfoFlags : s32 {
ORBIS_NET_SOCKINFO_F_SELF = 0x00000001,
ORBIS_NET_SOCKINFO_F_KERNEL = 0x00000002,
ORBIS_NET_SOCKINFO_F_OTHERS = 0x00000004,
ORBIS_NET_SOCKINFO_F_RECV_WAIT = 0x00010000,
ORBIS_NET_SOCKINFO_F_SEND_WAIT = 0x00020000,
ORBIS_NET_SOCKINFO_F_RECV_EWAIT = 0x00040000,
ORBIS_NET_SOCKINFO_F_SEND_EWAIT = 0x00080000,
ORBIS_NET_SOCKINFO_F_NONBLOCK = 0x00200000,
ORBIS_NET_SOCKINFO_F_ALL = 0x006F0007,
};
constexpr int ORBIS_NET_SOCKINFO_TIME_WAIT = 0x00000040;
struct OrbisNetSockInfo {
char name[ORBIS_NET_DEBUG_NAME_LEN_MAX + 1];
s32 pid;
OrbisNetId s;
s8 socket_type;
s8 policy;
s8 priority;
s8 reserved8;
s32 recv_queue_length;
s32 send_queue_length;
OrbisNetInAddr local_adr;
OrbisNetInAddr remote_adr;
u16 local_port;
u16 remote_port;
u16 local_vport;
u16 remote_vport;
s32 state;
s32 flags;
s32 tx_bps;
s32 rx_bps;
s32 max_tx_bps;
s32 max_rx_bps;
s32 tx_vbps;
s32 rx_vbps;
s32 recv_buffer_size;
s32 send_buffer_size;
s32 reserved6[8];
s32 tx_drops;
s32 rx_drops;
s32 tx_wait;
s32 reserved[2];
};
int PS4_SYSV_ABI in6addr_any();
int PS4_SYSV_ABI in6addr_loopback();
int PS4_SYSV_ABI sce_net_dummy();
int PS4_SYSV_ABI sce_net_in6addr_any();
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allnodes();
int PS4_SYSV_ABI sce_net_in6addr_linklocal_allrouters();
int PS4_SYSV_ABI sce_net_in6addr_loopback();
int PS4_SYSV_ABI sce_net_in6addr_nodelocal_allnodes();
OrbisNetId PS4_SYSV_ABI sceNetAccept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sceNetAddrConfig6GetInfo();
int PS4_SYSV_ABI sceNetAddrConfig6Start();
int PS4_SYSV_ABI sceNetAddrConfig6Stop();
int PS4_SYSV_ABI sceNetAllocateAllRouteInfo();
int PS4_SYSV_ABI sceNetBandwidthControlGetDataTraffic();
int PS4_SYSV_ABI sceNetBandwidthControlGetDefaultParam();
int PS4_SYSV_ABI sceNetBandwidthControlGetIfParam();
int PS4_SYSV_ABI sceNetBandwidthControlGetPolicy();
int PS4_SYSV_ABI sceNetBandwidthControlSetDefaultParam();
int PS4_SYSV_ABI sceNetBandwidthControlSetIfParam();
int PS4_SYSV_ABI sceNetBandwidthControlSetPolicy();
int PS4_SYSV_ABI sceNetBind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sceNetClearDnsCache();
int PS4_SYSV_ABI sceNetConfigAddArp();
int PS4_SYSV_ABI sceNetConfigAddArpWithInterface();
int PS4_SYSV_ABI sceNetConfigAddIfaddr();
int PS4_SYSV_ABI sceNetConfigAddMRoute();
int PS4_SYSV_ABI sceNetConfigAddRoute();
int PS4_SYSV_ABI sceNetConfigAddRoute6();
int PS4_SYSV_ABI sceNetConfigAddRouteWithInterface();
int PS4_SYSV_ABI sceNetConfigCleanUpAllInterfaces();
int PS4_SYSV_ABI sceNetConfigDelArp();
int PS4_SYSV_ABI sceNetConfigDelArpWithInterface();
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute();
int PS4_SYSV_ABI sceNetConfigDelDefaultRoute6();
int PS4_SYSV_ABI sceNetConfigDelIfaddr();
int PS4_SYSV_ABI sceNetConfigDelIfaddr6();
int PS4_SYSV_ABI sceNetConfigDelMRoute();
int PS4_SYSV_ABI sceNetConfigDelRoute();
int PS4_SYSV_ABI sceNetConfigDelRoute6();
int PS4_SYSV_ABI sceNetConfigDownInterface();
int PS4_SYSV_ABI sceNetConfigEtherGetLinkMode();
int PS4_SYSV_ABI sceNetConfigEtherPostPlugInOutEvent();
int PS4_SYSV_ABI sceNetConfigEtherSetLinkMode();
int PS4_SYSV_ABI sceNetConfigFlushRoute();
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute();
int PS4_SYSV_ABI sceNetConfigGetDefaultRoute6();
int PS4_SYSV_ABI sceNetConfigGetIfaddr();
int PS4_SYSV_ABI sceNetConfigGetIfaddr6();
int PS4_SYSV_ABI sceNetConfigRoutingShowRoutingConfig();
int PS4_SYSV_ABI sceNetConfigRoutingShowtCtlVar();
int PS4_SYSV_ABI sceNetConfigRoutingStart();
int PS4_SYSV_ABI sceNetConfigRoutingStop();
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute();
int PS4_SYSV_ABI sceNetConfigSetDefaultRoute6();
int PS4_SYSV_ABI sceNetConfigSetDefaultScope();
int PS4_SYSV_ABI sceNetConfigSetIfaddr();
int PS4_SYSV_ABI sceNetConfigSetIfaddr6();
int PS4_SYSV_ABI sceNetConfigSetIfaddr6WithFlags();
int PS4_SYSV_ABI sceNetConfigSetIfFlags();
int PS4_SYSV_ABI sceNetConfigSetIfLinkLocalAddr6();
int PS4_SYSV_ABI sceNetConfigSetIfmtu();
int PS4_SYSV_ABI sceNetConfigUnsetIfFlags();
int PS4_SYSV_ABI sceNetConfigUpInterface();
int PS4_SYSV_ABI sceNetConfigUpInterfaceWithFlags();
int PS4_SYSV_ABI sceNetConfigWlanAdhocClearWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanAdhocCreate();
int PS4_SYSV_ABI sceNetConfigWlanAdhocGetWakeOnWlanInfo();
int PS4_SYSV_ABI sceNetConfigWlanAdhocJoin();
int PS4_SYSV_ABI sceNetConfigWlanAdhocLeave();
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuClearWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuGetWakeOnWlanInfo();
int PS4_SYSV_ABI sceNetConfigWlanAdhocPspEmuSetWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanAdhocScanJoin();
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetExtInfoElement();
int PS4_SYSV_ABI sceNetConfigWlanAdhocSetWakeOnWlan();
int PS4_SYSV_ABI sceNetConfigWlanApStart();
int PS4_SYSV_ABI sceNetConfigWlanApStop();
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanQuery();
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStart();
int PS4_SYSV_ABI sceNetConfigWlanBackgroundScanStop();
int PS4_SYSV_ABI sceNetConfigWlanDiagGetDeviceInfo();
int PS4_SYSV_ABI sceNetConfigWlanDiagSetAntenna();
int PS4_SYSV_ABI sceNetConfigWlanDiagSetTxFixedRate();
int PS4_SYSV_ABI sceNetConfigWlanGetDeviceConfig();
int PS4_SYSV_ABI sceNetConfigWlanInfraGetRssiInfo();
int PS4_SYSV_ABI sceNetConfigWlanInfraLeave();
int PS4_SYSV_ABI sceNetConfigWlanInfraScanJoin();
int PS4_SYSV_ABI sceNetConfigWlanScan();
int PS4_SYSV_ABI sceNetConfigWlanSetDeviceConfig();
int PS4_SYSV_ABI sceNetConnect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sceNetControl();
int PS4_SYSV_ABI sceNetDhcpdStart();
int PS4_SYSV_ABI sceNetDhcpdStop();
int PS4_SYSV_ABI sceNetDhcpGetAutoipInfo();
int PS4_SYSV_ABI sceNetDhcpGetInfo();
int PS4_SYSV_ABI sceNetDhcpGetInfoEx();
int PS4_SYSV_ABI sceNetDhcpStart();
int PS4_SYSV_ABI sceNetDhcpStop();
int PS4_SYSV_ABI sceNetDumpAbort();
int PS4_SYSV_ABI sceNetDumpCreate();
int PS4_SYSV_ABI sceNetDumpDestroy();
int PS4_SYSV_ABI sceNetDumpRead();
int PS4_SYSV_ABI sceNetDuplicateIpStart();
int PS4_SYSV_ABI sceNetDuplicateIpStop();
int PS4_SYSV_ABI sceNetEpollAbort();
int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, OrbisNetId id,
OrbisNetEpollEvent* event);
int PS4_SYSV_ABI sceNetEpollCreate(const char* name, int flags);
int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid);
int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents,
int timeout);
int* PS4_SYSV_ABI sceNetErrnoLoc();
int PS4_SYSV_ABI sceNetEtherNtostr();
int PS4_SYSV_ABI sceNetEtherStrton();
int PS4_SYSV_ABI sceNetEventCallbackCreate();
int PS4_SYSV_ABI sceNetEventCallbackDestroy();
int PS4_SYSV_ABI sceNetEventCallbackGetError();
int PS4_SYSV_ABI sceNetEventCallbackWaitCB();
int PS4_SYSV_ABI sceNetFreeAllRouteInfo();
int PS4_SYSV_ABI sceNetGetArpInfo();
int PS4_SYSV_ABI sceNetGetDns6Info();
int PS4_SYSV_ABI sceNetGetDnsInfo();
int PS4_SYSV_ABI sceNetGetIfList();
int PS4_SYSV_ABI sceNetGetIfListOnce();
int PS4_SYSV_ABI sceNetGetIfName();
int PS4_SYSV_ABI sceNetGetIfnameNumList();
int PS4_SYSV_ABI sceNetGetMacAddress(Libraries::NetCtl::OrbisNetEtherAddr* addr, int flags);
int PS4_SYSV_ABI sceNetGetMemoryPoolStats();
int PS4_SYSV_ABI sceNetGetNameToIndex();
int PS4_SYSV_ABI sceNetGetpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sceNetGetRandom();
int PS4_SYSV_ABI sceNetGetRouteInfo();
int PS4_SYSV_ABI sceNetGetSockInfo(OrbisNetId s, OrbisNetSockInfo* info, int n, int flags);
int PS4_SYSV_ABI sceNetGetSockInfo6();
int PS4_SYSV_ABI sceNetGetsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen);
int PS4_SYSV_ABI sceNetGetsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen);
int PS4_SYSV_ABI sceNetGetStatisticsInfo();
int PS4_SYSV_ABI sceNetGetStatisticsInfoInternal();
int PS4_SYSV_ABI sceNetGetSystemTime();
u32 PS4_SYSV_ABI sceNetHtonl(u32 host32);
u64 PS4_SYSV_ABI sceNetHtonll(u64 host64);
u16 PS4_SYSV_ABI sceNetHtons(u16 host16);
const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size);
int PS4_SYSV_ABI sceNetInetNtopWithScopeId();
int PS4_SYSV_ABI sceNetInetPton(int af, const char* src, void* dst);
int PS4_SYSV_ABI sceNetInetPtonEx(int af, const char* src, void* dst, int flags);
int PS4_SYSV_ABI sceNetInetPtonWithScopeId();
int PS4_SYSV_ABI sceNetInfoDumpStart();
int PS4_SYSV_ABI sceNetInfoDumpStop();
int PS4_SYSV_ABI sceNetInit();
int PS4_SYSV_ABI sceNetInitParam();
int PS4_SYSV_ABI sceNetIoctl();
int PS4_SYSV_ABI sceNetListen(OrbisNetId s, int backlog);
int PS4_SYSV_ABI sceNetMemoryAllocate();
int PS4_SYSV_ABI sceNetMemoryFree();
u32 PS4_SYSV_ABI sceNetNtohl(u32 net32);
int PS4_SYSV_ABI sceNetNtohll();
u16 PS4_SYSV_ABI sceNetNtohs(u16 net16);
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags);
int PS4_SYSV_ABI sceNetPoolDestroy();
int PS4_SYSV_ABI sceNetPppoeStart();
int PS4_SYSV_ABI sceNetPppoeStop();
int PS4_SYSV_ABI sceNetRecv(OrbisNetId s, void* buf, u64 len, int flags);
int PS4_SYSV_ABI sceNetRecvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
u32* paddrlen);
int PS4_SYSV_ABI sceNetRecvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags);
int PS4_SYSV_ABI sceNetResolverAbort();
int PS4_SYSV_ABI sceNetResolverConnect();
int PS4_SYSV_ABI sceNetResolverConnectAbort();
int PS4_SYSV_ABI sceNetResolverConnectCreate();
int PS4_SYSV_ABI sceNetResolverConnectDestroy();
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags);
int PS4_SYSV_ABI sceNetResolverDestroy(OrbisNetId resolverid);
int PS4_SYSV_ABI sceNetResolverGetError(OrbisNetId resolverid, s32* status);
int PS4_SYSV_ABI sceNetResolverStartAton();
int PS4_SYSV_ABI sceNetResolverStartAton6();
int PS4_SYSV_ABI sceNetResolverStartNtoa(OrbisNetId resolverid, const char* hostname,
OrbisNetInAddr* addr, int timeout, int retry, int flags);
int PS4_SYSV_ABI sceNetResolverStartNtoa6();
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecords(OrbisNetId resolverid, const char* hostname,
OrbisNetResolverInfo* info, int timeout,
int retry, int flags);
int PS4_SYSV_ABI sceNetResolverStartNtoaMultipleRecordsEx();
int PS4_SYSV_ABI sceNetSend(OrbisNetId s, const void* buf, u64 len, int flags);
int PS4_SYSV_ABI sceNetSendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags);
int PS4_SYSV_ABI sceNetSendto(OrbisNetId s, const void* buf, u64 len, int flags,
const OrbisNetSockaddr* addr, u32 addrlen);
int PS4_SYSV_ABI sceNetSetDns6Info();
int PS4_SYSV_ABI sceNetSetDns6InfoToKernel();
int PS4_SYSV_ABI sceNetSetDnsInfo();
int PS4_SYSV_ABI sceNetSetDnsInfoToKernel();
int PS4_SYSV_ABI sceNetSetsockopt(OrbisNetId s, int level, int optname, const void* optval,
u32 optlen);
int PS4_SYSV_ABI sceNetShowIfconfig();
int PS4_SYSV_ABI sceNetShowIfconfigForBuffer();
int PS4_SYSV_ABI sceNetShowIfconfigWithMemory();
int PS4_SYSV_ABI sceNetShowNetstat();
int PS4_SYSV_ABI sceNetShowNetstatEx();
int PS4_SYSV_ABI sceNetShowNetstatExForBuffer();
int PS4_SYSV_ABI sceNetShowNetstatForBuffer();
int PS4_SYSV_ABI sceNetShowNetstatWithMemory();
int PS4_SYSV_ABI sceNetShowPolicy();
int PS4_SYSV_ABI sceNetShowPolicyWithMemory();
int PS4_SYSV_ABI sceNetShowRoute();
int PS4_SYSV_ABI sceNetShowRoute6();
int PS4_SYSV_ABI sceNetShowRoute6ForBuffer();
int PS4_SYSV_ABI sceNetShowRoute6WithMemory();
int PS4_SYSV_ABI sceNetShowRouteForBuffer();
int PS4_SYSV_ABI sceNetShowRouteWithMemory();
int PS4_SYSV_ABI sceNetShutdown(OrbisNetId s, int how);
OrbisNetId PS4_SYSV_ABI sceNetSocket(const char* name, int family, int type, int protocol);
int PS4_SYSV_ABI sceNetSocketAbort(OrbisNetId s, int flags);
int PS4_SYSV_ABI sceNetSocketClose(OrbisNetId s);
int PS4_SYSV_ABI sceNetSyncCreate();
int PS4_SYSV_ABI sceNetSyncDestroy();
int PS4_SYSV_ABI sceNetSyncGet();
int PS4_SYSV_ABI sceNetSyncSignal();
int PS4_SYSV_ABI sceNetSyncWait();
int PS4_SYSV_ABI sceNetSysctl();
int PS4_SYSV_ABI sceNetTerm();
int PS4_SYSV_ABI sceNetThreadCreate();
int PS4_SYSV_ABI sceNetThreadExit();
int PS4_SYSV_ABI sceNetThreadJoin();
int PS4_SYSV_ABI sceNetUsleep();
int PS4_SYSV_ABI Func_0E707A589F751C68();
int PS4_SYSV_ABI sceNetEmulationGet();
int PS4_SYSV_ABI sceNetEmulationSet();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Net
-79
View File
@@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <ranges>
#include "common/assert.h"
#include "common/types.h"
#include "net_epoll.h"
namespace Libraries::Net {
u32 ConvertEpollEventsIn(u32 orbis_events) {
u32 ret = 0;
#ifndef __FreeBSD__
if ((orbis_events & ORBIS_NET_EPOLLIN) != 0) {
ret |= EPOLLIN;
}
if ((orbis_events & ORBIS_NET_EPOLLOUT) != 0) {
ret |= EPOLLOUT;
}
#endif
return ret;
}
u32 ConvertEpollEventsOut(u32 epoll_events) {
u32 ret = 0;
#ifndef __FreeBSD__
if ((epoll_events & EPOLLIN) != 0) {
ret |= ORBIS_NET_EPOLLIN;
}
if ((epoll_events & EPOLLOUT) != 0) {
ret |= ORBIS_NET_EPOLLOUT;
}
if ((epoll_events & EPOLLERR) != 0) {
ret |= ORBIS_NET_EPOLLERR;
}
if ((epoll_events & EPOLLHUP) != 0) {
ret |= ORBIS_NET_EPOLLHUP;
}
#endif
return ret;
}
int EpollTable::CreateHandle(const char* name) {
std::scoped_lock lock{m_mutex};
if (auto it = std::ranges::find_if(epolls, [](const Epoll& e) { return e.Destroyed(); });
it != epolls.end()) {
*it = Epoll(name);
const auto ret = std::distance(epolls.begin(), it);
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
return ret;
} else {
epolls.emplace_back(name);
const auto ret = epolls.size() - 1;
LOG_DEBUG(Lib_Net, "epollid = {}", ret);
return ret;
}
}
void EpollTable::DeleteHandle(int d) {
UNREACHABLE();
}
Epoll* EpollTable::GetEpoll(int epollid) {
std::scoped_lock lock{m_mutex};
if (epollid >= epolls.size() || epolls[epollid].Destroyed()) {
return nullptr;
}
return &epolls[epollid];
}
} // namespace Libraries::Net
-86
View File
@@ -1,86 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/net.h"
#include <deque>
#include <mutex>
#include <vector>
#ifdef _WIN32
#include <wepoll.h>
#endif
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
// ADD libepoll-shim if using freebsd!
#include <sys/epoll.h>
#include <unistd.h>
#endif
namespace Libraries::Net {
#ifdef _WIN32
using epoll_handle = HANDLE;
#else
using epoll_handle = int;
#endif
struct Epoll {
std::vector<std::pair<u32 /*netId*/, OrbisNetEpollEvent>> events{};
std::string name;
epoll_handle epoll_fd;
std::deque<u32> async_resolutions{};
explicit Epoll(const char* name_) : name(name_), epoll_fd(epoll_create1(0)) {
#ifdef _WIN32
ASSERT(epoll_fd != nullptr);
#else
ASSERT(epoll_fd != -1);
#endif
if (name_ == nullptr) {
name = "anon";
}
}
bool Destroyed() const noexcept {
return destroyed;
}
void Destroy() noexcept {
events.clear();
#ifdef _WIN32
epoll_close(epoll_fd);
epoll_fd = nullptr;
#else
close(epoll_fd);
epoll_fd = -1;
#endif
name = "";
destroyed = true;
}
private:
bool destroyed{};
};
u32 ConvertEpollEventsIn(u32 orbis_events);
u32 ConvertEpollEventsOut(u32 epoll_events);
class EpollTable {
public:
EpollTable() = default;
virtual ~EpollTable() = default;
int CreateHandle(const char* name);
void DeleteHandle(int d);
Epoll* GetEpoll(int d);
private:
std::vector<Epoll> epolls;
std::mutex m_mutex;
};
} // namespace Libraries::Net
@@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "net_error.h"
#include "net_resolver.h"
#include "net_util.h"
namespace Libraries::Net {
int Resolver::ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry,
int flags) {
std::scoped_lock lock{m_mutex};
if (async_resolution) {
*sceNetErrnoLoc() = ORBIS_NET_RESOLVER_EBUSY;
return ORBIS_NET_ERROR_RESOLVER_EBUSY;
}
async_resolution = AsyncResolution{hostname, addr, timeout, retry, flags};
return ORBIS_OK;
}
void Resolver::Resolve() {
if (!EmulatorSettings.IsConnectedToNetwork()) {
resolution_error = ORBIS_NET_ERROR_RESOLVER_ENODNS;
return;
}
if (async_resolution) {
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
auto ret = netinfo->ResolveHostname(async_resolution->hostname, async_resolution->addr);
resolution_error = ret;
if (ret != ORBIS_OK) {
// Resolver errors are stored as ORBIS_NET_ERROR values.
resolution_error = -ret | ORBIS_NET_ERROR_BASE;
}
} else {
LOG_ERROR(Lib_Net, "async resolution has not been set-up");
}
}
} // namespace Libraries::Net
-39
View File
@@ -1,39 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/network/net.h"
#include <mutex>
#include <vector>
namespace Libraries::Net {
struct Resolver {
public:
Resolver(const char* name, int poolid, int flags) : name(name), poolid(poolid), flags(flags) {}
int ResolveAsync(const char* hostname, OrbisNetInAddr* addr, int timeout, int retry, int flags);
void Resolve();
int resolution_error = ORBIS_OK;
private:
struct AsyncResolution {
const char* hostname;
OrbisNetInAddr* addr;
int timeout;
int retry;
int flags;
};
std::string name;
int poolid;
int flags;
std::optional<AsyncResolution> async_resolution{};
std::mutex m_mutex;
};
} // namespace Libraries::Net
+1 -1
View File
@@ -17,9 +17,9 @@
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/net/net_util.h"
#include "core/libraries/network/net_ctl_codes.h"
#include "core/libraries/network/netctl.h"
#include "net_util.h"
namespace Libraries::NetCtl {
-133
View File
@@ -1,133 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/assert.h>
#include "core/libraries/kernel/kernel.h"
#include "net.h"
#include "net_error.h"
#include "sockets.h"
namespace Libraries::Net {
int P2PSocket::Close() {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::Shutdown(int how) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::Listen(int backlog) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
*Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN;
return -1;
}
int P2PSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
*Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN;
return -1;
}
int P2PSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
*Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN;
return -1;
}
int P2PSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
*Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN;
return -1;
}
SocketPtr P2PSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
*Libraries::Kernel::__Error() = ORBIS_NET_EAGAIN;
return nullptr;
}
int P2PSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::GetPeerName(OrbisNetSockaddr* addr, u32* namelen) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
int P2PSocket::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return 0;
}
u16 GetP2PConfiguredPort() {
return 0;
}
u32 GetP2PAdvertisedAddr() {
return 0;
}
bool EnsureP2PTransport() {
return false;
}
bool P2PTransportIsReady() {
return false;
}
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return -1;
}
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return -1;
}
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return -1;
}
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return -1;
}
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port) {
return -1;
}
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port) {
return -1;
}
} // namespace Libraries::Net
@@ -1,716 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#include <common/assert.h>
#include "common/error.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "net.h"
#ifndef _WIN32
#include <sys/stat.h>
#endif
#include "net_error.h"
#include "sockets.h"
namespace Libraries::Net {
#ifdef _WIN32
#define ERROR_CASE(errname) \
case (WSA##errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#else
#define ERROR_CASE(errname) \
case (errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#endif
static int ConvertReturnErrorCode(int retval) {
if (retval < 0) {
#ifdef _WIN32
switch (WSAGetLastError()) {
#else
switch (errno) {
#endif
#ifndef _WIN32 // These errorcodes don't exist in WinSock
ERROR_CASE(EPERM)
ERROR_CASE(ENOENT)
// ERROR_CASE(ESRCH)
// ERROR_CASE(EIO)
// ERROR_CASE(ENXIO)
// ERROR_CASE(E2BIG)
// ERROR_CASE(ENOEXEC)
// ERROR_CASE(EDEADLK)
ERROR_CASE(ENOMEM)
// ERROR_CASE(ECHILD)
// ERROR_CASE(EBUSY)
ERROR_CASE(EEXIST)
// ERROR_CASE(EXDEV)
ERROR_CASE(ENODEV)
// ERROR_CASE(ENOTDIR)
// ERROR_CASE(EISDIR)
ERROR_CASE(ENFILE)
// ERROR_CASE(ENOTTY)
// ERROR_CASE(ETXTBSY)
// ERROR_CASE(EFBIG)
ERROR_CASE(ENOSPC)
// ERROR_CASE(ESPIPE)
// ERROR_CASE(EROFS)
// ERROR_CASE(EMLINK)
ERROR_CASE(EPIPE)
// ERROR_CASE(EDOM)
// ERROR_CASE(ERANGE)
// ERROR_CASE(ENOLCK)
// ERROR_CASE(ENOSYS)
// ERROR_CASE(EIDRM)
// ERROR_CASE(EOVERFLOW)
// ERROR_CASE(EILSEQ)
// ERROR_CASE(ENOTSUP)
ERROR_CASE(ECANCELED)
// ERROR_CASE(EBADMSG)
ERROR_CASE(ENODATA)
// ERROR_CASE(ENOSR)
// ERROR_CASE(ENOSTR)
// ERROR_CASE(ETIME)
#endif
ERROR_CASE(EINTR)
ERROR_CASE(EBADF)
ERROR_CASE(EACCES)
ERROR_CASE(EFAULT)
ERROR_CASE(EINVAL)
ERROR_CASE(EMFILE)
ERROR_CASE(EWOULDBLOCK)
ERROR_CASE(EINPROGRESS)
ERROR_CASE(EALREADY)
ERROR_CASE(ENOTSOCK)
ERROR_CASE(EDESTADDRREQ)
ERROR_CASE(EMSGSIZE)
ERROR_CASE(EPROTOTYPE)
ERROR_CASE(ENOPROTOOPT)
ERROR_CASE(EPROTONOSUPPORT)
#if defined(__APPLE__) || defined(_WIN32)
ERROR_CASE(EOPNOTSUPP)
#endif
ERROR_CASE(EAFNOSUPPORT)
ERROR_CASE(EADDRINUSE)
ERROR_CASE(EADDRNOTAVAIL)
ERROR_CASE(ENETDOWN)
ERROR_CASE(ENETUNREACH)
ERROR_CASE(ENETRESET)
ERROR_CASE(ECONNABORTED)
ERROR_CASE(ECONNRESET)
ERROR_CASE(ENOBUFS)
ERROR_CASE(EISCONN)
ERROR_CASE(ENOTCONN)
ERROR_CASE(ETIMEDOUT)
ERROR_CASE(ECONNREFUSED)
ERROR_CASE(ELOOP)
ERROR_CASE(ENAMETOOLONG)
ERROR_CASE(EHOSTUNREACH)
ERROR_CASE(ENOTEMPTY)
}
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
return -1;
}
// if it is 0 or positive return it as it is
return retval;
}
static int ConvertLevels(int level) {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return SOL_SOCKET;
case ORBIS_NET_IPPROTO_IP:
return IPPROTO_IP;
case ORBIS_NET_IPPROTO_TCP:
return IPPROTO_TCP;
case ORBIS_NET_IPPROTO_IPV6:
return IPPROTO_IPV6;
}
return -1;
}
static void convertOrbisNetSockaddrToPosix(const OrbisNetSockaddr* src, sockaddr* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(sockaddr));
const OrbisNetSockaddrIn* src_in = (const OrbisNetSockaddrIn*)src;
sockaddr_in* dst_in = (sockaddr_in*)dst;
dst_in->sin_family = src_in->sin_family;
dst_in->sin_port = src_in->sin_port;
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
}
static void convertPosixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(OrbisNetSockaddr));
OrbisNetSockaddrIn* dst_in = (OrbisNetSockaddrIn*)dst;
sockaddr_in* src_in = (sockaddr_in*)src;
dst_in->sin_family = static_cast<unsigned char>(src_in->sin_family);
dst_in->sin_port = src_in->sin_port;
memcpy(&dst_in->sin_addr, &src_in->sin_addr, 4);
}
bool PosixSocket::IsValid() const {
#ifdef _WIN32
return sock != INVALID_SOCKET;
#else
return sock != -1;
#endif
}
int PosixSocket::Close() {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
auto out = closesocket(sock);
#else
auto out = ::close(sock);
#endif
return ConvertReturnErrorCode(out);
}
int PosixSocket::Shutdown(int how) {
return ConvertReturnErrorCode(::shutdown(sock, how));
}
int PosixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr addr2;
convertOrbisNetSockaddrToPosix(addr, &addr2);
return ConvertReturnErrorCode(::bind(sock, &addr2, sizeof(sockaddr_in)));
}
int PosixSocket::Listen(int backlog) {
std::scoped_lock lock{m_mutex};
return ConvertReturnErrorCode(::listen(sock, backlog));
}
static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
int posix_flags = 0;
if (sce_flags & ORBIS_NET_MSG_PEEK)
posix_flags |= MSG_PEEK;
#ifndef _WIN32
if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
posix_flags |= MSG_DONTWAIT;
#endif
// MSG_WAITALL is only valid for stream sockets
if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
posix_flags |= MSG_WAITALL;
return posix_flags;
}
// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
// This function uses select() with zero timeout to simulate non-blocking behavior.
static int socket_is_ready(int sock, bool is_read = true) {
fd_set fds{};
FD_ZERO(&fds);
FD_SET(sock, &fds);
timeval timeout{0, 0};
int res =
select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
if (res == 0) {
*Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK;
return -1;
} else if (res < 0) {
return ConvertReturnErrorCode(res);
}
return res;
}
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
int totalSent = 0;
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
// stream socket with multiple buffers
bool use_wsamsg =
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
msg->msg_iovlen > 1;
for (int i = 0; i < msg->msg_iovlen; ++i) {
char* buf = (char*)msg->msg_iov[i].iov_base;
size_t remaining = msg->msg_iov[i].iov_len;
while (remaining > 0) {
if (dontWait) {
int ready = socket_is_ready(sock, false);
if (ready <= 0)
return ready;
}
int sent = 0;
if (use_wsamsg) {
// only call WSASendMsg if we have multiple buffers
LPFN_WSASENDMSG wsasendmsg = nullptr;
GUID guid = WSAID_WSASENDMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
&wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
// fallback to send()
sent = ::send(sock, buf, remaining, 0);
} else {
DWORD bytesSent = 0;
int res = wsasendmsg(
sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), 0,
&bytesSent, nullptr, nullptr);
if (res == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
sent = bytesSent;
}
} else {
sent = ::send(sock, buf, remaining, 0);
if (sent == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
}
totalSent += sent;
remaining -= sent;
buf += sent;
if (!waitAll)
break;
}
}
return totalSent;
#else
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), native_flags);
return ConvertReturnErrorCode(res);
#endif
}
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
std::scoped_lock lock{m_mutex};
int res = 0;
#ifdef _WIN32
if (flags & ORBIS_NET_MSG_DONTWAIT) {
res = socket_is_ready(sock, false);
if (res <= 0)
return res;
}
#endif
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
if (to == nullptr) {
res = send(sock, (const char*)msg, len, posix_flags);
} else {
sockaddr addr{};
convertOrbisNetSockaddrToPosix(to, &addr);
res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
}
return ConvertReturnErrorCode(res);
}
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{receive_mutex};
#ifdef _WIN32
int totalReceived = 0;
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
// stream socket with multiple buffers
bool use_wsarecvmsg =
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
msg->msg_iovlen > 1;
for (int i = 0; i < msg->msg_iovlen; ++i) {
char* buf = (char*)msg->msg_iov[i].iov_base;
size_t remaining = msg->msg_iov[i].iov_len;
while (remaining > 0) {
// emulate DONTWAIT
if (dontWait) {
int ready = socket_is_ready(sock, true);
if (ready <= 0)
return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
}
int received = 0;
if (use_wsarecvmsg) {
// only call WSARecvMsg if multiple buffers + stream
LPFN_WSARECVMSG wsarecvmsg = nullptr;
GUID guid = WSAID_WSARECVMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
&wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
// fallback to recv()
received = ::recv(sock, buf, remaining, 0);
if (received == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
} else {
DWORD bytesReceived = 0;
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived,
nullptr, nullptr);
if (res == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
received = bytesReceived;
}
} else {
// fallback to recv() for UDP or single-buffer
received = ::recv(sock, buf, remaining, 0);
if (received == SOCKET_ERROR)
return ConvertReturnErrorCode(WSAGetLastError());
}
totalReceived += received;
remaining -= received;
buf += received;
// stop after first receive if WAITALL is not set
if (!waitAll)
break;
}
}
return totalReceived;
#else
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), native_flags);
return ConvertReturnErrorCode(res);
#endif
}
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
u32* fromlen) {
std::scoped_lock lock{receive_mutex};
int res = 0;
#ifdef _WIN32
if (flags & ORBIS_NET_MSG_DONTWAIT) {
res = socket_is_ready(sock);
if (res <= 0)
return res;
}
#endif
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
if (from == nullptr) {
res = recv(sock, (char*)buf, len, posix_flags);
} else {
sockaddr addr{};
socklen_t addrlen = sizeof(addr);
res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
(fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
if (res > 0)
convertPosixSockaddrToOrbis(&addr, from);
}
return ConvertReturnErrorCode(res);
}
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr addr2;
socklen_t len = sizeof(addr2);
net_socket new_socket = ::accept(sock, &addr2, &len);
#ifdef _WIN32
if (new_socket != INVALID_SOCKET) {
#else
if (new_socket >= 0) {
#endif
if (addr && addrlen) {
convertPosixSockaddrToOrbis(&addr2, addr);
*addrlen = sizeof(OrbisNetSockaddrIn);
}
return std::make_shared<PosixSocket>(new_socket);
}
ConvertReturnErrorCode(new_socket);
return nullptr;
}
int PosixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
std::scoped_lock lock{m_mutex};
sockaddr addr2;
convertOrbisNetSockaddrToPosix(addr, &addr2);
int result = ::connect(sock, &addr2, sizeof(sockaddr_in));
#ifdef _WIN32
// Winsock returns EWOULDBLOCK where real hardware returns EINPROGRESS
// Step in here on errors to address this.
if (result == -1) {
if (WSAGetLastError() == WSAEWOULDBLOCK) {
WSASetLastError(WSAEINPROGRESS);
}
}
#endif
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
result == -1 ? Common::GetLastErrorMsg() : "none");
return ConvertReturnErrorCode(result);
}
int PosixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
sockaddr addr;
convertOrbisNetSockaddrToPosix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_in);
}
int res = getsockname(sock, &addr, (socklen_t*)namelen);
if (res >= 0) {
convertPosixSockaddrToOrbis(&addr, name);
*namelen = sizeof(OrbisNetSockaddrIn);
}
return ConvertReturnErrorCode(res);
}
#define CASE_SETSOCKOPT(opt) \
case ORBIS_NET_##opt: \
return ConvertReturnErrorCode( \
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
#define CASE_SETSOCKOPT_VALUE(opt, value) \
case opt: \
if (optlen != sizeof(*value)) { \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
memcpy(value, optval, optlen); \
return 0
int PosixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
std::scoped_lock lock{m_mutex};
s32 native_level = ConvertLevels(level);
::linger native_linger;
if (native_level == SOL_SOCKET) {
switch (optname) {
CASE_SETSOCKOPT(SO_REUSEADDR);
CASE_SETSOCKOPT(SO_KEEPALIVE);
CASE_SETSOCKOPT(SO_BROADCAST);
CASE_SETSOCKOPT(SO_SNDBUF);
CASE_SETSOCKOPT(SO_RCVBUF);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, &sockopt_so_connecttimeo);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, &sockopt_so_reuseport);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, &sockopt_so_usecrypto);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, &sockopt_so_usesignature);
case ORBIS_NET_SO_SNDTIMEO:
case ORBIS_NET_SO_RCVTIMEO: {
if (optlen != sizeof(int)) {
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
return -1;
}
std::vector<char> val;
const auto optname_nat = (optname == ORBIS_NET_SO_SNDTIMEO) ? SO_SNDTIMEO : SO_RCVTIMEO;
int timeout_us = *(const int*)optval;
#ifdef _WIN32
DWORD timeout = timeout_us / 1000;
#else
timeval timeout{.tv_sec = timeout_us / 1000000, .tv_usec = timeout_us % 1000000};
#endif
val.insert(val.end(), (char*)&timeout, (char*)&timeout + sizeof(timeout));
optlen = sizeof(timeout);
return ConvertReturnErrorCode(
setsockopt(sock, native_level, optname_nat, val.data(), optlen));
}
case ORBIS_NET_SO_ONESBCAST: {
if (optlen != sizeof(sockopt_so_onesbcast)) {
*Libraries::Kernel::__Error() = ORBIS_NET_ERROR_EFAULT;
return -1;
}
memcpy(&sockopt_so_onesbcast, optval, optlen);
return ConvertReturnErrorCode(
setsockopt(sock, native_level, SO_BROADCAST, (const char*)optval, optlen));
}
case ORBIS_NET_SO_TYPE:
case ORBIS_NET_SO_ERROR: {
*Libraries::Kernel::__Error() = ORBIS_NET_ENOPROTOOPT;
return -1;
}
case ORBIS_NET_SO_LINGER: {
if (socket_type != ORBIS_NET_SOCK_STREAM) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
return -1;
}
if (optlen < sizeof(OrbisNetLinger)) {
LOG_ERROR(Lib_Net, "size missmatched! optlen = {} OrbisNetLinger={}", optlen,
sizeof(OrbisNetLinger));
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1;
}
const void* native_val = &native_linger;
u32 native_len = sizeof(native_linger);
native_linger.l_onoff = reinterpret_cast<const OrbisNetLinger*>(optval)->l_onoff;
native_linger.l_linger = reinterpret_cast<const OrbisNetLinger*>(optval)->l_linger;
return ConvertReturnErrorCode(
setsockopt(sock, native_level, SO_LINGER, (const char*)native_val, native_len));
}
case ORBIS_NET_SO_NAME:
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1; // don't support set for name
case ORBIS_NET_SO_NBIO: {
if (optlen < sizeof(sockopt_so_nbio)) {
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1;
} else {
memcpy(&sockopt_so_nbio, optval, sizeof(sockopt_so_nbio));
}
#ifdef _WIN32
static_assert(sizeof(u_long) == sizeof(sockopt_so_nbio),
"type used for ioctlsocket value does not have the expected size");
return ConvertReturnErrorCode(ioctlsocket(sock, FIONBIO, (u_long*)&sockopt_so_nbio));
#else
return ConvertReturnErrorCode(ioctl(sock, FIONBIO, &sockopt_so_nbio));
#endif
}
case ORBIS_NET_SO_ACCEPTTIMEO:
LOG_ERROR(Lib_Net, "Unhandled option ORBIS_NET_SO_ACCEPTTIMEO");
return ORBIS_OK;
}
} else if (native_level == IPPROTO_IP) {
switch (optname) {
// CASE_SETSOCKOPT(IP_HDRINCL);
CASE_SETSOCKOPT(IP_TOS);
CASE_SETSOCKOPT(IP_TTL);
CASE_SETSOCKOPT(IP_MULTICAST_IF);
CASE_SETSOCKOPT(IP_MULTICAST_TTL);
CASE_SETSOCKOPT(IP_MULTICAST_LOOP);
CASE_SETSOCKOPT(IP_ADD_MEMBERSHIP);
CASE_SETSOCKOPT(IP_DROP_MEMBERSHIP);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, &sockopt_ip_ttlchk);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, &sockopt_ip_maxttl);
case ORBIS_NET_IP_HDRINCL: {
if (socket_type != ORBIS_NET_SOCK_RAW) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROCUNAVAIL;
return -1;
}
return ConvertReturnErrorCode(
setsockopt(sock, native_level, optname, (const char*)optval, optlen));
}
}
} else if (native_level == IPPROTO_TCP) {
switch (optname) {
CASE_SETSOCKOPT(TCP_NODELAY);
CASE_SETSOCKOPT(TCP_MAXSEG);
CASE_SETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, &sockopt_tcp_mss_to_advertise);
}
}
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
return 0;
}
#define CASE_GETSOCKOPT(opt) \
case ORBIS_NET_##opt: { \
socklen_t optlen_temp = *optlen; \
auto retval = ConvertReturnErrorCode( \
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
*optlen = optlen_temp; \
return retval; \
}
#define CASE_GETSOCKOPT_VALUE(opt, value) \
case opt: \
if (*optlen < sizeof(value)) { \
*optlen = sizeof(value); \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
*optlen = sizeof(value); \
*(decltype(value)*)optval = value; \
return 0;
int PosixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
std::scoped_lock lock{m_mutex};
s32 native_level = ConvertLevels(level);
if (native_level == SOL_SOCKET) {
switch (optname) {
CASE_GETSOCKOPT(SO_REUSEADDR);
CASE_GETSOCKOPT(SO_KEEPALIVE);
CASE_GETSOCKOPT(SO_BROADCAST);
CASE_GETSOCKOPT(SO_LINGER);
CASE_GETSOCKOPT(SO_SNDBUF);
CASE_GETSOCKOPT(SO_RCVBUF);
CASE_GETSOCKOPT(SO_SNDTIMEO);
CASE_GETSOCKOPT(SO_RCVTIMEO);
CASE_GETSOCKOPT(SO_ERROR);
CASE_GETSOCKOPT(SO_TYPE);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NBIO, sockopt_so_nbio);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_CONNECTTIMEO, sockopt_so_connecttimeo);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_REUSEPORT, sockopt_so_reuseport);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_ONESBCAST, sockopt_so_onesbcast);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USECRYPTO, sockopt_so_usecrypto);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_USESIGNATURE, sockopt_so_usesignature);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_SO_NAME,
(char)0); // writes an empty string to the output buffer
case ORBIS_NET_SO_ERROR_EX: {
socklen_t optlen_temp = *optlen;
auto retval = ConvertReturnErrorCode(
getsockopt(sock, level, SO_ERROR, (char*)optval, &optlen_temp));
*optlen = optlen_temp;
if (retval < 0) {
s32 r = *Libraries::Kernel::__Error();
*Libraries::Kernel::__Error() = 0;
return r;
}
return retval;
}
}
} else if (native_level == IPPROTO_IP) {
switch (optname) {
CASE_GETSOCKOPT(IP_HDRINCL);
CASE_GETSOCKOPT(IP_TOS);
CASE_GETSOCKOPT(IP_TTL);
CASE_GETSOCKOPT(IP_MULTICAST_IF);
CASE_GETSOCKOPT(IP_MULTICAST_TTL);
CASE_GETSOCKOPT(IP_MULTICAST_LOOP);
CASE_GETSOCKOPT(IP_ADD_MEMBERSHIP);
CASE_GETSOCKOPT(IP_DROP_MEMBERSHIP);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_TTLCHK, sockopt_ip_ttlchk);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_IP_MAXTTL, sockopt_ip_maxttl);
}
} else if (native_level == IPPROTO_TCP) {
switch (optname) {
CASE_GETSOCKOPT(TCP_NODELAY);
CASE_GETSOCKOPT(TCP_MAXSEG);
CASE_GETSOCKOPT_VALUE(ORBIS_NET_TCP_MSS_TO_ADVERTISE, sockopt_tcp_mss_to_advertise);
}
}
UNREACHABLE_MSG("Unknown level ={} optname ={}", level, optname);
return 0;
}
int PosixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
sockaddr addr;
convertOrbisNetSockaddrToPosix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_in);
}
int res = ::getpeername(sock, &addr, (socklen_t*)namelen);
if (res >= 0) {
convertPosixSockaddrToOrbis(&addr, name);
*namelen = sizeof(OrbisNetSockaddrIn);
}
return ConvertReturnErrorCode(res);
}
int PosixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
#ifdef _WIN32
LOG_ERROR(Lib_Net, "(STUBBED) called");
sb->st_mode = 0000777u | 0140000u;
return 0;
#else
struct stat st{};
int result = ::fstat(sock, &st);
sb->st_mode = 0000777u | 0140000u;
sb->st_size = st.st_size;
sb->st_blocks = st.st_blocks;
sb->st_blksize = st.st_blksize;
// sb->st_flags = st.st_flags;
return ConvertReturnErrorCode(result);
#endif
}
} // namespace Libraries::Net
-197
View File
@@ -1,197 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <Ws2tcpip.h>
#include <afunix.h>
#include <iphlpapi.h>
#include <mstcpip.h>
#include <winsock2.h>
typedef SOCKET net_socket;
typedef int socklen_t;
#ifndef LPFN_WSASENDMSG
typedef INT(PASCAL* LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
LPDWORD lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
#endif
#ifndef WSAID_WSASENDMSG
static const GUID WSAID_WSASENDMSG = {
0xa441e712, 0x754f, 0x43ca, {0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}};
#endif
#ifndef LPFN_WSARECVMSG
typedef INT(PASCAL* LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg, LPDWORD lpdwNumberOfBytesRecvd,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
#endif
#ifndef WSAID_WSARECVMSG
static const GUID WSAID_WSARECVMSG = {
0xf689d7c8, 0x6f1f, 0x436b, {0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}};
#endif
#else
#include <cerrno>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
typedef int net_socket;
#endif
#include <map>
#include <memory>
#include <mutex>
#include "net.h"
namespace Libraries::Kernel {
struct OrbisKernelStat;
}
namespace Libraries::Net {
struct Socket;
typedef std::shared_ptr<Socket> SocketPtr;
struct OrbisNetLinger {
s32 l_onoff;
s32 l_linger;
};
struct Socket {
explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
virtual ~Socket() = default;
virtual bool IsValid() const = 0;
virtual int Close() = 0;
virtual int Shutdown(int how) = 0;
virtual int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) = 0;
virtual int GetSocketOptions(int level, int optname, void* optval, u32* optlen) = 0;
virtual int Bind(const OrbisNetSockaddr* addr, u32 addrlen) = 0;
virtual int Listen(int backlog) = 0;
virtual int SendMessage(const OrbisNetMsghdr* msg, int flags) = 0;
virtual int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) = 0;
virtual SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) = 0;
virtual int ReceiveMessage(OrbisNetMsghdr* msg, int flags) = 0;
virtual int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
u32* fromlen) = 0;
virtual int Connect(const OrbisNetSockaddr* addr, u32 namelen) = 0;
virtual int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) = 0;
virtual int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) = 0;
virtual int fstat(Libraries::Kernel::OrbisKernelStat* stat) = 0;
virtual std::optional<net_socket> Native() = 0;
std::mutex m_mutex;
std::mutex receive_mutex;
int socket_type;
};
struct PosixSocket : public Socket {
net_socket sock;
int sockopt_so_connecttimeo = 0;
int sockopt_so_reuseport = 0;
int sockopt_so_onesbcast = 0;
int sockopt_so_usecrypto = 0;
int sockopt_so_usesignature = 0;
int sockopt_so_nbio = 0;
int sockopt_ip_ttlchk = 0;
int sockopt_ip_maxttl = 0;
int sockopt_tcp_mss_to_advertise = 0;
int socket_type;
explicit PosixSocket(int domain, int type, int protocol)
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
socket_type = type;
}
explicit PosixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
bool IsValid() const override;
int Close() override;
int Shutdown(int how) override;
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
int Listen(int backlog) override;
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) override;
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return sock;
}
};
struct P2PSocket : public Socket {
explicit P2PSocket(int domain, int type, int protocol) : Socket(domain, type, protocol) {}
bool IsValid() const override {
return true;
}
int Close() override;
int Shutdown(int how) override;
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
int Listen(int backlog) override;
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) override;
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return {};
}
};
struct UnixSocket : public Socket {
net_socket sock;
int socket_type;
explicit UnixSocket(int domain, int type, int protocol)
: Socket(domain, type, protocol), sock(socket(domain, type, protocol)) {
socket_type = type;
}
explicit UnixSocket(net_socket sock) : Socket(0, 0, 0), sock(sock) {}
bool IsValid() const override;
int Close() override;
int Shutdown(int how) override;
int SetSocketOptions(int level, int optname, const void* optval, u32 optlen) override;
int GetSocketOptions(int level, int optname, void* optval, u32* optlen) override;
int Bind(const OrbisNetSockaddr* addr, u32 addrlen) override;
int Listen(int backlog) override;
int SendMessage(const OrbisNetMsghdr* msg, int flags) override;
int SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) override;
int ReceiveMessage(OrbisNetMsghdr* msg, int flags) override;
int ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) override;
SocketPtr Accept(OrbisNetSockaddr* addr, u32* addrlen) override;
int Connect(const OrbisNetSockaddr* addr, u32 namelen) override;
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return sock;
}
};
u16 GetP2PConfiguredPort();
u32 GetP2PAdvertisedAddr();
bool EnsureP2PTransport();
bool P2PTransportIsReady();
int P2PSignalingSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PSignalingRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int P2PControlSendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PControlRecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
int P2PMatching2SendTo(const void* data, u32 len, u32 dest_addr, u16 dest_port);
int P2PMatching2RecvFrom(void* buf, u32 len, u32* from_addr, u16* from_port);
} // namespace Libraries::Net
-452
View File
@@ -1,452 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/assert.h>
#include <common/logging/log.h>
#include <core/libraries/kernel/kernel.h>
#include <magic_enum/magic_enum.hpp>
#include "common/error.h"
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "net_error.h"
#include "sockets.h"
#include "sys_net.h"
namespace Libraries::Net {
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
int PS4_SYSV_ABI sys_connect(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Connect(addr, addrlen);
if (returncode >= 0) {
return returncode;
}
u32 error = *Libraries::Kernel::__Error();
// Don't log EINPROGRESS or EISCONN, these are normal to see from non-blocking communication.
if (error != ORBIS_NET_EINPROGRESS && error != ORBIS_NET_EISCONN) {
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
}
return -1;
}
int PS4_SYSV_ABI sys_bind(OrbisNetId s, const OrbisNetSockaddr* addr, u32 addrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Bind(addr, addrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_accept(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
auto new_sock = file->socket->Accept(addr, paddrlen);
if (!new_sock) {
u32 error = *Libraries::Kernel::__Error();
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
if (error != ORBIS_NET_EWOULDBLOCK) {
LOG_ERROR(Lib_Net,
"s = {} ({}) returned error code creating new socket for accepting: {}", s,
file->m_guest_name, error);
}
return -1;
}
auto fd = FDTable::Instance()->CreateHandle();
auto* new_file = FDTable::Instance()->GetFile(fd);
new_file->is_opened = true;
new_file->type = Core::FileSys::FileType::Socket;
new_file->socket = new_sock;
return fd;
}
int PS4_SYSV_ABI sys_getpeername(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->GetPeerName(addr, paddrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_getsockname(OrbisNetId s, OrbisNetSockaddr* addr, u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->GetSocketAddress(addr, paddrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_getsockopt(OrbisNetId s, int level, int optname, void* optval, u32* optlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
auto name = [&] -> std::string_view {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return NameOf((OrbisNetSocketSoOption)optname);
case ORBIS_NET_IPPROTO_IP:
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
case ORBIS_NET_IPPROTO_TCP:
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
default:
return std::string_view{"unknown"};
}
}();
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
NameOf((OrbisNetProtocol)level), name);
int returncode = file->socket->GetSocketOptions(level, optname, optval, optlen);
if (returncode >= 0) {
if (optname == ORBIS_NET_SO_ERROR) {
LOG_DEBUG(Lib_Net, "so_error = {}", *reinterpret_cast<s32*>(optval));
}
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_listen(OrbisNetId s, int backlog) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), backlog = {}", s, file->m_guest_name, backlog);
int returncode = file->socket->Listen(backlog);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_setsockopt(OrbisNetId s, int level, int optname, const void* optval,
u32 optlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
auto name = [&] -> std::string_view {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return NameOf((OrbisNetSocketSoOption)optname);
case ORBIS_NET_IPPROTO_IP:
return magic_enum::enum_name((OrbisNetSocketIpOption)optname);
case ORBIS_NET_IPPROTO_TCP:
return magic_enum::enum_name((OrbisNetSocketTcpOption)optname);
default:
return std::string_view{"unknown"};
}
}();
LOG_DEBUG(Lib_Net, "s = {} ({}), level = {}, optname = {}", s, file->m_guest_name,
NameOf((OrbisNetProtocol)level), name);
int returncode = file->socket->SetSocketOptions(level, optname, optval, optlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_shutdown(OrbisNetId s, int how) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), how = {}", s, file->m_guest_name, how);
int returncode = file->socket->Shutdown(how);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_socketex(const char* name, int family, int type, int protocol) {
auto sname = name ? name : "anon";
LOG_INFO(Lib_Net, "name = {} family = {} type = {} protocol = {}", std::string(sname),
magic_enum::enum_name((OrbisNetFamily)family),
magic_enum::enum_name((OrbisNetSocketType)type), protocol);
SocketPtr socket;
switch (type) {
case ORBIS_NET_SOCK_STREAM:
case ORBIS_NET_SOCK_DGRAM:
if (family == ORBIS_NET_AF_UNIX) {
socket = std::make_shared<UnixSocket>(family, type, protocol);
break;
}
[[fallthrough]];
case ORBIS_NET_SOCK_RAW:
socket = std::make_shared<PosixSocket>(family, type, protocol);
break;
case ORBIS_NET_SOCK_DGRAM_P2P:
case ORBIS_NET_SOCK_STREAM_P2P:
socket = std::make_shared<P2PSocket>(family, type, protocol);
break;
default:
UNREACHABLE_MSG("Unknown type {}", type);
}
if (!socket->IsValid()) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
auto fd = FDTable::Instance()->CreateHandle();
auto* sock = FDTable::Instance()->GetFile(fd);
sock->is_opened = true;
sock->type = Core::FileSys::FileType::Socket;
sock->socket = socket;
sock->m_guest_name = sname;
return fd;
}
int PS4_SYSV_ABI sys_socket(int family, int type, int protocol) {
return sys_socketex(nullptr, family, type, protocol);
}
#ifdef _WIN32
int socketpair(int family, int type, int protocol, net_socket fd[2]) {
if (family != AF_UNIX && family != AF_INET) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
if (type != SOCK_STREAM) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
SOCKET listener = INVALID_SOCKET;
SOCKET sock1 = INVALID_SOCKET;
SOCKET sock2 = INVALID_SOCKET;
sockaddr_in addr{};
int addrlen = sizeof(addr);
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == INVALID_SOCKET)
goto fail1;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
if (bind(listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
goto fail1;
if (listen(listener, 1) == SOCKET_ERROR)
goto fail1;
if (getsockname(listener, (sockaddr*)&addr, &addrlen) == SOCKET_ERROR)
goto fail1;
sock1 = socket(AF_INET, SOCK_STREAM, 0);
if (sock1 == INVALID_SOCKET)
goto fail2;
if (connect(sock1, (sockaddr*)&addr, addrlen) == SOCKET_ERROR)
goto fail2;
sock2 = accept(listener, nullptr, nullptr);
if (sock2 == INVALID_SOCKET)
goto fail3;
closesocket(listener);
fd[0] = sock1;
fd[1] = sock2;
return 0;
fail3:
closesocket(sock2);
fail2:
closesocket(sock1);
fail1:
closesocket(listener);
return -1;
}
#endif
int PS4_SYSV_ABI sys_socketpair(int family, int type, int protocol, int sv[2]) {
if (sv == nullptr) {
*Libraries::Kernel::__Error() = ORBIS_NET_EINVAL;
return -1;
}
if (family != ORBIS_NET_AF_UNIX) {
*Libraries::Kernel::__Error() = ORBIS_NET_EPROTONOSUPPORT;
return -1;
}
net_socket fd[2];
if (socketpair(family, type, protocol, fd) != 0) {
LOG_ERROR(Lib_Net, "socketpair failed with {}", Common::GetLastErrorMsg());
return -1;
}
auto fd1 = FDTable::Instance()->CreateHandle();
auto fd2 = FDTable::Instance()->CreateHandle();
auto* sock = FDTable::Instance()->GetFile(fd1);
sock->is_opened = true;
sock->type = Core::FileSys::FileType::Socket;
sock->socket = std::make_shared<UnixSocket>(fd[0]);
sock->m_guest_name = "anon_sock0";
sock = FDTable::Instance()->GetFile(fd2);
sock->is_opened = true;
sock->type = Core::FileSys::FileType::Socket;
sock->socket = std::make_shared<UnixSocket>(fd[1]);
sock->m_guest_name = "anon_sock1";
sv[0] = fd1;
sv[1] = fd2;
return 0;
}
int PS4_SYSV_ABI sys_netabort(OrbisNetId s, int flags) {
LOG_ERROR(Lib_Net, "(STUBBED) called");
return -1;
}
int PS4_SYSV_ABI sys_socketclose(OrbisNetId s) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({})", s, file->m_guest_name);
int returncode = file->socket->Close();
if (returncode >= 0) {
FDTable::Instance()->DeleteHandle(s);
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_send(OrbisNetId s, const void* buf, u64 len, int flags) {
return sys_sendto(s, buf, len, flags, nullptr, 0);
}
int PS4_SYSV_ABI sys_sendto(OrbisNetId s, const void* buf, u64 len, int flags,
const OrbisNetSockaddr* addr, u32 addrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
int returncode = file->socket->SendPacket(buf, len, flags, addr, addrlen);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_sendmsg(OrbisNetId s, const OrbisNetMsghdr* msg, int flags) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
int returncode = file->socket->SendMessage(msg, flags);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "error code returned: {}", (u32)*Libraries::Kernel::__Error());
return -1;
}
s64 PS4_SYSV_ABI sys_recv(OrbisNetId s, void* buf, u64 len, int flags) {
return sys_recvfrom(s, buf, len, flags, nullptr, nullptr);
}
s64 PS4_SYSV_ABI sys_recvfrom(OrbisNetId s, void* buf, u64 len, int flags, OrbisNetSockaddr* addr,
u32* paddrlen) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), len = {}, flags = {:#x}", s, file->m_guest_name, len, flags);
int returncode = file->socket->ReceivePacket(buf, len, flags, addr, paddrlen);
if (returncode >= 0) {
return returncode;
}
// Don't log EWOULDBLOCK, this is normal to see from non-blocking communication.
u32 error = *Libraries::Kernel::__Error();
if (error != ORBIS_NET_EWOULDBLOCK) {
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name, error);
}
return -1;
}
s64 PS4_SYSV_ABI sys_recvmsg(OrbisNetId s, OrbisNetMsghdr* msg, int flags) {
auto file = FDTable::Instance()->GetSocket(s);
if (!file) {
*Libraries::Kernel::__Error() = ORBIS_NET_EBADF;
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1;
}
LOG_DEBUG(Lib_Net, "s = {} ({}), flags = {:#x}", s, file->m_guest_name, flags);
int returncode = file->socket->ReceiveMessage(msg, flags);
if (returncode >= 0) {
return returncode;
}
LOG_ERROR(Lib_Net, "s = {} ({}) returned error code: {}", s, file->m_guest_name,
(u32)*Libraries::Kernel::__Error());
return -1;
}
int PS4_SYSV_ABI sys_htons(u16 v) {
return ((v << 8) & 0xff00) | ((v >> 8) & 0x00ff);
}
int PS4_SYSV_ABI sys_htonl(u32 v) {
return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24);
}
} // namespace Libraries::Net
-397
View File
@@ -1,397 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/assert.h>
#include "common/error.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "net.h"
#ifndef _WIN32
#include <sys/stat.h>
#include <sys/un.h>
#endif
#include "net_error.h"
#include "sockets.h"
namespace Libraries::Net {
#ifdef _WIN32
#define ERROR_CASE(errname) \
case (WSA##errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#else
#define ERROR_CASE(errname) \
case (errname): \
*Libraries::Kernel::__Error() = ORBIS_NET_##errname; \
return -1;
#endif
static int ConvertReturnErrorCode(int retval) {
if (retval < 0) {
#ifdef _WIN32
switch (WSAGetLastError()) {
#else
switch (errno) {
#endif
#ifndef _WIN32 // These errorcodes don't exist in WinSock
ERROR_CASE(EPERM)
ERROR_CASE(ENOENT)
// ERROR_CASE(ESRCH)
// ERROR_CASE(EIO)
// ERROR_CASE(ENXIO)
// ERROR_CASE(E2BIG)
// ERROR_CASE(ENOEXEC)
// ERROR_CASE(EDEADLK)
ERROR_CASE(ENOMEM)
// ERROR_CASE(ECHILD)
// ERROR_CASE(EBUSY)
ERROR_CASE(EEXIST)
// ERROR_CASE(EXDEV)
ERROR_CASE(ENODEV)
// ERROR_CASE(ENOTDIR)
// ERROR_CASE(EISDIR)
ERROR_CASE(ENFILE)
// ERROR_CASE(ENOTTY)
// ERROR_CASE(ETXTBSY)
// ERROR_CASE(EFBIG)
ERROR_CASE(ENOSPC)
// ERROR_CASE(ESPIPE)
// ERROR_CASE(EROFS)
// ERROR_CASE(EMLINK)
ERROR_CASE(EPIPE)
// ERROR_CASE(EDOM)
// ERROR_CASE(ERANGE)
// ERROR_CASE(ENOLCK)
// ERROR_CASE(ENOSYS)
// ERROR_CASE(EIDRM)
// ERROR_CASE(EOVERFLOW)
// ERROR_CASE(EILSEQ)
// ERROR_CASE(ENOTSUP)
ERROR_CASE(ECANCELED)
// ERROR_CASE(EBADMSG)
ERROR_CASE(ENODATA)
// ERROR_CASE(ENOSR)
// ERROR_CASE(ENOSTR)
// ERROR_CASE(ETIME)
#endif
ERROR_CASE(EINTR)
ERROR_CASE(EBADF)
ERROR_CASE(EACCES)
ERROR_CASE(EFAULT)
ERROR_CASE(EINVAL)
ERROR_CASE(EMFILE)
ERROR_CASE(EWOULDBLOCK)
ERROR_CASE(EINPROGRESS)
ERROR_CASE(EALREADY)
ERROR_CASE(ENOTSOCK)
ERROR_CASE(EDESTADDRREQ)
ERROR_CASE(EMSGSIZE)
ERROR_CASE(EPROTOTYPE)
ERROR_CASE(ENOPROTOOPT)
ERROR_CASE(EPROTONOSUPPORT)
#if defined(__APPLE__) || defined(_WIN32)
ERROR_CASE(EOPNOTSUPP)
#endif
ERROR_CASE(EAFNOSUPPORT)
ERROR_CASE(EADDRINUSE)
ERROR_CASE(EADDRNOTAVAIL)
ERROR_CASE(ENETDOWN)
ERROR_CASE(ENETUNREACH)
ERROR_CASE(ENETRESET)
ERROR_CASE(ECONNABORTED)
ERROR_CASE(ECONNRESET)
ERROR_CASE(ENOBUFS)
ERROR_CASE(EISCONN)
ERROR_CASE(ENOTCONN)
ERROR_CASE(ETIMEDOUT)
ERROR_CASE(ECONNREFUSED)
ERROR_CASE(ELOOP)
ERROR_CASE(ENAMETOOLONG)
ERROR_CASE(EHOSTUNREACH)
ERROR_CASE(ENOTEMPTY)
}
*Libraries::Kernel::__Error() = ORBIS_NET_EINTERNAL;
return -1;
}
// if it is 0 or positive return it as it is
return retval;
}
static int ConvertLevels(int level) {
switch (level) {
case ORBIS_NET_SOL_SOCKET:
return SOL_SOCKET;
case ORBIS_NET_IPPROTO_IP:
return IPPROTO_IP;
case ORBIS_NET_IPPROTO_TCP:
return IPPROTO_TCP;
case ORBIS_NET_IPPROTO_IPV6:
return IPPROTO_IPV6;
}
return -1;
}
static void convertOrbisNetSockaddrToUnix(const OrbisNetSockaddr* src, sockaddr_un* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(sockaddr_un));
const OrbisNetSockaddrUn* src_in = (const OrbisNetSockaddrUn*)src;
sockaddr_un* dst_in = (sockaddr_un*)dst;
dst_in->sun_family = src_in->sun_family;
memcpy(&dst_in->sun_path, &src_in->sun_path, src_in->sun_len);
}
static void convertUnixSockaddrToOrbis(sockaddr* src, OrbisNetSockaddr* dst) {
if (src == nullptr || dst == nullptr)
return;
memset(dst, 0, sizeof(OrbisNetSockaddrUn));
OrbisNetSockaddrUn* dst_in = (OrbisNetSockaddrUn*)dst;
sockaddr_un* src_in = (sockaddr_un*)src;
dst_in->sun_len = strnlen(src_in->sun_path, 108);
dst_in->sun_family = src_in->sun_family;
memcpy(&dst_in->sun_path, &src_in->sun_path, dst_in->sun_len);
}
bool UnixSocket::IsValid() const {
#ifdef _WIN32
return sock != INVALID_SOCKET;
#else
return sock != -1;
#endif
}
int UnixSocket::Close() {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
auto out = closesocket(sock);
#else
auto out = ::close(sock);
#endif
return ConvertReturnErrorCode(out);
}
int UnixSocket::Shutdown(int how) {
return ConvertReturnErrorCode(::shutdown(sock, how));
}
int UnixSocket::Bind(const OrbisNetSockaddr* addr, u32 addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr2;
convertOrbisNetSockaddrToUnix(addr, &addr2);
return ConvertReturnErrorCode(::bind(sock, (const sockaddr*)&addr2, sizeof(sockaddr_un)));
}
int UnixSocket::Listen(int backlog) {
std::scoped_lock lock{m_mutex};
return ConvertReturnErrorCode(::listen(sock, backlog));
}
int UnixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{m_mutex};
#ifdef _WIN32
DWORD bytesSent = 0;
LPFN_WSASENDMSG wsasendmsg = nullptr;
GUID guid = WSAID_WSASENDMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
return ConvertReturnErrorCode(-1);
}
int res = wsasendmsg(sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
&bytesSent, nullptr, nullptr);
if (res == SOCKET_ERROR) {
return ConvertReturnErrorCode(-1);
}
return static_cast<int>(bytesSent);
#else
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
return ConvertReturnErrorCode(res);
#endif
}
int UnixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
std::scoped_lock lock{m_mutex};
LOG_ERROR(Lib_Net, "called");
if (to != nullptr) {
sockaddr_un addr;
convertOrbisNetSockaddrToUnix(to, &addr);
return ConvertReturnErrorCode(
sendto(sock, (const char*)msg, len, flags, (sockaddr*)&addr, sizeof(sockaddr_un)));
} else {
return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
}
}
int UnixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{receive_mutex};
#ifdef _WIN32
LPFN_WSARECVMSG wsarecvmsg = nullptr;
GUID guid = WSAID_WSARECVMSG;
DWORD bytes = 0;
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
return ConvertReturnErrorCode(-1);
}
DWORD bytesReceived = 0;
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
if (res == SOCKET_ERROR) {
return ConvertReturnErrorCode(-1);
}
return static_cast<int>(bytesReceived);
#else
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
return ConvertReturnErrorCode(res);
#endif
}
int UnixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from, u32* fromlen) {
std::scoped_lock lock{receive_mutex};
LOG_ERROR(Lib_Net, "called");
if (from != nullptr) {
sockaddr_un addr;
int res = recvfrom(sock, (char*)buf, len, flags, (sockaddr*)&addr, (socklen_t*)fromlen);
convertUnixSockaddrToOrbis((sockaddr*)&addr, from);
*fromlen = sizeof(OrbisNetSockaddrUn);
return ConvertReturnErrorCode(res);
} else {
return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
}
}
SocketPtr UnixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr2;
socklen_t len = sizeof(addr2);
net_socket new_socket = ::accept(sock, (sockaddr*)&addr2, &len);
#ifdef _WIN32
if (new_socket != INVALID_SOCKET) {
#else
if (new_socket >= 0) {
#endif
if (addr && addrlen) {
convertUnixSockaddrToOrbis((sockaddr*)&addr2, addr);
*addrlen = sizeof(OrbisNetSockaddrUn);
}
return std::make_shared<UnixSocket>(new_socket);
}
ConvertReturnErrorCode(new_socket);
return nullptr;
}
int UnixSocket::Connect(const OrbisNetSockaddr* addr, u32 namelen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr2;
convertOrbisNetSockaddrToUnix(addr, &addr2);
int result = 0;
do {
result = ::connect(sock, (sockaddr*)&addr2, sizeof(sockaddr_un));
LOG_DEBUG(Lib_Net, "raw connect result = {}, errno = {}", result,
result == -1 ? Common::GetLastErrorMsg() : "none");
} while (result == -1 && (errno == EINTR || errno == EINPROGRESS));
return ConvertReturnErrorCode(result);
}
int UnixSocket::GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
sockaddr_un addr;
convertOrbisNetSockaddrToUnix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_un);
}
int res = getsockname(sock, (sockaddr*)&addr, (socklen_t*)namelen);
if (res >= 0) {
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
*namelen = sizeof(OrbisNetSockaddrUn);
}
return ConvertReturnErrorCode(res);
}
#define CASE_SETSOCKOPT(opt) \
case ORBIS_NET_##opt: \
return ConvertReturnErrorCode( \
setsockopt(sock, native_level, opt, (const char*)optval, optlen))
#define CASE_SETSOCKOPT_VALUE(opt, value) \
case opt: \
if (optlen != sizeof(*value)) { \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
memcpy(value, optval, optlen); \
return 0
int UnixSocket::SetSocketOptions(int level, int optname, const void* optval, u32 optlen) {
std::scoped_lock lock{m_mutex};
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
return -1;
}
#define CASE_GETSOCKOPT(opt) \
case ORBIS_NET_##opt: { \
socklen_t optlen_temp = *optlen; \
auto retval = ConvertReturnErrorCode( \
getsockopt(sock, native_level, opt, (char*)optval, &optlen_temp)); \
*optlen = optlen_temp; \
return retval; \
}
#define CASE_GETSOCKOPT_VALUE(opt, value) \
case opt: \
if (*optlen < sizeof(value)) { \
*optlen = sizeof(value); \
*Libraries::Kernel::__Error() = ORBIS_NET_EFAULT; \
return -1; \
} \
*optlen = sizeof(value); \
*(decltype(value)*)optval = value; \
return 0;
int UnixSocket::GetSocketOptions(int level, int optname, void* optval, u32* optlen) {
std::scoped_lock lock{m_mutex};
LOG_ERROR(Lib_Net, "Unknown level = {} optname = {}", level, optname);
return ConvertReturnErrorCode(-1);
}
int UnixSocket::GetPeerName(OrbisNetSockaddr* name, u32* namelen) {
std::scoped_lock lock{m_mutex};
LOG_DEBUG(Lib_Net, "called");
sockaddr_un addr;
convertOrbisNetSockaddrToUnix(name, &addr);
if (name != nullptr) {
*namelen = sizeof(sockaddr_un);
}
int res = ::getpeername(sock, (sockaddr*)&addr, (socklen_t*)namelen);
if (res >= 0) {
convertUnixSockaddrToOrbis((sockaddr*)&addr, name);
*namelen = sizeof(OrbisNetSockaddrUn);
}
return ConvertReturnErrorCode(res);
}
int UnixSocket::fstat(Libraries::Kernel::OrbisKernelStat* sb) {
#ifdef _WIN32
LOG_ERROR(Lib_Net, "(STUBBED) called");
sb->st_mode = 0000777u | 0140000u;
return 0;
#else
struct stat st{};
int result = ::fstat(sock, &st);
sb->st_mode = 0000777u | 0140000u;
sb->st_size = st.st_size;
sb->st_blocks = st.st_blocks;
sb->st_blksize = st.st_blksize;
// sb->st_flags = st.st_flags;
return ConvertReturnErrorCode(result);
#endif
}
} // namespace Libraries::Net
+143 -136
View File
@@ -12,9 +12,12 @@
#include "common/string_util.h"
#include "core/emulator_settings.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/network/net_upnp.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_p2p.h"
#include "core/libraries/net/net_upnp.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_score/np_score.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
@@ -28,11 +31,50 @@
namespace Libraries::Np {
NpHandler::NpHandler()
: m_matching2_contexts(
std::unique_ptr<NpMatching2::ContextManager>(new NpMatching2::ContextManager())),
m_matching2_state(std::make_unique<NpMatching2::NpMatching2State>()) {}
NpHandler::~NpHandler() = default;
NpHandler& NpHandler::GetInstance() {
static NpHandler s_instance;
return s_instance;
}
NpMatching2::ContextManager& NpHandler::GetMatching2ContextManager() {
return *m_matching2_contexts;
}
NpMatching2::NpMatching2State& NpHandler::GetMatching2State() {
return *m_matching2_state;
}
NpHandler::Matching2CacheGuard NpHandler::LockMatching2Cache(
NpMatching2::OrbisNpMatching2ContextId ctx_id) {
std::shared_ptr<NpMatching2::Matching2ContextCache> cache;
{
std::lock_guard lock(m_mutex_matching2_cache);
auto& entry = m_matching2_cache[ctx_id];
if (!entry) {
entry = std::make_shared<NpMatching2::Matching2ContextCache>();
}
cache = entry;
}
return Matching2CacheGuard(std::move(cache));
}
void NpHandler::ResetMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id) {
std::lock_guard lock(m_mutex_matching2_cache);
m_matching2_cache.erase(ctx_id);
}
void NpHandler::ResetMatching2Caches() {
std::lock_guard lock(m_mutex_matching2_cache);
m_matching2_cache.clear();
}
std::pair<std::string, u16> NpHandler::ParseServerAddress() const {
const std::string server_str = EmulatorSettings.GetShadNetServer();
u16 port = 31313; // default port
@@ -193,6 +235,9 @@ void NpHandler::Shutdown() {
if (m_worker_thread.joinable())
m_worker_thread.join();
// P2P goes with NP: closes the UDP port and removes its UPnP forwarding.
Net::StopP2P();
LOG_INFO(NpHandler, "Shutdown complete");
}
@@ -549,17 +594,17 @@ bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id
const std::vector<std::string>& to,
const std::string& message) {
if (session_id.empty() || to.empty()) {
LOG_ERROR(NpHandler, "SendSessionInvitation: empty session_id or recipient list");
LOG_ERROR(NpHandler, "empty session_id or recipient list");
return false;
}
const std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
if (base_url.empty()) {
LOG_ERROR(NpHandler, "SendSessionInvitation: WebAPI server address is empty");
LOG_ERROR(NpHandler, "WebAPI server address is empty");
return false;
}
const std::string token = GetBearerToken(user_id);
if (token.empty()) {
LOG_ERROR(NpHandler, "SendSessionInvitation: no bearer token for user_id={}", user_id);
LOG_ERROR(NpHandler, "no bearer token for user_id={}", user_id);
return false;
}
@@ -599,15 +644,14 @@ bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id
const httplib::Headers headers = {{"Authorization", "Bearer " + token}};
const auto res = cli.Post(path.c_str(), headers, body, content_type.c_str());
if (!res) {
LOG_ERROR(NpHandler, "SendSessionInvitation: POST {} failed (no response)", path);
LOG_ERROR(NpHandler, "POST {} failed (no response)", path);
return false;
}
if (res->status != 200 && res->status != 204) {
LOG_ERROR(NpHandler, "SendSessionInvitation: POST {} -> HTTP {}", path, res->status);
LOG_ERROR(NpHandler, "POST {} -> HTTP {}", path, res->status);
return false;
}
LOG_INFO(NpHandler, "SendSessionInvitation: sent invite to {} recipient(s) for session '{}'",
to.size(), session_id);
LOG_INFO(NpHandler, "sent invite to {} recipient(s) for session '{}'", to.size(), session_id);
return true;
}
@@ -688,35 +732,14 @@ bool NpHandler::AcceptSessionInvitation(s32 user_id, const std::string& invitati
// Whatever surface handled it (RECV dialog or the emulator prompt), retire the other one.
ImGui::InvitationPrompt::Dismiss(invitation_id);
if (!found) {
LOG_ERROR(NpHandler, "AcceptSessionInvitation: no pending invite '{}' for user_id={}",
invitation_id, user_id);
LOG_ERROR(NpHandler, "no pending invite '{}' for user_id={}", invitation_id, user_id);
return false;
}
// Raise the join event now that the user has explicitly accepted (via the RECV dialog or the
// emulator's system-UI equivalent).
PostSessionInvitationEvent(user_id, inv.session_id, invitation_id, inv.to_npid, inv.from_npid,
inv.from_account_id);
// Consume it server-side (PUT usedFlag=true).
const std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
const std::string token = GetBearerToken(user_id);
if (base_url.empty() || token.empty()) {
LOG_ERROR(NpHandler, "AcceptSessionInvitation: no WebAPI server/token for user_id={}",
user_id);
return false;
}
httplib::Client cli(base_url);
cli.set_connection_timeout(5);
cli.set_read_timeout(10);
const httplib::Headers headers = {{"Authorization", "Bearer " + token}};
const std::string path = "/v1/users/me/invitations/" + invitation_id;
const auto res = cli.Put(path.c_str(), headers, "{\"usedFlag\":true}", "application/json");
if (!res || (res->status != 200 && res->status != 204)) {
LOG_ERROR(NpHandler, "AcceptSessionInvitation: PUT {} failed ({})", path,
res ? res->status : 0);
return false;
}
LOG_INFO(NpHandler, "AcceptSessionInvitation: consumed '{}' session='{}'", invitation_id,
inv.session_id);
LOG_INFO(NpHandler, "accepted '{}' session='{}'", invitation_id, inv.session_id);
return true;
}
@@ -732,8 +755,7 @@ void NpHandler::DeclineSessionInvitation(s32 user_id, const std::string& invitat
v.begin(), v.end(),
[&](const PendingInvitation& p) { return p.invitation_id == invitation_id; }),
v.end());
LOG_INFO(NpHandler, "DeclineSessionInvitation: dismissed '{}' for user_id={}", invitation_id,
user_id);
LOG_INFO(NpHandler, "dismissed '{}' for user_id={}", invitation_id, user_id);
}
u32 NpHandler::GetLocalIpAddr(s32 user_id) const {
@@ -1117,7 +1139,7 @@ std::string NpHandler::GetNpCommId(s32 service_label) const {
com_id = ids[idx];
if (static_cast<size_t>(service_label) >= ids.size()) {
LOG_WARNING(NpHandler,
"GetNpCommId: service_label={} >= npbind entry count {} — falling back "
"service_label={} >= npbind entry count {} — falling back "
"to index 0",
service_label, ids.size());
}
@@ -1139,13 +1161,13 @@ s32 NpHandler::RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "RecordScore: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "RecordScore: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1189,7 +1211,7 @@ s32 NpHandler::RecordScore(s32 user_id, s32 service_label, u32 boardId, s32 pcId
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"RecordScore: user_id={} service_label={} board={} pcId={} score={} commentLen={} "
"user_id={} service_label={} board={} pcId={} score={} commentLen={} "
"gameInfoSize={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, pcId, score, commentLen, gameInfoSize, pkt_id,
com_id);
@@ -1204,13 +1226,13 @@ s32 NpHandler::RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 p
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "RecordGameData: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "RecordGameData: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1250,7 +1272,7 @@ s32 NpHandler::RecordGameData(s32 user_id, s32 service_label, u32 boardId, s32 p
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"RecordGameData: user_id={} service_label={} board={} pcId={} score={} dataSize={} "
"user_id={} service_label={} board={} pcId={} score={} dataSize={} "
"pkt_id={} com_id='{}'",
user_id, service_label, boardId, pcId, score, size, pkt_id, com_id);
return ORBIS_OK;
@@ -1264,13 +1286,13 @@ s32 NpHandler::GetGameData(s32 user_id, s32 service_label, u32 boardId, const st
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetGameData: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetGameData: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1310,7 +1332,7 @@ s32 NpHandler::GetGameData(s32 user_id, s32 service_label, u32 boardId, const st
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetGameData: user_id={} service_label={} board={} npId='{}' pcId={} recvSize={} "
"user_id={} service_label={} board={} npId='{}' pcId={} recvSize={} "
"pkt_id={} com_id='{}'",
user_id, service_label, boardId, npId, pcId, recvSize, pkt_id, com_id);
return ORBIS_OK;
@@ -1324,13 +1346,13 @@ s32 NpHandler::GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardI
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetGameDataByAccountId: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetGameDataByAccountId: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1371,7 +1393,7 @@ s32 NpHandler::GetGameDataByAccountId(s32 user_id, s32 service_label, u32 boardI
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetGameDataByAccountId: user_id={} service_label={} board={} accountId={} pcId={} "
"user_id={} service_label={} board={} accountId={} pcId={} "
"recvSize={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, accountId, pcId, recvSize, pkt_id, com_id);
return ORBIS_OK;
@@ -1385,13 +1407,13 @@ s32 NpHandler::GetBoardInfo(s32 user_id, s32 service_label, u32 boardId,
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetBoardInfo: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetBoardInfo: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1419,8 +1441,8 @@ s32 NpHandler::GetBoardInfo(s32 user_id, s32 service_label, u32 boardId,
pending.user_id = user_id;
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler, "GetBoardInfo: user_id={} service_label={} board={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, pkt_id, com_id);
LOG_INFO(NpHandler, "user_id={} service_label={} board={} pkt_id={} com_id='{}'", user_id,
service_label, boardId, pkt_id, com_id);
return ORBIS_OK;
}
@@ -1434,8 +1456,7 @@ s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId,
std::shared_ptr<NpScore::ScoreRequestCtx> req) {
// pcIds must either be empty (use 0 for everything) or match npIds in size.
if (!pcIds.empty() && pcIds.size() != npIds.size()) {
LOG_ERROR(NpHandler, "GetRankingByNpId: pcIds size {} != npIds size {}", pcIds.size(),
npIds.size());
LOG_ERROR(NpHandler, "pcIds size {} != npIds size {}", pcIds.size(), npIds.size());
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
}
@@ -1445,13 +1466,13 @@ s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId,
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetRankingByNpId: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetRankingByNpId: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1500,7 +1521,7 @@ s32 NpHandler::GetRankingByNpId(s32 user_id, s32 service_label, u32 boardId,
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetRankingByNpId: user_id={} service_label={} board={} npIdCount={} "
"user_id={} service_label={} board={} npIdCount={} "
"withPcId={} withComment={} withGameInfo={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, npIds.size(), !pcIds.empty(), commentArray != nullptr,
infoArray != nullptr, pkt_id, com_id);
@@ -1518,13 +1539,13 @@ s32 NpHandler::GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u3
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetRankingByRange: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetRankingByRange: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1569,7 +1590,7 @@ s32 NpHandler::GetRankingByRange(s32 user_id, s32 service_label, u32 boardId, u3
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetRankingByRange: user_id={} service_label={} board={} startRank={} numRanks={} "
"user_id={} service_label={} board={} startRank={} numRanks={} "
"withComment={} withGameInfo={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, startSerialRank, arrayNum, commentArray != nullptr,
infoArray != nullptr, pkt_id, com_id);
@@ -1587,13 +1608,13 @@ s32 NpHandler::GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetRankingByRangeA: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetRankingByRangeA: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1641,7 +1662,7 @@ s32 NpHandler::GetRankingByRangeA(s32 user_id, s32 service_label, u32 boardId, u
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetRankingByRangeA: user_id={} service_label={} board={} startRank={} numRanks={} "
"user_id={} service_label={} board={} startRank={} numRanks={} "
"withComment={} withGameInfo={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, startSerialRank, arrayNum, commentArray != nullptr,
infoArray != nullptr, pkt_id, com_id);
@@ -1661,7 +1682,7 @@ s32 NpHandler::GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetRankingByAccountId: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
@@ -1715,7 +1736,7 @@ s32 NpHandler::GetRankingByAccountId(s32 user_id, s32 service_label, u32 boardId
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetRankingByAccountId: user_id={} service_label={} board={} accountIdCount={} "
"user_id={} service_label={} board={} accountIdCount={} "
"withComment={} withGameInfo={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, accountIds.size(), commentArray != nullptr,
infoArray != nullptr, pkt_id, com_id);
@@ -1733,13 +1754,13 @@ s32 NpHandler::GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bo
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetFriendsRanking: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetFriendsRanking: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1784,7 +1805,7 @@ s32 NpHandler::GetFriendsRanking(s32 user_id, s32 service_label, u32 boardId, bo
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetFriendsRanking: user_id={} service_label={} board={} includeSelf={} "
"user_id={} service_label={} board={} includeSelf={} "
"arrayNum={} withComment={} withGameInfo={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, includeSelf, arrayNum, commentArray != nullptr,
infoArray != nullptr, pkt_id, com_id);
@@ -1802,13 +1823,13 @@ s32 NpHandler::GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, b
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "GetFriendsRankingA: user_id={} not connected", user_id);
LOG_WARNING(NpHandler, "user_id={} not connected", user_id);
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "GetFriendsRankingA: user_id={} not authenticated", user_id);
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
return ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN;
}
@@ -1855,7 +1876,7 @@ s32 NpHandler::GetFriendsRankingA(s32 user_id, s32 service_label, u32 boardId, b
m_pending_score.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler,
"GetFriendsRankingA: user_id={} service_label={} board={} includeSelf={} "
"user_id={} service_label={} board={} includeSelf={} "
"arrayNum={} withComment={} withGameInfo={} pkt_id={} com_id='{}'",
user_id, service_label, boardId, includeSelf, arrayNum, commentArray != nullptr,
infoArray != nullptr, pkt_id, com_id);
@@ -1880,7 +1901,7 @@ static u32 FillPlainRankArrayFromProto(const shadnet::GetScoreResponse& resp, u6
std::memcpy(out.npId.handle.data, npid.data(), cp);
out.npId.handle.term = 0;
LOG_INFO(NpHandler,
"FillPlainRankArrayFromProto: out[{}] (resp[{}]) npid='{}' (len={}) rank={} "
"out[{}] (resp[{}]) npid='{}' (len={}) rank={} "
"score={}",
out_i, i, npid, npid.size(), r.rank(), r.score());
out.pcId = r.pcid();
@@ -1926,7 +1947,7 @@ static u32 FillPlainRankArrayAFromProto(const shadnet::GetScoreResponse& resp, u
std::memcpy(out.onlineId.data, npid.data(), cp);
out.onlineId.term = 0;
LOG_INFO(NpHandler,
"FillPlainRankArrayAFromProto: out[{}] (resp[{}]) npid='{}' (len={}) rank={} "
"out[{}] (resp[{}]) npid='{}' (len={}) rank={} "
"score={} accountId={}",
out_i, i, npid, npid.size(), r.rank(), r.score(), r.accountid());
out.pcId = r.pcid();
@@ -1963,7 +1984,7 @@ static u32 FillPlayerRankArrayAFromProto(const shadnet::GetScoreResponse& resp,
const int n_req = static_cast<int>(requestedCount);
if (n_resp != n_req) {
LOG_WARNING(NpHandler,
"FillPlayerRankArrayAFromProto: response count {} != request count {} — "
"response count {} != request count {} — "
"will fill up to min() and leave extras at hasData=0",
n_resp, n_req);
}
@@ -2015,7 +2036,7 @@ static u32 FillRankArrayFromProto(const shadnet::GetScoreResponse& resp,
const int n_req = static_cast<int>(requestedNpIds.size());
if (n_resp != n_req) {
LOG_WARNING(NpHandler,
"FillRankArrayFromProto: response count {} != request count {} — "
"response count {} != request count {} — "
"will fill up to min() and leave extras at hasData=0",
n_resp, n_req);
}
@@ -2782,24 +2803,22 @@ void NpHandler::ReportTrophyUnlock(s32 user_id, s32 service_label, s32 trophy_id
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_DEBUG(NpHandler, "ReportTrophyUnlock: no shadNet session for user_id={}; skipping",
user_id);
LOG_DEBUG(NpHandler, "no shadNet session for user_id={}; skipping", user_id);
return;
}
client = it->second;
}
if (!client) {
LOG_DEBUG(NpHandler, "ReportTrophyUnlock: null client for user_id={}; skipping", user_id);
LOG_DEBUG(NpHandler, "null client for user_id={}; skipping", user_id);
return;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "ReportTrophyUnlock: not authenticated; trophy {} not mirrored",
trophy_id);
LOG_WARNING(NpHandler, "not authenticated; trophy {} not mirrored", trophy_id);
return;
}
if (!client->IsTrophiesEnabled()) {
LOG_WARNING(NpHandler,
"ReportTrophyUnlock: server has not advertised trophy support; trophy {} "
"server has not advertised trophy support; trophy {} "
"not mirrored",
trophy_id);
return;
@@ -2808,15 +2827,15 @@ void NpHandler::ReportTrophyUnlock(s32 user_id, s32 service_label, s32 trophy_id
const std::string com_id = GetNpCommId(service_label);
if (!IsValidNpCommId(com_id)) {
LOG_WARNING(NpHandler,
"ReportTrophyUnlock: no valid NP Communication ID for service_label={}; "
"no valid NP Communication ID for service_label={}; "
"skipping trophy {}",
service_label, trophy_id);
return;
}
const u64 pkt_id = client->UnlockTrophy(com_id, trophy_id, timestamp);
LOG_INFO(NpHandler, "ReportTrophyUnlock: user_id={} trophy={} com_id='{}' pkt_id={}", user_id,
trophy_id, com_id, pkt_id);
LOG_INFO(NpHandler, "user_id={} trophy={} com_id='{}' pkt_id={}", user_id, trophy_id, com_id,
pkt_id);
}
void NpHandler::SyncTrophies(
@@ -2827,30 +2846,28 @@ void NpHandler::SyncTrophies(
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_DEBUG(NpHandler, "SyncTrophies: no shadNet session for user_id={}; skipping",
user_id);
LOG_DEBUG(NpHandler, "no shadNet session for user_id={}; skipping", user_id);
return;
}
client = it->second;
}
if (!client) {
LOG_DEBUG(NpHandler, "SyncTrophies: null client for user_id={}; skipping", user_id);
LOG_DEBUG(NpHandler, "null client for user_id={}; skipping", user_id);
return;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "SyncTrophies: not authenticated; skipping sync");
LOG_WARNING(NpHandler, "not authenticated; skipping sync");
return;
}
if (!client->IsTrophiesEnabled()) {
LOG_WARNING(NpHandler, "SyncTrophies: server has not advertised trophy support; "
LOG_WARNING(NpHandler, "server has not advertised trophy support; "
"skipping sync");
return;
}
const std::string com_id = GetNpCommId(service_label);
if (!IsValidNpCommId(com_id)) {
LOG_WARNING(NpHandler,
"SyncTrophies: no valid NP Communication ID for service_label={}; skipping",
LOG_WARNING(NpHandler, "no valid NP Communication ID for service_label={}; skipping",
service_label);
return;
}
@@ -2860,7 +2877,7 @@ void NpHandler::SyncTrophies(
std::lock_guard lock(m_mutex_pending_trophy);
m_pending_trophy.emplace(pkt_id, std::move(on_merged));
}
LOG_INFO(NpHandler, "SyncTrophies: user_id={} uploading {} com_id='{}' pkt_id={}", user_id,
LOG_INFO(NpHandler, "user_id={} uploading {} com_id='{}' pkt_id={}", user_id,
local_trophies.size(), com_id, pkt_id);
}
@@ -3024,7 +3041,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
std::lock_guard lock(m_mutex_pending_score);
auto it = m_pending_score.find(pkt_id);
if (it == m_pending_score.end()) {
LOG_WARNING(NpHandler, "OnScoreReply: no pending request for pkt_id={} cmd={}", pkt_id,
LOG_WARNING(NpHandler, "no pending request for pkt_id={} cmd={}", pkt_id,
static_cast<int>(cmd));
return;
}
@@ -3060,8 +3077,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
default:
break;
}
LOG_WARNING(NpHandler,
"OnScoreReply: user_id={} pkt_id={} cmd={} server_error={} → orbis_err={:#x}",
LOG_WARNING(NpHandler, "user_id={} pkt_id={} cmd={} server_error={} → orbis_err={:#x}",
user_id, pkt_id, static_cast<int>(cmd), static_cast<int>(error), orbis_err);
req->SetResult(orbis_err);
return;
@@ -3075,8 +3091,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
const u32 rank = static_cast<u32>(body[0]) | (static_cast<u32>(body[1]) << 8) |
(static_cast<u32>(body[2]) << 16) | (static_cast<u32>(body[3]) << 24);
*req->tmpRankOut = rank;
LOG_INFO(NpHandler, "OnScoreReply: RecordScore user_id={} pkt_id={} rank={}", user_id,
pkt_id, rank);
LOG_INFO(NpHandler, "RecordScore user_id={} pkt_id={} rank={}", user_id, pkt_id, rank);
}
req->SetResult(ORBIS_OK);
break;
@@ -3087,8 +3102,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
// the caller. Server-side errors are mapped to ErrorType values
// (NotFound / ScoreInvalid / ScoreHasData) which arrive as the
// packet's error byte; if we got here, error == NoError.
LOG_INFO(NpHandler, "OnScoreReply: RecordScoreData user_id={} pkt_id={} ok", user_id,
pkt_id);
LOG_INFO(NpHandler, "RecordScoreData user_id={} pkt_id={} ok", user_id, pkt_id);
req->SetResult(ORBIS_OK);
break;
}
@@ -3099,7 +3113,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
? "GetScoreData"
: "GetScoreGameDataByAccId";
if (body.size() < 4) {
LOG_ERROR(NpHandler, "OnScoreReply: {} body too small ({})", cmd_name, body.size());
LOG_ERROR(NpHandler, "{} body too small ({})", cmd_name, body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3107,8 +3121,8 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
(static_cast<u32>(body[2]) << 16) |
(static_cast<u32>(body[3]) << 24);
if (static_cast<size_t>(4) + stored_size > body.size()) {
LOG_ERROR(NpHandler, "OnScoreReply: {} blob size {} exceeds body size {}", cmd_name,
stored_size, body.size());
LOG_ERROR(NpHandler, "{} blob size {} exceeds body size {}", cmd_name, stored_size,
body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3124,20 +3138,19 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
const u64 copy_n = std::min<u64>(static_cast<u64>(stored_size), pending.recvSize);
std::memcpy(pending.dataOut, body.data() + 4, static_cast<size_t>(copy_n));
if (copy_n < stored_size) {
LOG_WARNING(NpHandler,
"OnScoreReply: {} truncated user_id={} pkt_id={} stored={} recv={}",
LOG_WARNING(NpHandler, "{} truncated user_id={} pkt_id={} stored={} recv={}",
cmd_name, user_id, pkt_id, stored_size, pending.recvSize);
}
}
LOG_INFO(NpHandler, "OnScoreReply: {} user_id={} pkt_id={} storedSize={} recvSize={}",
cmd_name, user_id, pkt_id, stored_size, pending.recvSize);
LOG_INFO(NpHandler, "{} user_id={} pkt_id={} storedSize={} recvSize={}", cmd_name, user_id,
pkt_id, stored_size, pending.recvSize);
req->SetResult(ORBIS_OK);
break;
}
case ShadNet::CommandType::GetBoardInfos: {
if (body.size() < 4) {
LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos body too small ({})", body.size());
LOG_ERROR(NpHandler, "GetBoardInfos body too small ({})", body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3145,14 +3158,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
(static_cast<u32>(body[2]) << 16) |
(static_cast<u32>(body[3]) << 24);
if (static_cast<size_t>(4) + proto_size > body.size()) {
LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos proto size {} exceeds body size {}",
proto_size, body.size());
LOG_ERROR(NpHandler, "GetBoardInfos proto size {} exceeds body size {}", proto_size,
body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
shadnet::BoardInfo bi;
if (!bi.ParseFromArray(body.data() + 4, static_cast<int>(proto_size))) {
LOG_ERROR(NpHandler, "OnScoreReply: GetBoardInfos proto parse failed");
LOG_ERROR(NpHandler, "GetBoardInfos proto parse failed");
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3164,7 +3177,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
pending.boardInfo->uploadSizeLimit = bi.uploadsizelimit();
}
LOG_INFO(NpHandler,
"OnScoreReply: GetBoardInfos user_id={} pkt_id={} rankLimit={} updateMode={} "
"GetBoardInfos user_id={} pkt_id={} rankLimit={} updateMode={} "
"sortMode={} uploadNumLimit={} uploadSizeLimit={}",
user_id, pkt_id, bi.ranklimit(), bi.updatemode(), bi.sortmode(),
bi.uploadnumlimit(), bi.uploadsizelimit());
@@ -3175,7 +3188,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
case ShadNet::CommandType::GetScoreNpid: {
// Reply body = u32 LE proto size + GetScoreResponse proto bytes.
if (body.size() < 4) {
LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid body too small ({})", body.size());
LOG_ERROR(NpHandler, "GetScoreNpid body too small ({})", body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3183,14 +3196,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
(static_cast<u32>(body[2]) << 16) |
(static_cast<u32>(body[3]) << 24);
if (static_cast<size_t>(4) + proto_size > body.size()) {
LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid proto size {} exceeds body size {}",
proto_size, body.size());
LOG_ERROR(NpHandler, "GetScoreNpid proto size {} exceeds body size {}", proto_size,
body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
shadnet::GetScoreResponse resp;
if (!resp.ParseFromArray(body.data() + 4, static_cast<int>(proto_size))) {
LOG_ERROR(NpHandler, "OnScoreReply: GetScoreNpid proto parse failed");
LOG_ERROR(NpHandler, "GetScoreNpid proto parse failed");
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3202,9 +3215,8 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
if (pending.totalRecord != nullptr) {
*pending.totalRecord = resp.totalrecord();
}
LOG_INFO(NpHandler,
"OnScoreReply: GetScoreNpid user_id={} pkt_id={} found={}/{} totalRecord={}",
user_id, pkt_id, found, pending.arrayNum, resp.totalrecord());
LOG_INFO(NpHandler, "GetScoreNpid user_id={} pkt_id={} found={}/{} totalRecord={}", user_id,
pkt_id, found, pending.arrayNum, resp.totalrecord());
req->SetResult(static_cast<s32>(found));
break;
}
@@ -3213,8 +3225,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
// Index-aligned with the request's accountIds[] (empty-npid sentinel
// marks "no score on this board"), same contract as GetScoreNpid.
if (body.size() < 4) {
LOG_ERROR(NpHandler, "OnScoreReply: GetScoreAccountId body too small ({})",
body.size());
LOG_ERROR(NpHandler, "GetScoreAccountId body too small ({})", body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3222,15 +3233,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
(static_cast<u32>(body[2]) << 16) |
(static_cast<u32>(body[3]) << 24);
if (static_cast<size_t>(4) + proto_size > body.size()) {
LOG_ERROR(NpHandler,
"OnScoreReply: GetScoreAccountId proto size {} exceeds body size {}",
proto_size, body.size());
LOG_ERROR(NpHandler, "GetScoreAccountId proto size {} exceeds body size {}", proto_size,
body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
shadnet::GetScoreResponse resp;
if (!resp.ParseFromArray(body.data() + 4, static_cast<int>(proto_size))) {
LOG_ERROR(NpHandler, "OnScoreReply: GetScoreAccountId proto parse failed");
LOG_ERROR(NpHandler, "GetScoreAccountId proto parse failed");
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3243,8 +3253,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
if (pending.totalRecord != nullptr) {
*pending.totalRecord = resp.totalrecord();
}
LOG_INFO(NpHandler,
"OnScoreReply: GetScoreAccountId user_id={} pkt_id={} found={}/{} totalRecord={}",
LOG_INFO(NpHandler, "GetScoreAccountId user_id={} pkt_id={} found={}/{} totalRecord={}",
user_id, pkt_id, found, pending.arrayNum, resp.totalrecord());
req->SetResult(static_cast<s32>(found));
break;
@@ -3255,7 +3264,7 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
const char* cmd_name =
(cmd == ShadNet::CommandType::GetScoreRange) ? "GetScoreRange" : "GetScoreFriends";
if (body.size() < 4) {
LOG_ERROR(NpHandler, "OnScoreReply: {} body too small ({})", cmd_name, body.size());
LOG_ERROR(NpHandler, "{} body too small ({})", cmd_name, body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3263,14 +3272,14 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
(static_cast<u32>(body[2]) << 16) |
(static_cast<u32>(body[3]) << 24);
if (static_cast<size_t>(4) + proto_size > body.size()) {
LOG_ERROR(NpHandler, "OnScoreReply: {} proto size {} exceeds body size {}", cmd_name,
proto_size, body.size());
LOG_ERROR(NpHandler, "{} proto size {} exceeds body size {}", cmd_name, proto_size,
body.size());
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
shadnet::GetScoreResponse resp;
if (!resp.ParseFromArray(body.data() + 4, static_cast<int>(proto_size))) {
LOG_ERROR(NpHandler, "OnScoreReply: {} proto parse failed", cmd_name);
LOG_ERROR(NpHandler, "{} proto parse failed", cmd_name);
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3288,16 +3297,15 @@ void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
if (pending.totalRecord != nullptr) {
*pending.totalRecord = resp.totalrecord();
}
LOG_INFO(NpHandler, "OnScoreReply: {} user_id={} pkt_id={} found={}/{} totalRecord={}{}",
cmd_name, user_id, pkt_id, found, pending.arrayNum, resp.totalrecord(),
LOG_INFO(NpHandler, "{} user_id={} pkt_id={} found={}/{} totalRecord={}{}", cmd_name,
user_id, pkt_id, found, pending.arrayNum, resp.totalrecord(),
pending.aRankArray != nullptr ? " (A-variant)" : "");
req->SetResult(static_cast<s32>(found));
break;
}
default:
LOG_WARNING(NpHandler, "OnScoreReply: unexpected cmd={} pkt_id={}", static_cast<int>(cmd),
pkt_id);
LOG_WARNING(NpHandler, "unexpected cmd={} pkt_id={}", static_cast<int>(cmd), pkt_id);
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
break;
}
@@ -3310,7 +3318,7 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
std::lock_guard lock(m_mutex_pending_tus);
auto it = m_pending_tus.find(pkt_id);
if (it == m_pending_tus.end()) {
LOG_WARNING(NpHandler, "OnTusReply: no pending request for pkt_id={} cmd={}", pkt_id,
LOG_WARNING(NpHandler, "no pending request for pkt_id={} cmd={}", pkt_id,
static_cast<int>(cmd));
return;
}
@@ -3338,8 +3346,7 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
default:
break;
}
LOG_WARNING(NpHandler,
"OnTusReply: user_id={} pkt_id={} cmd={} server_error={} -> orbis {:#x}",
LOG_WARNING(NpHandler, "user_id={} pkt_id={} cmd={} server_error={} -> orbis {:#x}",
user_id, pkt_id, static_cast<int>(cmd), static_cast<int>(error),
static_cast<u32>(orbis_err));
req->SetResult(orbis_err);
@@ -3603,7 +3610,7 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
}
default:
// Set*/Delete* carry no payload to fill.
LOG_DEBUG(NpHandler, "OnTusReply: cmd={} ok (no payload)", static_cast<int>(cmd));
LOG_DEBUG(NpHandler, "cmd={} ok (no payload)", static_cast<int>(cmd));
break;
}
req->SetResult(ORBIS_OK);
+61 -3
View File
@@ -19,8 +19,10 @@
#include "common/types.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2_cache.h"
#include "core/libraries/np/np_score/np_score.h"
#include "core/libraries/np/np_score/np_score_ctx.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_tus/np_tus.h"
#include "core/libraries/np/np_tus/np_tus_ctx.h"
#include "core/libraries/np/np_types.h"
@@ -30,8 +32,42 @@
namespace Libraries::Np {
namespace NpMatching2 {
class ContextManager;
struct NpMatching2State;
} // namespace NpMatching2
class NpHandler {
public:
class Matching2CacheGuard {
public:
Matching2CacheGuard(Matching2CacheGuard&&) noexcept = default;
Matching2CacheGuard& operator=(Matching2CacheGuard&&) noexcept = default;
Matching2CacheGuard(const Matching2CacheGuard&) = delete;
Matching2CacheGuard& operator=(const Matching2CacheGuard&) = delete;
NpMatching2::Matching2ContextCache* operator->() {
return m_cache.get();
}
const NpMatching2::Matching2ContextCache* operator->() const {
return m_cache.get();
}
NpMatching2::Matching2ContextCache& operator*() {
return *m_cache;
}
private:
friend class NpHandler;
explicit Matching2CacheGuard(std::shared_ptr<NpMatching2::Matching2ContextCache> cache)
: m_cache(std::move(cache)), m_lock(m_cache->mutex) {}
std::shared_ptr<NpMatching2::Matching2ContextCache> m_cache;
std::unique_lock<std::recursive_mutex> m_lock;
};
static NpHandler& GetInstance();
NpHandler(const NpHandler&) = delete;
@@ -322,9 +358,23 @@ public:
s32 RegisterStateCallback(StateCallback cb, void* userdata);
void UnregisterStateCallback(s32 handle);
Matching2CacheGuard LockMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id);
void ResetMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id);
void ResetMatching2Caches();
NpMatching2::ContextManager& GetMatching2ContextManager();
NpMatching2::NpMatching2State& GetMatching2State();
NpSignaling::NpSignalingState& GetSignalingState() {
return m_signaling_state;
}
const NpSignaling::NpSignalingState& GetSignalingState() const {
return m_signaling_state;
}
private:
NpHandler() = default;
~NpHandler() = default;
NpHandler();
~NpHandler();
/// Connect one user. Blocks until connected+authenticated or failed.
bool ConnectUser(s32 user_id, const std::string& host, u16 port, const std::string& npid,
@@ -476,6 +526,14 @@ private:
// Friend state per user, seeded from LoginReply and updated by notifications/actions.
mutable std::mutex m_mutex_friend_state;
std::map<s32, FriendListSnapshot> m_friend_state;
mutable std::mutex m_mutex_matching2_cache;
std::map<NpMatching2::OrbisNpMatching2ContextId,
std::shared_ptr<NpMatching2::Matching2ContextCache>>
m_matching2_cache;
std::unique_ptr<NpMatching2::ContextManager> m_matching2_contexts;
std::unique_ptr<NpMatching2::NpMatching2State> m_matching2_state;
NpSignaling::NpSignalingState m_signaling_state;
};
} // namespace Libraries::Np
} // namespace Libraries::Np
+2
View File
@@ -111,6 +111,8 @@ void NotifyNpStateFromUserServiceEvent(Libraries::UserService::OrbisUserServiceE
s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpId* np_id);
s32 PS4_SYSV_ABI sceNpGetAccountIdA(Libraries::UserService::OrbisUserServiceUserId user_id,
u64* account_id);
s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id,
OrbisNpOnlineId* online_id);
s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id,
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <cstdint>
#include <cstring>
#include <memory>
#include <fmt/format.h>
@@ -9,7 +10,7 @@
#include "core/emulator_settings.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/net.h"
#include "core/libraries/net/net.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
@@ -17,6 +18,7 @@
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/signaling_handler.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpMatching2 {
@@ -35,7 +37,12 @@ int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContext
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
return ContextManager::Instance().CreateContext(param->npId, param->serviceLabel, ctxId);
const int rc = NpHandler::GetInstance().GetMatching2ContextManager().CreateContext(
param->npId, param->serviceLabel, ctxId);
if (rc == ORBIS_OK) {
SignalingHandler::AddContextRef();
}
return rc;
}
int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param,
@@ -55,7 +62,12 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex
const Libraries::Np::OrbisNpId np_id =
Libraries::Np::NpHandler::GetInstance().GetNpId(param->userId);
return ContextManager::Instance().CreateContext(&np_id, param->serviceLabel, ctxId, true);
const int rc = NpHandler::GetInstance().GetMatching2ContextManager().CreateContext(
&np_id, param->serviceLabel, ctxId, true);
if (rc == ORBIS_OK) {
SignalingHandler::AddContextRef();
}
return rc;
}
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId,
@@ -71,7 +83,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId,
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -97,7 +109,7 @@ int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId,
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -125,7 +137,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -147,7 +159,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCa
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextManager::Instance().ApplyContextCallback(callback, userdata);
NpHandler::GetInstance().GetMatching2ContextManager().ApplyContextCallback(callback, userdata);
return ORBIS_OK;
}
@@ -159,7 +171,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback(
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -179,7 +191,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2Context
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -199,7 +211,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -207,6 +219,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context
ctx->signaling_callback = callback;
ctx->signaling_callback_arg = userdata;
SignalingHandler::StartMatching2(*ctx);
return ORBIS_OK;
}
@@ -224,7 +237,7 @@ int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64
return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT;
}
const s32 rc = ContextManager::Instance().Start(ctxId);
const s32 rc = NpHandler::GetInstance().GetMatching2ContextManager().Start(ctxId);
if (rc != ORBIS_OK) {
return rc;
}
@@ -241,14 +254,19 @@ int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) {
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
const s32 rc = ContextManager::Instance().Stop(ctxId);
const s32 rc = NpHandler::GetInstance().GetMatching2ContextManager().Stop(ctxId);
if (rc != ORBIS_OK) {
return rc;
}
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (ctx && ctx->signaling_callback) {
SignalingHandler::StopMatching2(*ctx,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TERMINATED_BY_MYSELF);
}
const s32 submit_rc = MmContextStop(ctxId);
if (submit_rc != ORBIS_OK) {
ContextManager::Instance().CompleteStop(ctxId);
NpHandler::GetInstance().GetMatching2ContextManager().CompleteStop(ctxId, ctx);
return submit_rc;
}
return ORBIS_OK;
@@ -276,7 +294,6 @@ int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* p
InitEventDispatcher();
SetInitialized(true);
g_state.initialized.store(true);
return ORBIS_OK;
}
@@ -290,10 +307,15 @@ int PS4_SYSV_ABI sceNpMatching2Terminate() {
}
SetInitialized(false);
g_state.initialized.store(false);
TermEventDispatcher();
ContextManager::Instance().Reset();
NpHandler::GetInstance().GetMatching2ContextManager().Reset();
{
auto& state = NpHandler::GetInstance().GetMatching2State();
std::lock_guard lock(state.mutex);
state.next_request_id = 1;
}
SignalingHandler::ReleaseContextRef();
return ORBIS_OK;
}
@@ -310,12 +332,16 @@ int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam(
LOG_ERROR(Lib_NpMatching2, "requestOpt null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
LOG_INFO(Lib_NpMatching2, "ctx={} opt={} callback={:#x} arg={} timeout={} appId={}", ctxId,
fmt::ptr(requestOpt), reinterpret_cast<std::uintptr_t>(requestOpt->callback),
fmt::ptr(requestOpt->arg), requestOpt->timeout, requestOpt->appId);
ctx->default_request_callback = requestOpt->callback;
ctx->default_request_callback_arg = requestOpt->arg;
@@ -334,6 +360,11 @@ int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId,
LOG_ERROR(Lib_NpMatching2, "serverId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
*serverId = 1;
@@ -356,7 +387,7 @@ int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -383,7 +414,7 @@ int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId,
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -411,7 +442,7 @@ int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -439,7 +470,7 @@ int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -466,7 +497,7 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -482,7 +513,8 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId,
int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataExternalRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, request = {}, requestOpt = {}, caller = {}",
ctxId, fmt::ptr(request), fmt::ptr(requestOpt), fmt::ptr(__builtin_return_address(0)));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
@@ -493,7 +525,7 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -509,7 +541,8 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(
int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataInternalRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, request = {}, requestOpt = {}, caller = {}",
ctxId, fmt::ptr(request), fmt::ptr(requestOpt), fmt::ptr(__builtin_return_address(0)));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
@@ -520,7 +553,7 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -546,7 +579,7 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -555,8 +588,9 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont
constexpr s32 kInactive = 0;
constexpr s32 kPending = 1;
constexpr s32 kActive = 2;
const auto room_it = ctx->room_cache.find(roomId);
const bool in_room = room_it != ctx->room_cache.end() &&
auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx->ctx_id);
const auto room_it = cache->rooms.find(roomId);
const bool in_room = room_it != cache->rooms.end() &&
room_it->second.members.find(memberId) != room_it->second.members.end();
if (!in_room) {
@@ -567,8 +601,8 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont
return ORBIS_OK;
}
auto peer_it = ctx->peers.find(memberId);
if (peer_it == ctx->peers.end()) {
auto peer_it = cache->peers.find(memberId);
if (peer_it == cache->peers.end()) {
PeerInfo pi{};
pi.member_id = memberId;
pi.status = kPending;
@@ -578,7 +612,7 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont
pi.port = member_it->second.port;
pi.online_id = member_it->second.np_id.handle;
}
peer_it = ctx->peers.emplace(memberId, pi).first;
peer_it = cache->peers.emplace(memberId, pi).first;
}
const s32 status = peer_it->second.status == kActive ? kActive : kPending;
@@ -609,10 +643,11 @@ int PS4_SYSV_ABI sceNpMatching2DestroyContext(OrbisNpMatching2ContextId ctxId) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!ContextManager::Instance().Destroy(ctxId)) {
if (!NpHandler::GetInstance().GetMatching2ContextManager().Destroy(ctxId)) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
SignalingHandler::ReleaseContextRef();
return ORBIS_OK;
}
@@ -625,7 +660,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback(
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -638,13 +673,14 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback(
int PS4_SYSV_ABI sceNpMatching2RegisterRoomMessageCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomMessageCallback callback, void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, callback = {}, userdata = {}", ctxId,
fmt::ptr(callback), userdata);
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -709,7 +745,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataExternalList(
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -736,7 +772,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -746,11 +782,14 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
void* request_data = BuildGetRoomDataInternalPayload(*ctx, request->roomId);
auto request_payload_owner = std::make_shared<CallbackPayload>();
void* request_data =
BuildGetRoomDataInternalPayload(*ctx, *request_payload_owner, request->roomId);
PendingEvent ev{};
ev.type = PendingEvent::REQUEST_CB;
ev.ctx_id = ctxId;
ev.context_owner = ctx;
ev.fire_at = std::chrono::steady_clock::now();
ev.req_id = reqId;
ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_INTERNAL;
@@ -759,6 +798,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
ev.request_cb = request_cb.callback;
ev.request_cb_arg = request_cb.arg;
ev.request_data = request_data;
ev.payload_owner = std::move(request_payload_owner);
ScheduleEvent(std::move(ev));
return ORBIS_OK;
}
@@ -777,7 +817,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList(
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -790,8 +830,49 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList(
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal() {
LOG_INFO(Lib_NpMatching2, "called");
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal(
OrbisNpMatching2ContextId ctxId,
const OrbisNpMatching2GetRoomMemberDataInternalRequest* request,
const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId || (request->attrIdNum > 0 && !request->attrId)) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
auto request_payload_owner = std::make_shared<CallbackPayload>();
void* request_data =
BuildGetRoomMemberDataInternalPayload(*ctx, *request_payload_owner, *request);
PendingEvent ev{};
ev.type = PendingEvent::REQUEST_CB;
ev.ctx_id = ctxId;
ev.context_owner = ctx;
ev.fire_at = std::chrono::steady_clock::now();
ev.req_id = reqId;
ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_INTERNAL_A;
ev.error_code = 0;
const RequestCallbackInfo request_cb = ConsumeRequestCallback(ctx);
ev.request_cb = request_cb.callback;
ev.request_cb_arg = request_cb.arg;
ev.request_data = request_data;
ev.payload_owner = std::move(request_payload_owner);
ScheduleEvent(std::move(ev));
return ORBIS_OK;
}
@@ -810,7 +891,7 @@ int PS4_SYSV_ABI sceNpMatching2GetUserInfoListA(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -838,7 +919,7 @@ int PS4_SYSV_ABI sceNpMatching2GetUserInfoList(OrbisNpMatching2ContextId ctxId,
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -876,7 +957,7 @@ int PS4_SYSV_ABI sceNpMatching2JoinRoomA(OrbisNpMatching2ContextId ctxId,
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -904,7 +985,7 @@ int PS4_SYSV_ABI sceNpMatching2KickoutRoomMember(OrbisNpMatching2ContextId ctxId
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -938,8 +1019,32 @@ int PS4_SYSV_ABI sceNpMatching2SetLobbyMemberDataInternal() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2SetRoomMemberDataInternal() {
LOG_INFO(Lib_NpMatching2, "called");
int PS4_SYSV_ABI sceNpMatching2SetRoomMemberDataInternal(
OrbisNpMatching2ContextId ctxId,
const OrbisNpMatching2SetRoomMemberDataInternalRequest* request,
const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId ||
(request->roomMemberBinAttrInternalNum > 0 && !request->roomMemberBinAttrInternal)) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmSetRoomMemberDataInternal(ctxId, reqId, *request);
return ORBIS_OK;
}
@@ -966,7 +1071,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -978,13 +1083,13 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct
*reqId = request_id;
auto request_payload_owner = std::make_shared<CallbackPayload>();
ctx->request_payload_override = request_payload_owner.get();
void* request_data = BuildSignalingGetPingInfoPayload(*ctx, request->roomId);
ctx->request_payload_override = nullptr;
void* request_data =
BuildSignalingGetPingInfoPayload(*ctx, *request_payload_owner, request->roomId);
PendingEvent ev{};
ev.type = PendingEvent::REQUEST_CB;
ev.ctx_id = ctxId;
ev.context_owner = ctx;
ev.fire_at = std::chrono::steady_clock::now();
ev.req_id = request_id;
ev.req_event = ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO;
@@ -993,7 +1098,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct
ev.request_cb = request_cb.callback;
ev.request_cb_arg = request_cb.arg;
ev.request_data = request_data;
ev.request_payload_owner = std::move(request_payload_owner);
ev.payload_owner = std::move(request_payload_owner);
ScheduleEvent(std::move(ev));
return ORBIS_OK;
}
@@ -1013,7 +1118,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfoA(OrbisNpMatching2Conte
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -1032,7 +1137,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2Contex
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
auto ctx = NpHandler::GetInstance().GetMatching2ContextManager().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
@@ -1041,10 +1146,13 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2Contex
return FillMatching2ConnectionInfo(*ctx, roomId, memberId, infoType, connInfo, false);
}
int PS4_SYSV_ABI sceNpMatching2SignalingGetLocalNetInfo(OrbisNpMatching2ContextId ctxId,
void* info) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
return ORBIS_OK;
int PS4_SYSV_ABI sceNpMatching2SignalingGetLocalNetInfo(void* info) {
LOG_INFO(Lib_NpMatching2, "called");
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
return FillMatching2LocalNetInfo(info);
}
int PS4_SYSV_ABI sceNpMatching2SignalingGetPeerNetInfoResult() {
@@ -12,12 +12,142 @@ class SymbolsResolver;
namespace Libraries::Np::NpMatching2 {
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param);
int PS4_SYSV_ABI sceNpMatching2Terminate();
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,
OrbisNpMatching2ContextId* ctxId);
int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param,
OrbisNpMatching2ContextId* ctxId);
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2CreateJoinRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2JoinRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2CreateJoinRoomRequestA* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCallback callback,
void* userdata);
int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyEventCallback callback, void* userdata);
int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RoomEventCallback callback,
void* userdata);
int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2SignalingCallback callback,
void* userdata);
int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 timeout);
int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId);
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param);
int PS4_SYSV_ABI sceNpMatching2Terminate();
int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RequestOptParam* requestOpt);
int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2ServerId* serverId);
int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2GetWorldInfoListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2LeaveRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2SearchRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2SetUserInfoRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, void* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataExternalRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetRoomDataInternalRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId,
s32* connStatus, void* peerAddr,
u16* peerPort);
int PS4_SYSV_ABI sceNpMatching2AbortContextStart(OrbisNpMatching2ContextId ctxId);
int PS4_SYSV_ABI sceNpMatching2DestroyContext(OrbisNpMatching2ContextId ctxId);
int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyMessageCallback callback, void* userdata);
int PS4_SYSV_ABI sceNpMatching2RegisterRoomMessageCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomMessageCallback callback, void* userdata);
int PS4_SYSV_ABI sceNpMatching2GetMemoryInfo();
int PS4_SYSV_ABI sceNpMatching2GetSslMemoryInfo();
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberIdListLocal();
int PS4_SYSV_ABI sceNpMatching2GetRoomPasswordLocal();
int PS4_SYSV_ABI sceNpMatching2GetSignalingOptParamLocal();
int PS4_SYSV_ABI sceNpMatching2GetLobbyInfoList();
int PS4_SYSV_ABI sceNpMatching2GetLobbyMemberDataInternal();
int PS4_SYSV_ABI sceNpMatching2GetLobbyMemberDataInternalList();
int PS4_SYSV_ABI sceNpMatching2GetRoomDataExternalList(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomDataExternalListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomDataInternalRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataExternalList(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetRoomMemberDataExternalListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2GetRoomMemberDataInternal(
OrbisNpMatching2ContextId ctxId,
const OrbisNpMatching2GetRoomMemberDataInternalRequest* request,
const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2GetUserInfoListA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2GetUserInfoListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2GetUserInfoList(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2GetUserInfoListRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2GrantRoomOwner(OrbisNpMatching2ContextId ctxId, void* reqParam,
void* optParam, s32* reqId);
int PS4_SYSV_ABI sceNpMatching2JoinLobby(OrbisNpMatching2ContextId ctxId, void* reqParam,
void* optParam, s32* reqId);
int PS4_SYSV_ABI sceNpMatching2JoinRoomA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2JoinRoomRequestA* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2KickoutRoomMember(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2KickoutRoomMemberRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2LeaveLobby();
int PS4_SYSV_ABI sceNpMatching2SendLobbyChatMessage();
int PS4_SYSV_ABI sceNpMatching2SendRoomChatMessage(OrbisNpMatching2ContextId ctxId, void* reqParam,
void* optParam, s32* reqId);
int PS4_SYSV_ABI sceNpMatching2SetLobbyMemberDataInternal();
int PS4_SYSV_ABI sceNpMatching2SetRoomMemberDataInternal(
OrbisNpMatching2ContextId ctxId,
const OrbisNpMatching2SetRoomMemberDataInternalRequest* request,
const OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId);
int PS4_SYSV_ABI sceNpMatching2SetSignalingOptParam();
int PS4_SYSV_ABI sceNpMatching2SignalingGetPeerNetInfo();
int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ctxId,
const void* reqParam, const void* optParam,
s32* reqId);
int PS4_SYSV_ABI sceNpMatching2SignalingCancelPeerNetInfo();
int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfoA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId,
u32 infoType, void* connInfo);
int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId,
u32 infoType, void* connInfo);
int PS4_SYSV_ABI sceNpMatching2SignalingGetLocalNetInfo(void* info);
int PS4_SYSV_ABI sceNpMatching2SignalingGetPeerNetInfoResult();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpMatching2
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <map>
#include <mutex>
#include <vector>
#include "common/types.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_types.h"
namespace Libraries::Np::NpMatching2 {
struct PeerInfo {
u32 addr = 0;
u16 port = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
s32 conn_id = 0;
s32 status = 0;
Libraries::Np::OrbisNpOnlineId online_id{};
bool sent_established = false;
u32 ping_us = 0;
};
struct MemberBinCache {
OrbisNpMatching2AttributeId id = 0;
u64 update_date = 0;
std::vector<u8> data;
};
struct MemberCache {
Libraries::Np::OrbisNpId np_id{};
Libraries::Np::OrbisNpAccountId account_id = 0;
Libraries::Np::OrbisNpPlatformType platform = Libraries::Np::OrbisNpPlatformType::None;
u64 join_date = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
OrbisNpMatching2TeamId team_id = 0;
OrbisNpMatching2RoomGroupId group_id = 0;
OrbisNpMatching2NatType nat_type = 0;
OrbisNpMatching2Flags flag_attr = 0;
u32 addr = 0;
u16 port = 0;
std::map<OrbisNpMatching2AttributeId, MemberBinCache> bins;
};
struct RoomCache {
u32 num_slots = 0;
u64 mask_password = 0;
OrbisNpMatching2SignalingType signaling_type = ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH;
OrbisNpMatching2RoomMemberId signaling_main_member = 0;
OrbisNpMatching2ServerId server_id = 0;
OrbisNpMatching2WorldId world_id = 0;
OrbisNpMatching2LobbyId lobby_id = 0;
OrbisNpMatching2RoomId room_id = 0;
u16 max_slot = 0;
u16 public_slots = 0;
u16 private_slots = 0;
u16 open_public_slots = 0;
u16 open_private_slots = 0;
u64 passwd_slot_mask = 0;
u64 joined_slot_mask = 0;
OrbisNpMatching2Flags flags = 0;
std::vector<OrbisNpMatching2RoomBinAttrInternal> bin_attrs_internal;
std::vector<std::vector<u8>> bin_buffers;
std::map<OrbisNpMatching2RoomGroupId, OrbisNpMatching2RoomGroup> groups;
std::map<OrbisNpMatching2RoomMemberId, MemberCache> members;
bool owner = false;
};
struct Matching2ContextCache {
mutable std::recursive_mutex mutex;
std::map<OrbisNpMatching2RoomMemberId, PeerInfo> peers;
std::map<OrbisNpMatching2RoomId, RoomCache> rooms;
void Reset() {
peers.clear();
rooms.clear();
}
};
} // namespace Libraries::Np::NpMatching2
File diff suppressed because it is too large Load Diff
@@ -16,6 +16,7 @@
#include "common/types.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_matching2/np_matching2_cache.h"
#include "core/libraries/np/np_types.h"
namespace shadnet {
@@ -28,69 +29,12 @@ class LeaveRoomReply;
class SearchRoomReply;
} // namespace shadnet
namespace Libraries::Np {
class NpHandler;
}
namespace Libraries::Np::NpMatching2 {
struct PeerInfo {
u32 addr = 0;
u16 port = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
s32 conn_id = 0;
s32 status = 0;
Libraries::Np::OrbisNpOnlineId online_id{};
bool handshake_started = false;
bool sent_check = false;
bool sent_established = false;
u64 nonce = 0;
u32 ping_us = 0;
std::chrono::steady_clock::time_point last_send{};
std::chrono::steady_clock::time_point last_check_send{};
};
struct MemberBinCache {
OrbisNpMatching2AttributeId id = 0;
u64 update_date = 0;
std::vector<u8> data;
};
struct MemberCache {
Libraries::Np::OrbisNpId np_id{};
Libraries::Np::OrbisNpAccountId account_id = 0;
Libraries::Np::OrbisNpPlatformType platform = Libraries::Np::OrbisNpPlatformType::None;
u64 join_date = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
OrbisNpMatching2TeamId team_id = 0;
OrbisNpMatching2RoomGroupId group_id = 0;
OrbisNpMatching2NatType nat_type = 0;
OrbisNpMatching2Flags flag_attr = 0;
u32 addr = 0;
u16 port = 0;
std::map<OrbisNpMatching2AttributeId, MemberBinCache> bins;
};
struct RoomCache {
u32 num_slots = 0;
u64 mask_password = 0;
OrbisNpMatching2SignalingType signaling_type = ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH;
OrbisNpMatching2RoomMemberId signaling_main_member = 0;
OrbisNpMatching2ServerId server_id = 0;
OrbisNpMatching2WorldId world_id = 0;
OrbisNpMatching2LobbyId lobby_id = 0;
OrbisNpMatching2RoomId room_id = 0;
u16 max_slot = 0;
u16 public_slots = 0;
u16 private_slots = 0;
u16 open_public_slots = 0;
u16 open_private_slots = 0;
u64 passwd_slot_mask = 0;
u64 joined_slot_mask = 0;
OrbisNpMatching2Flags flags = 0;
std::vector<OrbisNpMatching2RoomBinAttrInternal> bin_attrs_internal;
std::vector<std::vector<u8>> bin_buffers;
std::map<OrbisNpMatching2RoomGroupId, OrbisNpMatching2RoomGroup> groups;
std::map<OrbisNpMatching2RoomMemberId, MemberCache> members;
bool owner = false;
};
struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2RoomDataInternal> room_data;
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponse> create_join_response;
@@ -106,6 +50,10 @@ struct CallbackPayload {
room_member_data_external_list_response;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataExternalListResponseA>
room_member_data_external_list_response_a;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataInternalResponse>
room_member_data_internal_response;
std::unique_ptr<OrbisNpMatching2GetRoomMemberDataInternalResponseA>
room_member_data_internal_response_a;
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponse> user_info_list_response;
std::unique_ptr<OrbisNpMatching2GetUserInfoListResponseA> user_info_list_response_a;
std::unique_ptr<OrbisNpMatching2GetWorldInfoListResponse> world_info_response;
@@ -120,6 +68,7 @@ struct CallbackPayload {
std::vector<OrbisNpMatching2RoomBinAttrInternal> room_bin_attrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal*> room_bin_attr_ptrs;
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal> member_bin_attrs;
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal*> member_bin_attr_ptrs;
std::vector<OrbisNpMatching2RoomDataExternal> room_data_external;
std::vector<OrbisNpMatching2RoomDataExternalA> room_data_external_a;
std::vector<OrbisNpMatching2UserInfo> user_info;
@@ -136,18 +85,20 @@ struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2RoomMessageInfoA> room_message_info_a;
std::unique_ptr<OrbisNpMatching2RoomMessageDestination> room_message_dst;
std::unique_ptr<Libraries::Np::OrbisNpId> room_message_src_npid;
std::unique_ptr<Libraries::Np::OrbisNpPeerAddressA> room_message_src_addr;
std::unique_ptr<Libraries::Np::OrbisNpOnlineId> room_message_src_online_id;
std::vector<OrbisNpMatching2RoomMemberId> room_message_multicast_members;
std::vector<u8> room_message_data;
void* room_message_callback_data = nullptr;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdate> room_member_update;
std::unique_ptr<OrbisNpMatching2RoomMemberUpdateA> room_member_update_a;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternalUpdate> room_member_data_internal_update;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternalUpdateA>
room_member_data_internal_update_a;
std::unique_ptr<OrbisNpMatching2RoomUpdate> room_update;
std::unique_ptr<OrbisNpMatching2RoomDataInternalUpdate> room_data_internal_update;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_chg_flag_attr;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_prev_flag_attr;
std::unique_ptr<OrbisNpMatching2TeamId> event_chg_team_id;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_chg_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_prev_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternal> event_member;
@@ -171,6 +122,8 @@ struct CallbackPayload {
room_data_external_list_response_a.reset();
room_member_data_external_list_response.reset();
room_member_data_external_list_response_a.reset();
room_member_data_internal_response.reset();
room_member_data_internal_response_a.reset();
user_info_list_response.reset();
user_info_list_response_a.reset();
world_info_response.reset();
@@ -185,6 +138,7 @@ struct CallbackPayload {
room_bin_attrs.clear();
room_bin_attr_ptrs.clear();
member_bin_attrs.clear();
member_bin_attr_ptrs.clear();
room_data_external.clear();
room_data_external_a.clear();
user_info.clear();
@@ -200,17 +154,18 @@ struct CallbackPayload {
room_message_info_a.reset();
room_message_dst.reset();
room_message_src_npid.reset();
room_message_src_addr.reset();
room_message_src_online_id.reset();
room_message_multicast_members.clear();
room_message_data.clear();
room_message_callback_data = nullptr;
room_member_update.reset();
room_member_update_a.reset();
room_member_data_internal_update.reset();
room_member_data_internal_update_a.reset();
room_update.reset();
room_data_internal_update.reset();
event_chg_flag_attr.reset();
event_prev_flag_attr.reset();
event_chg_team_id.reset();
event_chg_passwd_slot_mask.reset();
event_prev_passwd_slot_mask.reset();
event_member.reset();
@@ -219,7 +174,7 @@ struct CallbackPayload {
}
};
struct ContextObject {
struct ContextObject : std::enable_shared_from_this<ContextObject> {
OrbisNpMatching2ContextId ctx_id = 0;
bool started = false;
bool stop_pending = false;
@@ -231,8 +186,6 @@ struct ContextObject {
Libraries::Np::OrbisNpId owner_np_id{};
Libraries::Np::OrbisNpOnlineId online_id{};
u64 handler_registration_generation = 1;
OrbisNpMatching2WorldId world_id = 0;
OrbisNpMatching2LobbyId lobby_id = 0;
OrbisNpMatching2RoomId room_id = 0;
@@ -241,14 +194,6 @@ struct ContextObject {
u32 max_slot = 5;
OrbisNpMatching2Flags flag_attr = 0;
std::map<OrbisNpMatching2RoomMemberId, PeerInfo> peers;
std::map<OrbisNpMatching2RoomId, RoomCache> room_cache;
CallbackPayload request_payload;
CallbackPayload* request_payload_override = nullptr;
CallbackPayload room_event_payload;
OrbisNpMatching2ContextCallback context_callback = nullptr;
void* context_callback_arg = nullptr;
OrbisNpMatching2RequestCallback default_request_callback = nullptr;
@@ -265,47 +210,6 @@ struct ContextObject {
void* lobby_message_callback_arg = nullptr;
OrbisNpMatching2SignalingCallback signaling_callback = nullptr;
void* signaling_callback_arg = nullptr;
void Reset() {
ctx_id = 0;
started = false;
stop_pending = false;
destroy_pending = false;
a_variant = false;
server_id = 1;
service_label = 0;
owner_np_id = {};
online_id = {};
handler_registration_generation = 1;
world_id = 0;
lobby_id = 0;
room_id = 0;
my_member_id = 0;
is_room_owner = false;
max_slot = 5;
flag_attr = 0;
peers.clear();
room_cache.clear();
request_payload.Reset();
request_payload_override = nullptr;
room_event_payload.Reset();
context_callback = nullptr;
context_callback_arg = nullptr;
default_request_callback = nullptr;
default_request_callback_arg = nullptr;
per_request_callback = nullptr;
per_request_callback_arg = nullptr;
room_event_callback = nullptr;
room_event_callback_arg = nullptr;
room_message_callback = nullptr;
room_message_callback_arg = nullptr;
lobby_event_callback = nullptr;
lobby_event_callback_arg = nullptr;
lobby_message_callback = nullptr;
lobby_message_callback_arg = nullptr;
signaling_callback = nullptr;
signaling_callback_arg = nullptr;
}
};
struct RequestCallbackInfo {
@@ -317,15 +221,13 @@ class ContextManager {
public:
static constexpr u32 kMaxContexts = 10;
static ContextManager& Instance();
s32 CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLabel service_label,
OrbisNpMatching2ContextId* out_ctx_id, bool a_variant = false);
bool Check(OrbisNpMatching2ContextId ctx_id);
ContextObject* Get(OrbisNpMatching2ContextId ctx_id);
std::shared_ptr<ContextObject> Get(OrbisNpMatching2ContextId ctx_id);
bool Destroy(OrbisNpMatching2ContextId ctx_id);
void CompleteStop(OrbisNpMatching2ContextId ctx_id);
void CompleteStop(OrbisNpMatching2ContextId ctx_id,
const std::shared_ptr<ContextObject>& expected_context);
s32 Start(OrbisNpMatching2ContextId ctx_id);
s32 Stop(OrbisNpMatching2ContextId ctx_id);
@@ -335,12 +237,13 @@ public:
void Reset();
private:
friend class Libraries::Np::NpHandler;
ContextManager() = default;
ContextObject* GetLocked(OrbisNpMatching2ContextId ctx_id);
std::shared_ptr<ContextObject> GetLocked(OrbisNpMatching2ContextId ctx_id);
std::mutex m_mutex;
std::array<ContextObject, kMaxContexts + 1> m_contexts{};
std::array<bool, kMaxContexts + 1> m_used{};
std::array<std::shared_ptr<ContextObject>, kMaxContexts + 1> m_contexts{};
OrbisNpMatching2ContextId m_next_id = 1;
OrbisNpMatching2ContextCallback m_pending_context_callback = nullptr;
@@ -359,6 +262,7 @@ struct PendingEvent {
};
Type type = CONTEXT_CB;
OrbisNpMatching2ContextId ctx_id = 0;
std::shared_ptr<ContextObject> context_owner;
std::chrono::steady_clock::time_point fire_at;
OrbisNpMatching2Event ctx_event{};
@@ -370,7 +274,7 @@ struct PendingEvent {
OrbisNpMatching2RequestCallback request_cb = nullptr;
void* request_cb_arg = nullptr;
void* request_data = nullptr;
std::shared_ptr<CallbackPayload> request_payload_owner;
std::shared_ptr<CallbackPayload> payload_owner;
OrbisNpMatching2RoomId room_id = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
@@ -402,32 +306,45 @@ struct NpMatching2State {
std::atomic<bool> dispatch_running{false};
};
extern NpMatching2State g_state;
OrbisNpMatching2RequestId AllocRequestId();
bool IsInitialized();
void SetInitialized(bool initialized);
void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt);
RequestCallbackInfo ConsumeRequestCallback(ContextObject* ctx);
void StoreRequestCallback(const std::shared_ptr<ContextObject>& ctx,
const OrbisNpMatching2RequestOptParam* requestOpt);
RequestCallbackInfo ConsumeRequestCallback(const std::shared_ptr<ContextObject>& ctx);
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp);
void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp);
void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp);
void* BuildSearchRoomPayloadA(ContextObject& ctx, const shadnet::SearchRoomReply& resp);
void* BuildGetRoomDataExternalListPayload(ContextObject& ctx,
void* BuildCreateJoinRoomPayload(ContextObject& ctx, CallbackPayload& payload,
const shadnet::CreateJoinRoomResponse& resp);
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, CallbackPayload& payload,
const shadnet::CreateJoinRoomResponse& resp);
void* BuildLeaveRoomPayload(ContextObject& ctx, CallbackPayload& payload,
const shadnet::LeaveRoomReply& resp);
void* BuildGetWorldInfoListPayload(ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetWorldInfoListReply& resp);
void* BuildSearchRoomPayload(ContextObject& ctx, CallbackPayload& payload,
const shadnet::SearchRoomReply& resp);
void* BuildSearchRoomPayloadA(ContextObject& ctx, CallbackPayload& payload,
const shadnet::SearchRoomReply& resp);
void* BuildGetRoomDataExternalListPayload(ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetRoomDataExternalListReply& resp);
void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx,
void* BuildGetRoomDataExternalListPayloadA(ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetRoomDataExternalListReply& resp);
void* BuildGetRoomMemberDataExternalListPayload(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp);
ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetRoomMemberDataExternalListPayloadA(
ContextObject& ctx, const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetUserInfoListPayload(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, const shadnet::GetUserInfoListReply& resp);
void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetRoomMemberDataExternalListReply& resp);
void* BuildGetUserInfoListPayload(ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetUserInfoListReply& resp);
void* BuildGetUserInfoListPayloadA(ContextObject& ctx, CallbackPayload& payload,
const shadnet::GetUserInfoListReply& resp);
void* BuildGetRoomDataInternalPayload(ContextObject& ctx, CallbackPayload& payload,
OrbisNpMatching2RoomId room_id);
void* BuildGetRoomMemberDataInternalPayload(
ContextObject& ctx, CallbackPayload& payload,
const OrbisNpMatching2GetRoomMemberDataInternalRequest& request);
void* BuildRoomMessagePayload(CallbackPayload& p, bool a_variant, OrbisNpMatching2CastType castType,
const std::vector<OrbisNpMatching2RoomMemberId>& dstMembers,
const MemberCache* srcMember, const std::vector<u8>& msg);
File diff suppressed because it is too large Load Diff
@@ -35,6 +35,7 @@ enum class MmCommand : u16 {
GetUserInfoList = 113,
GetRoomMemberDataExternalList = 114,
SendRoomMessage = 115,
SetRoomMemberDataInternal = 116,
};
void SetMmShadNetClient(std::shared_ptr<ShadNet::ShadNetClient> client,
@@ -81,6 +82,8 @@ s32 MmSendRoomMessage(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestI
const OrbisNpMatching2SendRoomMessageRequest& request);
s32 MmSetRoomDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataInternalRequest& request);
s32 MmSetRoomMemberDataInternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomMemberDataInternalRequest& request);
s32 MmSetRoomDataExternal(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2SetRoomDataExternalRequest& request);
s32 MmKickoutRoomMember(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
@@ -2,63 +2,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include "common/logging/log.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/sockets.h"
#include "common/singleton.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_p2p.h"
#include "core/libraries/net/net_util.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_signaling/np_signaling_transport.h"
#include "core/libraries/np/signaling_handler.h"
namespace Libraries::Np::NpMatching2 {
namespace {
constexpr s32 kMatching2ConnInactive = 0;
constexpr s32 kMatching2ConnPending = 1;
constexpr s32 kMatching2ConnActive = 2;
constexpr auto kMatching2HandshakeRetry = std::chrono::milliseconds(350);
constexpr auto kMatching2HandshakeTimeout = std::chrono::seconds(10);
constexpr auto kMatching2StunPingInterval = std::chrono::seconds(5);
std::atomic<bool> g_matching2_stop{false};
std::mutex g_matching2_thread_mutex;
std::thread g_matching2_thread;
enum class Matching2HandshakeKind : u8 {
Offer = 1,
Accept = 2,
Check = 3,
CheckAck = 4,
};
#pragma pack(push, 1)
struct Matching2HandshakePacket {
u8 magic[4] = {'S', 'H', 'A', 'D'};
u8 type = 0x21;
u8 kind = 0;
u64 room_id = 0;
u16 from_member_id = 0;
u16 to_member_id = 0;
u8 online_id_from[16]{};
u32 mapped_addr = 0;
u16 mapped_port = 0;
u16 reserved = 0;
u64 nonce = 0;
};
#pragma pack(pop)
static_assert(sizeof(Matching2HandshakePacket) == 0x32);
#pragma pack(push, 1)
struct Matching2StunPing {
@@ -69,284 +37,27 @@ struct Matching2StunPing {
#pragma pack(pop)
static_assert(sizeof(Matching2StunPing) == 21);
bool HasMatching2Magic(const Matching2HandshakePacket& pkt) {
return pkt.magic[0] == 'S' && pkt.magic[1] == 'H' && pkt.magic[2] == 'A' &&
pkt.magic[3] == 'D' && pkt.type == 0x21;
}
std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) {
char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{};
std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
return std::string(buf);
}
bool ShouldConnectToPeer(const RoomCache& room, OrbisNpMatching2RoomMemberId self,
OrbisNpMatching2RoomMemberId peer) {
if (peer == 0 || peer == self) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR &&
room.signaling_main_member != 0) {
return self == room.signaling_main_member || peer == room.signaling_main_member;
}
return true;
}
bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
if (peer.addr != 0 && peer.port != 0) {
return true;
}
if (member.addr != 0 && member.port != 0) {
peer.addr = member.addr;
peer.port = member.port;
if (peer.online_id.data[0] == 0) {
peer.online_id = member.np_id.handle;
}
return true;
}
const std::string online_id(member.np_id.handle.data);
u32 resolved_addr = 0;
u16 resolved_port = 0;
if (!online_id.empty() && RequestSignalingInfos(online_id, &resolved_addr, &resolved_port)) {
peer.addr = resolved_addr;
peer.port = resolved_port;
}
if (peer.online_id.data[0] == 0) {
peer.online_id = member.np_id.handle;
}
return peer.addr != 0 && peer.port != 0;
}
void MarkMatching2PeerActive(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, u32 addr, u16 port) {
if (member_id == 0 || member_id == ctx.my_member_id) {
return;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (addr != 0) {
peer.addr = addr;
}
if (port != 0) {
peer.port = port;
}
const bool first_active = peer.status != kMatching2ConnActive;
peer.status = kMatching2ConnActive;
peer.handshake_started = true;
if (first_active && !peer.sent_established) {
peer.sent_established = true;
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK);
LOG_INFO(Lib_NpMatching2,
"Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}",
ctx.ctx_id, room_id, member_id, peer.addr, Libraries::Net::sceNetNtohs(peer.port));
}
}
bool SendMatching2Handshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, Matching2HandshakeKind kind,
u64 nonce) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return false;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return false;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (!ResolvePeerEndpoint(member_it->second, peer)) {
LOG_WARNING(Lib_NpMatching2, "Matching2 signaling: unresolved endpoint room={} member={}",
room_id, member_id);
return false;
}
Matching2HandshakePacket pkt{};
pkt.kind = static_cast<u8>(kind);
pkt.room_id = room_id;
pkt.from_member_id = ctx.my_member_id;
pkt.to_member_id = member_id;
std::memcpy(pkt.online_id_from, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
pkt.mapped_addr = Net::GetP2PAdvertisedAddr();
pkt.mapped_port = Net::GetP2PConfiguredPort() != 0
? Libraries::Net::sceNetHtons(Net::GetP2PConfiguredPort())
: 0;
pkt.nonce = nonce;
const int rc = Net::P2PMatching2SendTo(&pkt, sizeof(pkt), peer.addr, peer.port);
const auto now = std::chrono::steady_clock::now();
peer.last_send = now;
if (kind == Matching2HandshakeKind::Check) {
peer.last_check_send = now;
}
LOG_DEBUG(Lib_NpMatching2,
"Matching2 handshake send kind={} ctx={} room={} {}->{} dst={:#x}:{} rc={}",
static_cast<u8>(kind), ctx.ctx_id, room_id, ctx.my_member_id, member_id, peer.addr,
Libraries::Net::sceNetNtohs(peer.port), rc);
return rc >= 0;
}
ContextObject* FindContextForMatching2Packet(const Matching2HandshakePacket& pkt) {
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx || ctx->room_id != pkt.room_id || ctx->my_member_id != pkt.to_member_id) {
continue;
}
if (ctx->room_cache.find(static_cast<OrbisNpMatching2RoomId>(pkt.room_id)) !=
ctx->room_cache.end()) {
return ctx;
}
}
return nullptr;
}
void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port,
const Matching2HandshakePacket& pkt) {
if (!HasMatching2Magic(pkt)) {
return;
}
ContextObject* ctx = FindContextForMatching2Packet(pkt);
if (!ctx) {
LOG_DEBUG(Lib_NpMatching2, "Matching2 handshake: no ctx for room={} to_member={}",
pkt.room_id, pkt.to_member_id);
return;
}
const auto room_id = static_cast<OrbisNpMatching2RoomId>(pkt.room_id);
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(pkt.from_member_id);
auto room_it = ctx->room_cache.find(room_id);
if (room_it == ctx->room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx->my_member_id, member_id)) {
return;
}
PeerInfo& peer = ctx->peers[member_id];
peer.member_id = member_id;
peer.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr;
peer.port = pkt.mapped_port != 0 ? pkt.mapped_port : from_port;
peer.status =
peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending;
peer.handshake_started = true;
SetNpOnlineId(peer.online_id,
std::string_view(reinterpret_cast<const char*>(pkt.online_id_from),
ORBIS_NP_ONLINEID_MAX_LENGTH));
const auto kind = static_cast<Matching2HandshakeKind>(pkt.kind);
switch (kind) {
case Matching2HandshakeKind::Offer:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Accept, 0);
break;
case Matching2HandshakeKind::Accept:
peer.sent_check = true;
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Check, peer.nonce);
break;
case Matching2HandshakeKind::Check:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::CheckAck,
pkt.nonce);
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
break;
case Matching2HandshakeKind::CheckAck:
if (pkt.nonce == 0 || pkt.nonce == peer.nonce) {
if (peer.last_check_send.time_since_epoch().count() != 0) {
const auto rtt = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - peer.last_check_send);
peer.ping_us =
static_cast<u32>(std::min<u64>(static_cast<u64>(std::max<s64>(0, rtt.count())),
std::numeric_limits<u32>::max()));
peer.last_check_send = {};
}
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
}
break;
default:
break;
}
}
void Matching2HandshakeThreadMain() {
auto last_stun_ping = std::chrono::steady_clock::time_point{};
while (!g_matching2_stop.load(std::memory_order_relaxed)) {
Matching2HandshakePacket pkt{};
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Net::P2PMatching2RecvFrom(&pkt, sizeof(pkt), &from_addr, &from_port);
if (rc == sizeof(pkt)) {
HandleMatching2HandshakePacket(from_addr, from_port, pkt);
}
const auto now = std::chrono::steady_clock::now();
const bool should_stun_ping = last_stun_ping.time_since_epoch().count() == 0 ||
now - last_stun_ping >= kMatching2StunPingInterval;
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx) {
continue;
}
if (should_stun_ping) {
SendMatching2StunPing(*ctx);
}
if (ctx->room_id == 0) {
continue;
}
auto room_it = ctx->room_cache.find(ctx->room_id);
if (room_it == ctx->room_cache.end()) {
continue;
}
for (auto& [member_id, peer] : ctx->peers) {
if (peer.status != kMatching2ConnPending || !peer.handshake_started) {
continue;
}
if (peer.last_send.time_since_epoch().count() != 0 &&
now - peer.last_send > kMatching2HandshakeTimeout) {
peer.status = kMatching2ConnInactive;
QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT);
continue;
}
if (peer.last_send.time_since_epoch().count() == 0 ||
now - peer.last_send >= kMatching2HandshakeRetry) {
SendMatching2Handshake(*ctx, ctx->room_id, member_id,
peer.sent_check ? Matching2HandshakeKind::Check
: Matching2HandshakeKind::Offer,
peer.sent_check ? peer.nonce : 0);
}
}
}
if (should_stun_ping) {
last_stun_ping = now;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
} // namespace
bool SendMatching2StunPing(const ContextObject& ctx) {
if (!NpSignaling::Stubs::Matching2Enabled()) {
if (!NpSignaling::Transport::Matching2Enabled()) {
return false;
}
if (!ctx.started || ctx.online_id.data[0] == '\0') {
return false;
}
if (!NpSignaling::Stubs::EnsureTransport()) {
if (!NpSignaling::Transport::EnsureTransport()) {
return false;
}
const u32 server_addr = NpSignaling::Stubs::MmServerAddr();
const u16 server_udp = NpSignaling::Stubs::MmServerUdpPort();
const u32 server_addr = NpSignaling::Transport::MmServerAddr();
const u16 server_udp = NpSignaling::Transport::MmServerUdpPort();
if (server_addr == 0 || server_udp == 0) {
return false;
}
@@ -354,10 +65,10 @@ bool SendMatching2StunPing(const ContextObject& ctx) {
Matching2StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = NpSignaling::Stubs::AdvertisedAddr();
ping.local_ip = NpSignaling::Transport::AdvertisedAddr();
const int rc =
NpSignaling::Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp);
NpSignaling::Transport::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp);
LOG_DEBUG(Lib_NpMatching2,
"Matching2 STUN ping: ctx={} online_id='{}' server={:#x}:{} local_ip={:#x} rc={}",
ctx.ctx_id, OnlineIdToString(ctx.online_id), server_addr,
@@ -368,9 +79,17 @@ bool SendMatching2StunPing(const ContextObject& ctx) {
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id,
OrbisNpMatching2Event event, s32 error_code) {
if (!ctx.signaling_callback) {
LOG_ERROR(Lib_NpMatching2,
"Event skipped: ctx={} room={} member={} event={:#x} no callback", ctx.ctx_id,
room_id, member_id, static_cast<u16>(event));
return;
}
PendingEvent ev{};
ev.type = PendingEvent::SIGNALING_CB;
ev.ctx_id = ctx.ctx_id;
ev.context_owner = ctx.shared_from_this();
ev.fire_at = std::chrono::steady_clock::now();
ev.room_id = room_id;
ev.member_id = member_id;
@@ -379,95 +98,50 @@ void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId roo
ScheduleEvent(std::move(ev));
}
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx.my_member_id, member_id)) {
return;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return;
}
Net::EnsureP2PTransport();
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (peer.status == kMatching2ConnActive) {
return;
}
ResolvePeerEndpoint(member_it->second, peer);
peer.status = kMatching2ConnPending;
peer.handshake_started = true;
peer.sent_check = false;
if (peer.nonce == 0) {
peer.nonce = static_cast<u64>(std::chrono::steady_clock::now().time_since_epoch().count() ^
(static_cast<u64>(ctx.ctx_id) << 48) ^
(static_cast<u64>(member_id) << 16));
}
SendMatching2Handshake(ctx, room_id, member_id, Matching2HandshakeKind::Offer, 0);
}
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id) {
const auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
StartMatching2PeerHandshake(ctx, room_id, member_id);
}
}
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
std::vector<OrbisNpMatching2RoomMemberId> dead_members;
{
auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id);
const auto room_it = cache->rooms.find(room_id);
if (room_it == cache->rooms.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == 0 || member_id == ctx.my_member_id) {
continue;
}
const auto peer_it = cache->peers.find(member_id);
if (peer_it != cache->peers.end()) {
peer_it->second.status = kMatching2ConnInactive;
}
dead_members.push_back(member_id);
}
}
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == 0 || member_id == ctx.my_member_id) {
continue;
}
auto peer_it = ctx.peers.find(member_id);
if (peer_it != ctx.peers.end()) {
peer_it->second.status = kMatching2ConnInactive;
peer_it->second.handshake_started = false;
peer_it->second.sent_check = false;
}
for (const auto member_id : dead_members) {
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, error_code);
LOG_INFO(Lib_NpMatching2, "Matching2 signaling dead: ctx={} room={} member={} reason={:#x}",
ctx.ctx_id, room_id, member_id, static_cast<u32>(error_code));
}
SignalingHandler::StopMatching2(ctx, room_id, error_code);
}
void StartMatching2HandshakeThread() {
std::lock_guard lock(g_matching2_thread_mutex);
if (g_matching2_thread.joinable()) {
return;
}
g_matching2_stop.store(false, std::memory_order_relaxed);
g_matching2_thread = std::thread(Matching2HandshakeThreadMain);
void StartMatching2SignalingRuntime() {
SignalingHandler::Start();
}
void StopMatching2HandshakeThread() {
{
std::lock_guard lock(g_matching2_thread_mutex);
g_matching2_stop.store(true, std::memory_order_relaxed);
}
if (g_matching2_thread.joinable()) {
g_matching2_thread.join();
}
void StopMatching2SignalingRuntime() {
SignalingHandler::Stop();
}
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id);
const auto room_it = cache->rooms.find(roomId);
if (room_it == cache->rooms.end()) {
return 0;
}
@@ -477,8 +151,8 @@ u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
if (member_id == ctx.my_member_id) {
continue;
}
const auto peer_it = ctx.peers.find(member_id);
if (peer_it == ctx.peers.end() || peer_it->second.ping_us == 0) {
const auto peer_it = cache->peers.find(member_id);
if (peer_it == cache->peers.end() || peer_it->second.ping_us == 0) {
continue;
}
total_ping += peer_it->second.ping_us;
@@ -488,16 +162,16 @@ u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
return ping_count == 0 ? 0 : static_cast<u32>(total_ping / ping_count);
}
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
CallbackPayload& p =
ctx.request_payload_override ? *ctx.request_payload_override : ctx.request_payload;
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, CallbackPayload& p,
OrbisNpMatching2RoomId roomId) {
p.Reset();
const auto room_it = ctx.room_cache.find(roomId);
auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id);
const auto room_it = cache->rooms.find(roomId);
p.ping_info_response = std::make_unique<OrbisNpMatching2SignalingGetPingInfoResponse>();
auto& out = *p.ping_info_response;
out = {};
if (room_it != ctx.room_cache.end()) {
if (room_it != cache->rooms.end()) {
out.serverId = room_it->second.server_id;
out.worldId = room_it->second.world_id;
out.roomId = room_it->second.room_id;
@@ -512,6 +186,16 @@ void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomI
return p.request_data;
}
// Our own address combined with the external IP from the STUN echo plus our P2P port
static void FillMappedAddr(OrbisNpMatching2SignalingConnectionInfoAddr& out) {
u32 addr = Common::Singleton<NetUtil::NetUtilInternal>::Instance()->GetExternalIp();
if (addr == 0) {
addr = Net::GetP2PAdvertisedAddr();
}
out.addr = addr;
out.port = Net::sceNetHtons(Net::GetP2PAdvertisedPort());
}
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
bool a_variant) {
@@ -520,8 +204,9 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
auto cache = NpHandler::GetInstance().LockMatching2Cache(ctx.ctx_id);
const auto room_it = cache->rooms.find(roomId);
if (room_it == cache->rooms.end()) {
LOG_INFO(Lib_NpMatching2, "room={} not cached for connection info", roomId);
if (a_variant) {
*static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo) = {};
@@ -534,8 +219,8 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId
const auto member_it = room_it->second.members.find(memberId);
const MemberCache* member =
member_it != room_it->second.members.end() ? &member_it->second : nullptr;
const auto peer_it = ctx.peers.find(memberId);
const PeerInfo* peer = peer_it != ctx.peers.end() ? &peer_it->second : nullptr;
const auto peer_it = cache->peers.find(memberId);
const PeerInfo* peer = peer_it != cache->peers.end() ? &peer_it->second : nullptr;
if (a_variant) {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo);
@@ -547,8 +232,10 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
FillMappedAddr(out->address);
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
@@ -586,8 +273,10 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId
out->npId = member->np_id;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
FillMappedAddr(out->address);
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
@@ -611,4 +300,39 @@ s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId
return ORBIS_OK;
}
s32 FillMatching2LocalNetInfo(void* info) {
auto* out = static_cast<NpSignaling::OrbisNpSignalingNetInfo*>(info);
if (!out || out->size != sizeof(NpSignaling::OrbisNpSignalingNetInfo)) {
LOG_ERROR(Lib_NpMatching2, "bad net info (size {})", out ? out->size : 0);
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
// GetIp() is empty until something asks for it. RetrieveIp() connects out, so only once.
if (netinfo->GetIp().empty()) {
netinfo->RetrieveIp();
}
out->localAddr = NpSignaling::ParseIpv4Nbo(netinfo->GetIp());
if (out->localAddr == 0) {
out->localAddr = NpSignaling::ParseIpv4Nbo("127.0.0.1");
}
out->mappedAddr = 0;
out->natStatus = 0;
NetCtl::OrbisNetCtlNatInfo nat_info{};
nat_info.size = sizeof(nat_info);
if (NetCtl::sceNetCtlGetNatInfo(&nat_info) >= 0) {
out->mappedAddr = nat_info.mapped_addr;
out->natStatus = nat_info.nat_type;
}
if (out->mappedAddr == 0) {
const u32 external = netinfo->GetExternalIp();
out->mappedAddr = external != 0 ? external : out->localAddr;
}
out->_pad_14 = 0;
LOG_INFO(Lib_NpMatching2, "localAddr={:#x} mappedAddr={:#x} natStatus={}", out->localAddr,
out->mappedAddr, out->natStatus);
return ORBIS_OK;
}
} // namespace Libraries::Np::NpMatching2
@@ -9,15 +9,13 @@
namespace Libraries::Np::NpMatching2 {
struct ContextObject;
struct CallbackPayload;
void StartMatching2HandshakeThread();
void StopMatching2HandshakeThread();
void StartMatching2SignalingRuntime();
void StopMatching2SignalingRuntime();
bool SendMatching2StunPing(const ContextObject& ctx);
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id);
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code);
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
@@ -25,9 +23,11 @@ void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId roo
OrbisNpMatching2Event event, s32 error_code);
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId);
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId);
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, CallbackPayload& payload,
OrbisNpMatching2RoomId roomId);
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
bool a_variant);
s32 FillMatching2LocalNetInfo(void* info);
} // namespace Libraries::Np::NpMatching2
@@ -2,14 +2,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <string>
#include <vector>
#include <fmt/format.h>
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/net_util.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_util.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_handler.h"
@@ -17,7 +17,8 @@
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
#include "core/libraries/np/np_signaling/np_signaling_transport.h"
#include "core/libraries/np/signaling_handler.h"
namespace Libraries::Np::NpSignaling {
@@ -25,10 +26,9 @@ using Libraries::Net::sceNetNtohs;
s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s32 cpuAffinityMask,
s64 threadStackSize) {
if (g_initialized) {
if (NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_ALREADY_INITIALIZED;
}
InitSignalingMutex();
SignalingMutexGuard lock;
u32 app_type_4 = 0;
@@ -67,63 +67,49 @@ s32 PS4_SYSV_ABI sceNpSignalingInitialize(s64 memorySize, s32 threadPriority, s3
return check_app_type_rc;
}
g_initialized = true;
NpHandler::GetInstance().GetSignalingState().initialized = true;
const s32 rc = Helpers::StartMainRuntime(threadPriority, cpuAffinityMask, threadStackSize);
if (rc < 0) {
g_initialized = false;
NpHandler::GetInstance().GetSignalingState().initialized = false;
Helpers::ShutdownSignalingHeap();
return rc;
}
const s32 echo_rc = Helpers::StartEchoRuntime(threadPriority, cpuAffinityMask);
if (echo_rc < 0) {
g_initialized = false;
NpHandler::GetInstance().GetSignalingState().initialized = false;
Helpers::ShutdownRuntime();
Helpers::ShutdownSignalingHeap();
return echo_rc;
}
const s32 callout_rc = NpCommon::sceNpCalloutInitCtx(
&g_callout_ctx, "SceNpSignalingCallout", static_cast<u64>(threadStackSize), threadPriority,
static_cast<u64>(cpuAffinityMask));
if (callout_rc < 0) {
g_initialized = false;
Helpers::ShutdownRuntime();
Helpers::ShutdownSignalingHeap();
return callout_rc;
}
g_callout_ctx_active = true;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingTerminate() {
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
{
SignalingMutexGuard lock;
g_initialized = false;
NpHandler::GetInstance().GetSignalingState().initialized = false;
}
Helpers::ShutdownRuntime();
if (g_callout_ctx_active) {
NpCommon::sceNpCalloutTermCtx(&g_callout_ctx);
g_callout_ctx_active = false;
}
Helpers::ShutdownSignalingHeap();
size_t context_count = 0;
{
SignalingMutexGuard lock;
g_contexts.clear();
g_connections.clear();
g_npid_to_conn.clear();
g_peer_netinfo_results.clear();
auto& state = NpHandler::GetInstance().GetSignalingState();
context_count = state.contexts.size();
state.ResetSig1Data();
}
for (size_t i = 0; i < context_count; ++i) {
SignalingHandler::ReleaseContextRef();
}
DestroySignalingMutex();
LOG_INFO(Lib_NpSignaling, "cleared all state");
return ORBIS_OK;
@@ -133,7 +119,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
OrbisNpSignalingContextId* outContextId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
@@ -147,12 +133,12 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const bool transport_ready = Stubs::EnsureTransport();
const bool transport_ready = Transport::EnsureTransport();
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
@@ -161,7 +147,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
return ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE;
}
NpSignalingContext& ctx = g_contexts[ctx_id];
NpSignalingContext& ctx = NpHandler::GetInstance().GetSignalingState().contexts[ctx_id];
ctx.callback = reinterpret_cast<OrbisNpSignalingHandler>(callback);
ctx.callback_arg = callbackArg;
ctx.active = true;
@@ -171,7 +157,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
ctx.compiled_sdk_version = CaptureCompiledSdkVersion();
ctx.activate_budget_us = kActivateBudgetMaxUs;
ctx.activate_last_update_us = NowUs();
ctx.bound_port = Stubs::ConfiguredPort();
ctx.bound_port = Transport::ConfiguredPort();
*outContextId = ctx_id;
LOG_INFO(Lib_NpSignaling, "ctxId={} owner='{}' callback={} arg={} sdk={:#x} p2p_port={}",
@@ -179,9 +165,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContext(const void* npId, void* callback, v
fmt::ptr(callbackArg), ctx.compiled_sdk_version, ctx.bound_port);
}
if (transport_ready) {
SendStunPing(ctx_id);
}
SignalingHandler::AddContextRef();
return ORBIS_OK;
}
@@ -189,7 +173,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void*
OrbisNpSignalingContextId* outContextId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
@@ -202,15 +186,21 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void*
if (npid_rc < 0) {
return npid_rc;
}
// Account id isn't needed without matchmaking wired up; leave it 0 for now.
const OrbisNpAccountId account_id = 0;
OrbisNpAccountId account_id = 0;
const s32 account_rc = NpManager::sceNpGetAccountIdA(userId, &account_id);
if (account_rc < 0) {
return account_rc;
}
if (account_id == 0) {
return ORBIS_NP_ERROR_USER_NOT_FOUND;
}
const bool transport_ready = Stubs::EnsureTransport();
const bool transport_ready = Transport::EnsureTransport();
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
@@ -219,7 +209,7 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void*
return ORBIS_NP_SIGNALING_ERROR_CTXID_NOT_AVAILABLE;
}
NpSignalingContext& ctx = g_contexts[ctx_id];
NpSignalingContext& ctx = NpHandler::GetInstance().GetSignalingState().contexts[ctx_id];
ctx.callback = reinterpret_cast<OrbisNpSignalingHandler>(callback);
ctx.callback_arg = callbackArg;
ctx.active = true;
@@ -231,16 +221,14 @@ s32 PS4_SYSV_ABI sceNpSignalingCreateContextA(s32 userId, void* callback, void*
ctx.compiled_sdk_version = CaptureCompiledSdkVersion();
ctx.activate_budget_us = kActivateBudgetMaxUs;
ctx.activate_last_update_us = NowUs();
ctx.bound_port = Stubs::ConfiguredPort();
ctx.bound_port = Transport::ConfiguredPort();
*outContextId = ctx_id;
LOG_INFO(Lib_NpSignaling, "ctxId={} userId={} owner='{}' accountId={:#x}", ctx_id, userId,
OnlineIdToString(ctx.owner_online_id), account_id);
}
if (transport_ready) {
SendStunPing(ctx_id);
}
SignalingHandler::AddContextRef();
return ORBIS_OK;
}
@@ -248,13 +236,13 @@ s32 PS4_SYSV_ABI sceNpSignalingDeleteContext(OrbisNpSignalingContextId ctxId) {
std::vector<s32> active_conns;
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
LOG_INFO(Lib_NpSignaling, "ctxId={}", ctxId);
for (const auto& [cid, ci] : g_connections) {
for (const auto& [cid, ci] : NpHandler::GetInstance().GetSignalingState().connections) {
if (ci.ctx_id == ctxId && ci.status != ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE) {
active_conns.push_back(cid);
}
@@ -262,148 +250,91 @@ s32 PS4_SYSV_ABI sceNpSignalingDeleteContext(OrbisNpSignalingContextId ctxId) {
}
for (const s32 cid : active_conns) {
CloseConnectionAndDispatchDead(cid, ORBIS_NP_SIGNALING_ERROR_TERMINATED_BY_PEER);
SignalingHandler::TerminateSig1(cid);
}
{
SignalingMutexGuard lock;
RemoveContextConnectionsLocked(ctxId);
for (auto it = g_peer_netinfo_results.begin(); it != g_peer_netinfo_results.end();) {
it = (it->second.ctx_id == ctxId) ? g_peer_netinfo_results.erase(it) : std::next(it);
for (auto it = NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.begin();
it != NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.end();) {
it = (it->second.ctx_id == ctxId)
? NpHandler::GetInstance().GetSignalingState().peer_netinfo_results.erase(it)
: std::next(it);
}
g_contexts.erase(ctxId);
NpHandler::GetInstance().GetSignalingState().contexts.erase(ctxId);
}
SignalingHandler::ReleaseContextRef();
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpSignalingActivateConnection(OrbisNpSignalingContextId ctxId,
const void* peerNpId,
OrbisNpSignalingConnectionId* outConnId) {
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} peerNpId={:p} connId={:p}", NowMs(), ctxId, peerNpId,
fmt::ptr(outConnId));
const s64 call_time = NowMs();
const void* caller = __builtin_return_address(0);
const void* frame = __builtin_frame_address(0);
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} peerNpId={:p} outConnId={:p}", call_time, ctxId,
peerNpId, fmt::ptr(outConnId));
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
LOG_INFO(Lib_NpSignaling,
"result: t={} ctxId={} peer='<unresolved>' "
"rc={:#x}",
call_time, ctxId, ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED);
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
if (!peerNpId || !outConnId) {
LOG_INFO(Lib_NpSignaling,
"result: t={} ctxId={} peer='<unresolved>' "
"rc={:#x}",
call_time, ctxId, ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT);
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
OrbisNpId peer_npid{};
OrbisNpOnlineId peer_online_id{};
if (NormalizeNpId(peerNpId, &peer_npid, &peer_online_id) != ORBIS_OK) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
const s32 normalize_rc = NormalizeNpId(peerNpId, &peer_npid, &peer_online_id);
if (normalize_rc != ORBIS_OK) {
Helpers::LogInvalidActivationContext(call_time, peerNpId, outConnId, caller, frame);
LOG_INFO(Lib_NpSignaling,
"result: t={} ctxId={} peer='<invalid>' "
"rc={:#x}",
call_time, ctxId, normalize_rc);
return normalize_rc;
}
const std::string peer_online_id_str = OnlineIdToString(peer_online_id);
s32 cid = 0;
bool reused_established = false;
bool queue_activation = false;
bool start_handshake = true;
{
SignalingMutexGuard lock;
if (!g_initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (std::memcmp(&ctx_it->second.owner_npid, &peer_npid, sizeof(peer_npid)) == 0) {
return ORBIS_NP_SIGNALING_ERROR_OWN_NP_ID;
}
if (!ConsumeActivationBudgetGatedLocked(ctx_it->second)) {
return ORBIS_NP_SIGNALING_ERROR_EXCEED_RATE_LIMIT;
}
const CtxNpIdKey lookup_key = MakeCtxNpIdKey(ctxId, peer_npid);
const auto existing_it = g_npid_to_conn.find(lookup_key);
if (existing_it != g_npid_to_conn.end()) {
const auto conn_it = g_connections.find(existing_it->second);
if (conn_it != g_connections.end() && conn_it->second.state == ConnState::Inactive) {
RemoveConnectionLocked(existing_it->second);
} else if (conn_it != g_connections.end()) {
cid = existing_it->second;
ConnectionInfo& ci = conn_it->second;
const bool was_locally_activated = ci.locally_activated;
ci.locally_activated = true;
if (ci.state == ConnState::Established) {
ClearLingerAndTimeoutLocked(cid);
reused_established = true;
start_handshake = false;
} else {
ClearLingerAndTimeoutLocked(cid);
start_handshake = true;
}
if (!was_locally_activated) {
queue_activation = true;
}
}
}
bool created_new = false;
if (cid == 0) {
cid = AllocateConnectionIdLocked();
if (cid < 0) {
return ORBIS_NP_SIGNALING_ERROR_OUT_OF_MEMORY;
}
ConnectionInfo ci{};
ci.conn_id = cid;
ci.ctx_id = ctxId;
ci.state = ConnState::SendingOffer;
ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING;
ci.is_initiator = true;
ci.locally_activated = true;
ci.npid = peer_npid;
ci.online_id = peer_online_id;
g_connections[cid] = std::move(ci);
g_npid_to_conn[lookup_key] = cid;
created_new = true;
}
if (created_new) {
ArmConnectTimeoutLocked(cid);
queue_activation = true;
start_handshake = true;
}
if (queue_activation) {
QueueActivationLocked(cid, peer_online_id_str, start_handshake);
}
LOG_INFO(Lib_NpSignaling, "ctxId={} peer='{}' connId={} Status: {}", ctxId,
peer_online_id_str, cid,
created_new ? "queued, async"
: reused_established ? "queued local activation on established connection"
: queue_activation ? "queued local activation on existing connection"
: "reused transient");
const s32 rc = SignalingHandler::ActivateSig1(ctxId, peer_npid, peer_online_id, outConnId);
if (rc == ORBIS_OK) {
LOG_INFO(Lib_NpSignaling,
"result: t={} ctxId={} peer='{}' rc={:#x} "
"connId={}",
call_time, ctxId, peer_online_id_str, rc, *outConnId);
} else {
LOG_INFO(Lib_NpSignaling, "result: t={} ctxId={} peer='{}' rc={:#x}", call_time, ctxId,
peer_online_id_str, rc);
}
*outConnId = cid;
if (queue_activation) {
g_dispatch_cv.notify_all();
return ORBIS_OK;
}
if (reused_established) {
EstablishConnection(cid, false);
}
return ORBIS_OK;
return rc;
}
s32 PS4_SYSV_ABI sceNpSignalingActivateConnectionA(
OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr,
OrbisNpSignalingConnectionId* outConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerAddr || peerAddr->accountId == 0 || !outConnId || peerAddr->platformType == 0) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId);
if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() ||
!ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if ((ctx_it->second.flags & 0x4) != 0 && ctx_it->second.account_id == peerAddr->accountId &&
@@ -422,7 +353,7 @@ s32 PS4_SYSV_ABI sceNpSignalingActivateConnectionA(
ci.conn_id = cid;
ci.ctx_id = ctxId;
ci.status = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING;
g_connections[cid] = std::move(ci);
NpHandler::GetInstance().GetSignalingState().connections[cid] = std::move(ci);
*outConnId = cid;
LOG_INFO(Lib_NpSignaling, "ctxId={} accountId={:#x} platform={} connId={}", ctxId,
@@ -434,22 +365,23 @@ s32 PS4_SYSV_ABI sceNpSignalingDeactivateConnection(OrbisNpSignalingContextId ct
OrbisNpSignalingConnectionId connId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_connections.find(connId);
if (it == g_connections.end() || it->second.ctx_id != ctxId) {
const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(connId);
if (it == NpHandler::GetInstance().GetSignalingState().connections.end() ||
it->second.ctx_id != ctxId) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
LOG_INFO(Lib_NpSignaling, "t={} ctxId={} connId={} peer='{}' status={}", NowMs(), ctxId,
connId, OnlineIdToString(it->second.online_id), it->second.status);
}
DeactivateConnectionFaithful(connId);
SignalingHandler::DeactivateSig1(connId);
return ORBIS_OK;
}
@@ -457,19 +389,20 @@ s32 PS4_SYSV_ABI sceNpSignalingTerminateConnection(OrbisNpSignalingContextId ctx
OrbisNpSignalingConnectionId connId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_connections.find(connId);
if (it == g_connections.end() || it->second.ctx_id != ctxId) {
const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(connId);
if (it == NpHandler::GetInstance().GetSignalingState().connections.end() ||
it->second.ctx_id != ctxId) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
LOG_INFO(Lib_NpSignaling, "ctxId={} connId={}", ctxId, connId);
}
TerminateConnectionFaithful(connId);
SignalingHandler::TerminateSig1(connId);
{
SignalingMutexGuard lock;
RemoveConnectionLocked(connId);
@@ -482,7 +415,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatus(OrbisNpSignalingContextId ctx
u32* outStatus, u32* outPeerAddr,
u16* outPeerPort) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outStatus) {
@@ -492,20 +425,27 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionStatus(OrbisNpSignalingContextId ctx
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_connections.find(connId);
if (it == g_connections.end() || it->second.ctx_id != ctxId) {
const auto it = NpHandler::GetInstance().GetSignalingState().connections.find(connId);
if (it == NpHandler::GetInstance().GetSignalingState().connections.end() ||
it->second.ctx_id != ctxId) {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
return ORBIS_OK;
}
const auto transport_it =
NpHandler::GetInstance().GetSignalingState().peer_transports.find(it->second.transport_id);
const auto* transport =
transport_it != NpHandler::GetInstance().GetSignalingState().peer_transports.end()
? &transport_it->second
: nullptr;
const s32 state = ConnectionStateFromStatus(it->second.status);
if (state == 10) {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_ACTIVE;
if (outPeerAddr) {
*outPeerAddr = it->second.addr;
if (outPeerAddr && transport) {
*outPeerAddr = transport->addr;
}
if (outPeerPort) {
*outPeerPort = it->second.port;
if (outPeerPort && transport) {
*outPeerPort = transport->port;
}
} else if (state != 0) {
*outStatus = ORBIS_NP_SIGNALING_CONN_STATUS_PENDING;
@@ -522,7 +462,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfo(OrbisNpSignalingContextId ctxId
OrbisNpSignalingConnectionId connId, s32 infoCode,
void* outInfo) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outInfo) {
@@ -538,7 +478,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionInfoA(OrbisNpSignalingContextId ctxI
OrbisNpSignalingConnectionId connId, s32 infoCode,
void* outInfo) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outInfo) {
@@ -555,7 +495,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromNpId(OrbisNpSignalingContextId c
OrbisNpSignalingConnectionId* outConnId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
@@ -570,19 +510,20 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromNpId(OrbisNpSignalingContextId c
}
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
const auto it = g_npid_to_conn.find(MakeCtxNpIdKey(ctxId, peer_npid));
if (it == g_npid_to_conn.end()) {
const auto it = NpHandler::GetInstance().GetSignalingState().npid_to_conn.find(
MakeCtxNpIdKey(ctxId, peer_npid));
if (it == NpHandler::GetInstance().GetSignalingState().npid_to_conn.end()) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
const auto conn_it = g_connections.find(it->second);
if (conn_it == g_connections.end()) {
const auto conn_it = NpHandler::GetInstance().GetSignalingState().connections.find(it->second);
if (conn_it == NpHandler::GetInstance().GetSignalingState().connections.end()) {
return ORBIS_NP_SIGNALING_ERROR_CONN_NOT_FOUND;
}
@@ -596,7 +537,7 @@ s32 PS4_SYSV_ABI
sceNpSignalingGetConnectionFromPeerAddress(OrbisNpSignalingContextId ctxId, u32 peerAddr,
u16 peerPort, OrbisNpSignalingConnectionId* outConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outConnId) {
@@ -606,9 +547,12 @@ sceNpSignalingGetConnectionFromPeerAddress(OrbisNpSignalingContextId ctxId, u32
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
for (const auto& [id, ci] : g_connections) {
if (ci.ctx_id == ctxId && ci.addr == peerAddr && ci.port == peerPort &&
ConnectionStateFromStatus(ci.status) == 10) {
for (const auto& [id, ci] : NpHandler::GetInstance().GetSignalingState().connections) {
const auto transport_it =
NpHandler::GetInstance().GetSignalingState().peer_transports.find(ci.transport_id);
if (transport_it != NpHandler::GetInstance().GetSignalingState().peer_transports.end() &&
ci.ctx_id == ctxId && transport_it->second.addr == peerAddr &&
transport_it->second.port == peerPort && ConnectionStateFromStatus(ci.status) == 10) {
*outConnId = id;
LOG_INFO(Lib_NpSignaling, "ctxId={} addr={:#x} port={} connId={}", ctxId, peerAddr,
sceNetNtohs(peerPort), id);
@@ -622,14 +566,15 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddressA(
OrbisNpSignalingContextId ctxId, const OrbisNpSignalingAccountPlatformPair* peerAddr,
OrbisNpSignalingConnectionId* outConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerAddr || !outConnId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId);
if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() ||
!ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if ((ctx_it->second.flags & 0x4) == 0) {
@@ -642,11 +587,11 @@ s32 PS4_SYSV_ABI sceNpSignalingGetConnectionFromPeerAddressA(
s32 PS4_SYSV_ABI sceNpSignalingSetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId,
s32 optionValue) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
const auto it = g_contexts.find(ctxId);
if (it == g_contexts.end() || !it->second.active) {
const auto it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId);
if (it == NpHandler::GetInstance().GetSignalingState().contexts.end() || !it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (optionId != ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG) {
@@ -665,14 +610,14 @@ s32 PS4_SYSV_ABI sceNpSignalingSetContextOption(OrbisNpSignalingContextId ctxId,
s32 PS4_SYSV_ABI sceNpSignalingGetContextOption(OrbisNpSignalingContextId ctxId, s32 optionId,
s32* outOptionValue) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!outOptionValue) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto it = g_contexts.find(ctxId);
if (it == g_contexts.end() || !it->second.active) {
const auto it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId);
if (it == NpHandler::GetInstance().GetSignalingState().contexts.end() || !it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if (optionId != ORBIS_NP_SIGNALING_CONTEXT_OPTION_FLAG) {
@@ -686,7 +631,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetLocalNetInfo(OrbisNpSignalingContextId ctxId,
OrbisNpSignalingNetInfo* info) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
@@ -698,7 +643,14 @@ s32 PS4_SYSV_ABI sceNpSignalingGetLocalNetInfo(OrbisNpSignalingContextId ctxId,
}
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
// GetIp() is empty until something asks for it. RetrieveIp() connects out, so only once.
if (netinfo->GetIp().empty()) {
netinfo->RetrieveIp();
}
info->localAddr = ParseIpv4Nbo(netinfo->GetIp());
if (info->localAddr == 0) {
info->localAddr = ParseIpv4Nbo("127.0.0.1");
}
NetCtl::OrbisNetCtlNatInfo nat_info{};
nat_info.size = sizeof(nat_info);
@@ -723,7 +675,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfo(OrbisNpSignalingContextId ctxId, c
OrbisNpSignalingRequestId* outReqId) {
{
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
}
@@ -756,14 +708,15 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoA(OrbisNpSignalingContextId ctxId,
const void* peerAccountPayload,
OrbisNpSignalingRequestId* outReqId) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerAccountPayload || !outReqId) {
return ORBIS_NP_SIGNALING_ERROR_INVALID_ARGUMENT;
}
const auto ctx_it = g_contexts.find(ctxId);
if (ctx_it == g_contexts.end() || !ctx_it->second.active) {
const auto ctx_it = NpHandler::GetInstance().GetSignalingState().contexts.find(ctxId);
if (ctx_it == NpHandler::GetInstance().GetSignalingState().contexts.end() ||
!ctx_it->second.active) {
return ORBIS_NP_SIGNALING_ERROR_CTX_NOT_FOUND;
}
if ((ctx_it->second.flags & 0x4) == 0) {
@@ -780,7 +733,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoA(OrbisNpSignalingContextId ctxId,
s32 PS4_SYSV_ABI sceNpSignalingCancelPeerNetInfo(OrbisNpSignalingContextId ctxId, s32 reqOrConnId) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!IsContextValidLocked(ctxId)) {
@@ -797,7 +750,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoResult(OrbisNpSignalingContextId ct
OrbisNpSignalingConnectionId connId,
OrbisNpSignalingNetInfo* peerNetInfo) {
SignalingMutexGuard lock;
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!peerNetInfo) {
@@ -824,7 +777,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetPeerNetInfoResult(OrbisNpSignalingContextId ct
}
s32 PS4_SYSV_ABI sceNpSignalingGetMemoryInfo(OrbisNpSignalingMemoryInfo* info) {
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!info) {
@@ -838,7 +791,7 @@ s32 PS4_SYSV_ABI sceNpSignalingGetMemoryInfo(OrbisNpSignalingMemoryInfo* info) {
s32 PS4_SYSV_ABI
sceNpSignalingGetConnectionStatistics(OrbisNpSignalingConnectionStatistics* stats) {
if (!g_initialized) {
if (!NpHandler::GetInstance().GetSignalingState().initialized) {
return ORBIS_NP_SIGNALING_ERROR_NOT_INITIALIZED;
}
if (!stats) {
@@ -851,55 +804,4 @@ sceNpSignalingGetConnectionStatistics(OrbisNpSignalingConnectionStatistics* stat
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("0UvTFeomAUM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingActivateConnection);
LIB_FUNCTION("ZPLavCKqAB0", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingActivateConnectionA);
LIB_FUNCTION("X1G4kkN2R-8", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCancelPeerNetInfo);
LIB_FUNCTION("5yYjEdd4t8Y", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCreateContext);
LIB_FUNCTION("dDLNFdY8dws", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCreateContextA);
LIB_FUNCTION("6UEembipgrM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingDeactivateConnection);
LIB_FUNCTION("hx+LIg-1koI", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingDeleteContext);
LIB_FUNCTION("GQ0hqmzj0F4", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromNpId);
LIB_FUNCTION("CkPxQjSm018", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromPeerAddress);
LIB_FUNCTION("B7cT9aVby7A", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromPeerAddressA);
LIB_FUNCTION("AN3h0EBSX7A", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionInfo);
LIB_FUNCTION("rcylknsUDwg", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionInfoA);
LIB_FUNCTION("C6ZNCDTj00Y", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionStatistics);
LIB_FUNCTION("bD-JizUb3JM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionStatus);
LIB_FUNCTION("npU5V56id34", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetContextOption);
LIB_FUNCTION("U8AQMlOFBc8", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetLocalNetInfo);
LIB_FUNCTION("tOpqyDyMje4", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetMemoryInfo);
LIB_FUNCTION("zFgFHId7vAE", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfo);
LIB_FUNCTION("Shr7bZq8QHY", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfoA);
LIB_FUNCTION("2HajCEGgG4s", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfoResult);
LIB_FUNCTION("3KOuC4RmZZU", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingInitialize);
LIB_FUNCTION("IHRDvZodPYY", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingSetContextOption);
LIB_FUNCTION("NPhw0UXaNrk", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingTerminate);
LIB_FUNCTION("b4qaXPzMJxo", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingTerminateConnection);
}
} // namespace Libraries::Np::NpSignaling
@@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <cstring>
#include <string>
#include <string_view>
#include <fmt/format.h>
#include "common/alignment.h"
#include "common/logging/log.h"
@@ -10,19 +15,26 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/net_util.h"
#include "core/libraries/libs.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_util.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
#include "core/libraries/np/np_signaling/np_signaling_transport.h"
#include "core/libraries/np/signaling_handler.h"
#include "core/memory.h"
namespace Libraries::Np::NpSignaling::Helpers {
namespace {
constexpr s32 InferredNpAppType = 0;
constexpr uintptr_t kHostPageSize = 0x1000;
constexpr size_t kDumpRowSize = 32;
bool g_signaling_heap_initialized = false;
void* g_signaling_heap_base = nullptr;
@@ -50,8 +62,84 @@ s16 GetAppTypeStateGate() {
void SetAppTypeMarker(u16 marker) {
LOG_DEBUG(Lib_NpSignaling, "marker={:#x}", marker);
}
std::string HexDumpRow(const u8* bytes, size_t size) {
static constexpr char kHex[] = "0123456789abcdef";
std::string result;
result.reserve(size * 3);
for (size_t i = 0; i < size; ++i) {
if (i != 0) {
result.push_back(' ');
}
result.push_back(kHex[bytes[i] >> 4]);
result.push_back(kHex[bytes[i] & 0xf]);
}
return result;
}
std::string AsciiDumpRow(const u8* bytes, size_t size) {
std::string result;
result.reserve(size);
for (size_t i = 0; i < size; ++i) {
result.push_back(std::isprint(bytes[i]) ? static_cast<char>(bytes[i]) : '.');
}
return result;
}
void LogMemoryWindow(std::string_view label, const void* focus, size_t focus_size, size_t before,
size_t after) {
if (!focus) {
LOG_INFO(Lib_NpSignaling, "Invalid activation dump {}: unavailable (null focus)", label);
return;
}
const uintptr_t focus_addr = reinterpret_cast<uintptr_t>(focus);
const uintptr_t page_start = focus_addr & ~(kHostPageSize - 1);
const uintptr_t page_end = page_start + kHostPageSize;
const uintptr_t start = std::max(page_start, focus_addr - std::min(before, focus_addr));
const uintptr_t requested_end = focus_addr + focus_size + after;
const uintptr_t end = std::min(page_end, requested_end);
const size_t size = end > start ? end - start : 0;
auto* memory = Core::Memory::Instance();
if (size == 0 || !memory || !memory->IsValidMapping(start, size)) {
LOG_INFO(Lib_NpSignaling,
"Invalid activation dump {}: unavailable start={:#x} size={:#x} focus={:#x}",
label, start, size, focus_addr);
return;
}
LOG_INFO(Lib_NpSignaling,
"Invalid activation dump {}: start={:#x} size={:#x} focus={:#x} focus_offset={:#x} "
"focus_size={:#x}",
label, start, size, focus_addr, focus_addr - start, focus_size);
const auto* bytes = reinterpret_cast<const u8*>(start);
for (size_t offset = 0; offset < size; offset += kDumpRowSize) {
const size_t row_size = std::min(kDumpRowSize, size - offset);
LOG_INFO(Lib_NpSignaling, "Invalid activation dump {} {:#x} +{:#05x}: {} |{}|", label,
start + offset, offset, HexDumpRow(bytes + offset, row_size),
AsciiDumpRow(bytes + offset, row_size));
}
}
} // namespace
void LogInvalidActivationContext(s64 call_time, const void* peer_npid, const void* out_conn_id,
const void* caller, const void* frame) {
LOG_INFO(Lib_NpSignaling,
"Invalid activation context: t={} caller={:p} frame={:p} peerNpId={:p} "
"outConnId={:p} peer_to_out_delta={}",
call_time, caller, frame, peer_npid, out_conn_id,
reinterpret_cast<intptr_t>(out_conn_id) - reinterpret_cast<intptr_t>(peer_npid));
// Keep each read within the focus address's host page so diagnostics cannot cross into an
// unmapped guard page. The peer window is intentionally large enough to include adjacent room
// member/scratch records and, for the observed title, the nearby output connection ID.
LogMemoryWindow("peer", peer_npid, sizeof(OrbisNpId), 0x100, 0x200);
LogMemoryWindow("stack", frame, sizeof(uintptr_t) * 2, 0x100, 0x100);
LogMemoryWindow("caller_code", caller, 1, 0x40, 0x40);
}
void SetRuntimeHooks(const SignalingRuntimeHooks& hooks) {
g_runtime_hooks = hooks;
g_runtime_hooks_registered = true;
@@ -142,13 +230,6 @@ s32 StartMainRuntime(s32 thread_priority, s32 cpu_affinity_mask, s64 thread_stac
if (g_runtime_hooks.start_dispatch != nullptr) {
g_runtime_hooks.start_dispatch(priority, affinity, stack_size);
}
if (g_runtime_hooks.start_receive != nullptr) {
g_runtime_hooks.start_receive(priority, affinity, stack_size);
}
if (g_runtime_hooks.start_ping != nullptr) {
g_runtime_hooks.start_ping(priority, affinity, stack_size);
}
return ORBIS_OK;
}
@@ -156,16 +237,28 @@ s32 StartEchoRuntime(s32 thread_priority, s32 cpu_affinity_mask) {
LOG_DEBUG(Lib_NpSignaling, "thread_priority={} cpu_affinity_mask={}", thread_priority,
cpu_affinity_mask);
auto sock = Libraries::Net::sceNetSocket("SceNpSignalingIoctl", 2, 6, 0);
// A DGRAM_P2P socket, which also starts the P2P transport.
auto sock =
Libraries::Net::sceNetSocket("SceNpSignalingIoctl", Libraries::Net::ORBIS_NET_AF_INET,
Libraries::Net::ORBIS_NET_SOCK_DGRAM_P2P, 0);
if (sock < 0) {
return sock;
// Only an emulator-side problem (the P2P UDP port is taken): signaling itself still
// works without the echo probe, so carry on as the console would.
LOG_WARNING(Lib_NpSignaling, "P2P socket for the echo probe failed: {:#x}",
static_cast<u32>(sock));
return ORBIS_OK;
}
const s32 ioctl_rc = Libraries::Net::sceNetIoctl();
std::array<u8, 36> ioctl_data{};
const s32 ioctl_rc = Libraries::Net::sceNetIoctl(sock, 0xc02450ca, ioctl_data.data());
Libraries::Net::sceNetSocketClose(sock);
g_echo_probe_word2 = 0;
g_echo_probe_word3 = 0;
if (ioctl_rc >= 0) {
std::memcpy(&g_echo_probe_word2, ioctl_data.data() + 16, sizeof(g_echo_probe_word2));
std::memcpy(&g_echo_probe_word3, ioctl_data.data() + 24, sizeof(g_echo_probe_word3));
}
g_echo_thread_handle = static_cast<u64>(-1);
g_echo_thread_status = -1;
return ioctl_rc;
@@ -180,12 +273,6 @@ void ShutdownRuntime() {
return;
}
if (g_runtime_hooks.stop_ping != nullptr) {
g_runtime_hooks.stop_ping();
}
if (g_runtime_hooks.stop_receive != nullptr) {
g_runtime_hooks.stop_receive();
}
if (g_runtime_hooks.stop_dispatch != nullptr) {
g_runtime_hooks.stop_dispatch();
}
@@ -200,268 +287,30 @@ void ShutdownRuntime() {
namespace Libraries::Np::NpSignaling {
using Libraries::Net::sceNetNtohs;
static Kernel::PthreadT g_dispatch_thread{};
static Kernel::PthreadT g_receive_thread{};
static bool g_receive_stop = false;
static Kernel::PthreadT g_ping_thread{};
static bool g_ping_stop = false;
static void HandleStunEcho(s32 ctx_id, const StunEcho& echo) {
SignalingMutexGuard lock;
const auto it = g_contexts.find(ctx_id);
if (it == g_contexts.end() || !it->second.active) {
return;
}
NpSignalingContext& ctx = it->second;
ctx.ext_addr.store(echo.ext_ip);
ctx.ext_port.store(echo.ext_port);
LOG_DEBUG(Lib_NpSignaling, "STUN echo: ctxId={} ext_addr={:#x} ext_port={}", ctx_id,
echo.ext_ip, sceNetNtohs(echo.ext_port));
auto* netinfo = Common::Singleton<NetUtil::NetUtilInternal>::Instance();
netinfo->SetExternalIp(echo.ext_ip);
ctx.stun_cv.notify_all();
}
static bool HasSignalingMagic(const u8* buf, size_t nbytes) {
return nbytes >= 5 && std::memcmp(buf, kSignalingMagic, sizeof(kSignalingMagic)) == 0;
}
static void DrainControlPackets() {
static constexpr u32 kBufSize = 256;
u8 buf[kBufSize];
for (;;) {
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Stubs::ControlRecvFrom(buf, kBufSize, &from_addr, &from_port);
if (rc <= 0) {
break;
}
const auto nbytes = static_cast<size_t>(rc);
if (nbytes == sizeof(SignalingControl) && HasSignalingMagic(buf, nbytes) &&
buf[4] == static_cast<u8>(SignalingPacketType::Control)) {
SignalingControl ctrl{};
std::memcpy(&ctrl, buf, sizeof(ctrl));
HandleControlPacket(from_addr, from_port, ctrl);
} else {
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: bad control packet (size={})", nbytes);
}
}
}
static void ReceiveThreadMain() {
static constexpr u32 kBufSize = 256;
u8 buf[kBufSize];
while (!g_receive_stop) {
DrainControlPackets();
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Stubs::SignalingRecvFrom(buf, kBufSize, &from_addr, &from_port);
if (rc <= 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
const auto nbytes = static_cast<size_t>(rc);
if (nbytes != sizeof(StunEcho)) {
LOG_DEBUG(
Lib_NpSignaling,
"ReceiveThread DATA from {:#x}:{} size={} b0-4={:02x},{:02x},{:02x},{:02x},{:02x}",
from_addr, sceNetNtohs(from_port), nbytes, buf[0], buf[1], buf[2], buf[3],
nbytes >= 5 ? buf[4] : 0);
}
if (nbytes == sizeof(StunEcho)) {
s32 ctx_id = 0;
{
SignalingMutexGuard lock;
for (const auto& [cid, ctx] : g_contexts) {
if (ctx.active) {
ctx_id = cid;
break;
}
}
}
if (ctx_id == 0) {
continue;
}
StunEcho echo{};
std::memcpy(&echo, buf, sizeof(echo));
HandleStunEcho(ctx_id, echo);
continue;
}
if (!HasSignalingMagic(buf, nbytes)) {
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: dropping non-SHAD packet (size={})",
nbytes);
continue;
}
const u8 type = buf[4];
if (nbytes == sizeof(SignalingEchoPing) &&
type == static_cast<u8>(SignalingPacketType::EchoPing)) {
SignalingEchoPing ping{};
std::memcpy(&ping, buf, sizeof(ping));
SignalingEchoPong pong{};
pong.conn_id = ping.conn_id;
pong.orig_ts_us = ping.send_ts_us;
Stubs::SignalingSendTo(&pong, sizeof(pong), from_addr, from_port);
continue;
}
if (nbytes == sizeof(SignalingEchoPong) &&
type == static_cast<u8>(SignalingPacketType::EchoPong)) {
SignalingEchoPong pong{};
std::memcpy(&pong, buf, sizeof(pong));
const s64 now = NowUs();
s32 rtt_us = static_cast<s32>(now - static_cast<s64>(pong.orig_ts_us));
if (rtt_us < 0) {
rtt_us = 0;
}
{
SignalingMutexGuard lock;
RecordRttSampleLocked(static_cast<s32>(pong.conn_id), rtt_us);
}
continue;
}
if (nbytes == sizeof(SignalingHandshake) &&
type == static_cast<u8>(SignalingPacketType::Handshake)) {
SignalingHandshake hs{};
std::memcpy(&hs, buf, sizeof(hs));
HandleHandshakePacket(from_addr, from_port, hs);
continue;
}
LOG_WARNING(Lib_NpSignaling, "ReceiveThread: unexpected SHAD packet type={} size={}", type,
nbytes);
}
}
static PS4_SYSV_ABI void* ReceiveThreadFunc(void*) {
ReceiveThreadMain();
return nullptr;
}
static void StartReceiveThread(s32 priority, u64 affinity_mask, u64 stack_size) {
g_receive_stop = false;
if (!g_receive_thread) {
NpCommon::sceNpCreateThread(&g_receive_thread, ReceiveThreadFunc, nullptr, priority,
stack_size, affinity_mask, "SceNpSignalingRecv");
}
}
static void StopReceiveThread() {
{
SignalingMutexGuard lock;
g_receive_stop = true;
}
if (g_receive_thread) {
NpCommon::sceNpJoinThread(g_receive_thread, nullptr);
g_receive_thread = {};
}
}
static void PingThreadMain() {
while (!g_ping_stop) {
if (!Stubs::EnsureTransport()) {
std::this_thread::sleep_for(std::chrono::milliseconds(kSigRetryMs));
continue;
}
struct CtxSnapshot {
s32 ctx_id;
OrbisNpOnlineId online_id{};
bool resolved;
};
std::vector<CtxSnapshot> contexts;
{
SignalingMutexGuard lock;
for (const auto& [ctx_id, ctx] : g_contexts) {
if (ctx.active) {
contexts.push_back({ctx_id, ctx.owner_online_id, ctx.ext_addr.load() != 0});
}
}
}
if (Stubs::Matching2Enabled()) {
const u32 server_addr = Stubs::MmServerAddr();
const u16 server_port = Stubs::MmServerUdpPort();
for (const auto& cs : contexts) {
if (server_addr == 0 || server_port == 0) {
break;
}
StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, cs.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = Stubs::AdvertisedAddr();
Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_port);
}
}
SendEchoPings();
const bool any_unresolved = std::any_of(contexts.begin(), contexts.end(),
[](const auto& cs) { return !cs.resolved; });
const u32 sleep_ms = any_unresolved ? kSigRetryMs : kSigPingMs;
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
}
}
static PS4_SYSV_ABI void* PingThreadFunc(void*) {
PingThreadMain();
return nullptr;
}
static void StartPingThread(s32 priority, u64 affinity_mask, u64 stack_size) {
g_ping_stop = false;
if (!g_ping_thread) {
NpCommon::sceNpCreateThread(&g_ping_thread, PingThreadFunc, nullptr, priority, stack_size,
affinity_mask, "SceNpSignalingPing");
}
}
static void StopPingThread() {
g_ping_stop = true;
if (g_ping_thread) {
NpCommon::sceNpJoinThread(g_ping_thread, nullptr);
g_ping_thread = {};
}
}
static void DispatchThreadMain() {
auto& state = NpHandler::GetInstance().GetSignalingState();
for (;;) {
ProcessPendingActivations();
QueuedDispatch dispatch;
{
std::unique_lock<std::mutex> lock(g_dispatch_mutex);
std::unique_lock<std::mutex> lock(state.dispatch_mutex);
for (;;) {
if (g_dispatch_stop) {
if (state.dispatch_stop) {
return;
}
if (g_dispatch_queue.empty()) {
g_dispatch_cv.wait_for(lock, std::chrono::seconds(1), [] {
return g_dispatch_stop || !g_dispatch_queue.empty();
if (state.dispatch_queue.empty()) {
state.dispatch_cv.wait_for(lock, std::chrono::seconds(1), [&state] {
return state.dispatch_stop || !state.dispatch_queue.empty();
});
break;
}
const auto it = g_dispatch_queue.begin();
const auto it = state.dispatch_queue.begin();
const auto now = std::chrono::steady_clock::now();
if (it->first > now) {
g_dispatch_cv.wait_until(lock, it->first);
state.dispatch_cv.wait_until(lock, it->first);
break;
}
dispatch = std::move(it->second);
g_dispatch_queue.erase(it);
state.dispatch_queue.erase(it);
break;
}
}
@@ -488,36 +337,85 @@ static PS4_SYSV_ABI void* DispatchThreadFunc(void*) {
}
static void StartDispatchThread(s32 priority, u64 affinity_mask, u64 stack_size) {
std::lock_guard<std::mutex> lock(g_dispatch_mutex);
g_dispatch_stop = false;
if (!g_dispatch_thread) {
NpCommon::sceNpCreateThread(&g_dispatch_thread, DispatchThreadFunc, nullptr, priority,
auto& state = NpHandler::GetInstance().GetSignalingState();
std::lock_guard<std::mutex> lock(state.dispatch_mutex);
state.dispatch_stop = false;
if (!state.dispatch_thread) {
NpCommon::sceNpCreateThread(&state.dispatch_thread, DispatchThreadFunc, nullptr, priority,
stack_size, affinity_mask, "SceNpSignalingMain");
}
}
static void StopDispatchThread() {
auto& state = NpHandler::GetInstance().GetSignalingState();
{
std::lock_guard<std::mutex> lock(g_dispatch_mutex);
g_dispatch_stop = true;
g_dispatch_queue.clear();
std::lock_guard<std::mutex> lock(state.dispatch_mutex);
state.dispatch_stop = true;
state.dispatch_queue.clear();
}
g_dispatch_cv.notify_all();
if (g_dispatch_thread) {
NpCommon::sceNpJoinThread(g_dispatch_thread, nullptr);
g_dispatch_thread = {};
state.dispatch_cv.notify_all();
if (state.dispatch_thread) {
NpCommon::sceNpJoinThread(state.dispatch_thread, nullptr);
state.dispatch_thread = {};
}
}
void RegisterRuntimeHooks() {
Helpers::SetRuntimeHooks({
.start_dispatch = StartDispatchThread,
.start_receive = StartReceiveThread,
.start_ping = StartPingThread,
.stop_ping = StopPingThread,
.stop_receive = StopReceiveThread,
.stop_dispatch = StopDispatchThread,
});
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("0UvTFeomAUM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingActivateConnection);
LIB_FUNCTION("ZPLavCKqAB0", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingActivateConnectionA);
LIB_FUNCTION("X1G4kkN2R-8", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCancelPeerNetInfo);
LIB_FUNCTION("5yYjEdd4t8Y", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCreateContext);
LIB_FUNCTION("dDLNFdY8dws", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingCreateContextA);
LIB_FUNCTION("6UEembipgrM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingDeactivateConnection);
LIB_FUNCTION("hx+LIg-1koI", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingDeleteContext);
LIB_FUNCTION("GQ0hqmzj0F4", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromNpId);
LIB_FUNCTION("CkPxQjSm018", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromPeerAddress);
LIB_FUNCTION("B7cT9aVby7A", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionFromPeerAddressA);
LIB_FUNCTION("AN3h0EBSX7A", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionInfo);
LIB_FUNCTION("rcylknsUDwg", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionInfoA);
LIB_FUNCTION("C6ZNCDTj00Y", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionStatistics);
LIB_FUNCTION("bD-JizUb3JM", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetConnectionStatus);
LIB_FUNCTION("npU5V56id34", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetContextOption);
LIB_FUNCTION("U8AQMlOFBc8", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetLocalNetInfo);
LIB_FUNCTION("tOpqyDyMje4", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetMemoryInfo);
LIB_FUNCTION("zFgFHId7vAE", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfo);
LIB_FUNCTION("Shr7bZq8QHY", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfoA);
LIB_FUNCTION("2HajCEGgG4s", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingGetPeerNetInfoResult);
LIB_FUNCTION("3KOuC4RmZZU", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingInitialize);
LIB_FUNCTION("IHRDvZodPYY", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingSetContextOption);
LIB_FUNCTION("NPhw0UXaNrk", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingTerminate);
LIB_FUNCTION("b4qaXPzMJxo", "libSceNpSignaling", 1, "libSceNpSignaling",
sceNpSignalingTerminateConnection);
}
} // namespace Libraries::Np::NpSignaling
@@ -9,14 +9,12 @@ namespace Libraries::Np::NpSignaling::Helpers {
struct SignalingRuntimeHooks {
void (*start_dispatch)(s32 priority, u64 affinity_mask, u64 stack_size);
void (*start_receive)(s32 priority, u64 affinity_mask, u64 stack_size);
void (*start_ping)(s32 priority, u64 affinity_mask, u64 stack_size);
void (*stop_ping)();
void (*stop_receive)();
void (*stop_dispatch)();
};
void SetRuntimeHooks(const SignalingRuntimeHooks& hooks);
void LogInvalidActivationContext(s64 call_time, const void* peer_npid, const void* out_conn_id,
const void* caller, const void* frame);
s32 CheckInitializeAppType(u32* is_app_type_4);
s32 InitSignalingHeap(s64 pool_size);
void ShutdownSignalingHeap();
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,7 @@
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -21,9 +22,6 @@
namespace Libraries::Np::NpSignaling {
extern NpCommon::OrbisNpCalloutContext g_callout_ctx;
extern bool g_callout_ctx_active;
constexpr s64 kActivateCooldownUs = 6'000'000;
constexpr s64 kActivateBudgetMaxUs = 600'000'000;
constexpr u32 kSigRetryMs = 500;
@@ -133,9 +131,8 @@ enum class ConnState : s32 {
Established = 10,
};
struct ConnectionInfo {
OrbisNpSignalingConnectionId conn_id = 0;
OrbisNpSignalingContextId ctx_id = 0;
struct PeerTransport {
s32 transport_id = 0;
u32 addr = 0;
u16 port = 0;
@@ -143,40 +140,41 @@ struct ConnectionInfo {
u32 local_addr = 0;
u16 local_port = 0;
s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
OrbisNpId npid{};
OrbisNpOnlineId online_id{};
OrbisNpOnlineId local_online_id{};
s32 activation_mode = 0;
s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
ConnState state = ConnState::Inactive;
bool peer_activated = false;
bool peer_established = false;
bool transport_established_fired = false;
bool is_initiator = false;
static constexpr u32 kProbeSampleCount = 6;
s32 probe_rtt_samples[kProbeSampleCount] = {-1, -1, -1, -1, -1, -1};
u32 probe_sample_write_index = 0;
s64 last_echo_ping_us = 0;
s64 last_peer_rx_us = 0;
s64 last_handshake_send_us = 0;
};
ConnState state = ConnState::Inactive;
bool established_fired = false;
bool established_event_fired = false;
bool peer_activated = false;
struct ConnectionInfo {
OrbisNpSignalingConnectionId conn_id = 0;
OrbisNpSignalingContextId ctx_id = 0;
s32 transport_id = 0;
s32 status = ORBIS_NP_SIGNALING_CONN_STATUS_INACTIVE;
OrbisNpId npid{};
OrbisNpOnlineId online_id{};
bool sig1_established_event_fired = false;
bool locally_activated = false;
bool peer_activated = false;
bool peer_activated_fired = false;
bool peer_established = false;
bool mutual_fired = false;
bool dead_fired = false;
bool is_initiator = false;
s64 last_handshake_send_us = 0;
NpCommon::OrbisNpCalloutEntry timeout_callout{};
bool timeout_callout_armed = false;
NpCommon::OrbisNpCalloutEntry step_callout{};
bool step_callout_armed = false;
bool deactivate_lingering = false;
u32 echo_derived_value = 0;
};
struct NpSignalingContext {
@@ -246,39 +244,66 @@ struct PeerNetInfoResult {
u32 nat_route_kind = 0;
};
extern std::unordered_map<s32, NpSignalingContext> g_contexts;
extern std::unordered_map<s32, ConnectionInfo> g_connections;
extern std::unordered_map<CtxNpIdKey, s32, CtxNpIdKeyHash> g_npid_to_conn;
extern std::unordered_map<s32, PeerNetInfoResult> g_peer_netinfo_results;
struct PendingActivation {
OrbisNpSignalingConnectionId conn_id = 0;
std::string peer_online_id;
bool start_handshake = true;
};
extern std::vector<PendingActivation> g_pending_activations;
extern u32 g_peer_netinfo_next_id;
extern u32 g_peak_connection_count;
extern u32 g_last_assigned_context_id;
extern u32 g_connection_id_seed;
extern bool g_initialized;
extern Libraries::Kernel::PthreadMutexT g_mutex_storage;
struct NpSignalingState {
std::unordered_map<s32, NpSignalingContext> contexts;
std::unordered_map<s32, ConnectionInfo> connections;
std::unordered_map<s32, PeerTransport> peer_transports;
std::unordered_map<CtxNpIdKey, s32, CtxNpIdKeyHash> npid_to_conn;
std::unordered_map<s32, PeerNetInfoResult> peer_netinfo_results;
std::vector<PendingActivation> pending_activations;
u32 peer_netinfo_next_id = 1;
u32 peak_connection_count = 0;
u32 last_assigned_context_id = 0;
u32 connection_id_seed = 0;
u32 transport_id_seed = 0;
bool initialized = false;
std::recursive_mutex mutex;
std::multimap<std::chrono::steady_clock::time_point, QueuedDispatch> dispatch_queue;
std::mutex dispatch_mutex;
std::condition_variable dispatch_cv;
Libraries::Kernel::PthreadT dispatch_thread = nullptr;
bool dispatch_stop = false;
std::atomic<bool> runtime_stop{false};
std::mutex runtime_thread_mutex;
std::thread runtime_thread;
std::mutex activation_mutex;
std::condition_variable activation_cv;
std::thread activation_thread;
u32 context_refs = 0;
void ResetSig1Data() {
contexts.clear();
connections.clear();
npid_to_conn.clear();
peer_netinfo_results.clear();
pending_activations.clear();
peer_netinfo_next_id = 1;
peak_connection_count = 0;
last_assigned_context_id = 0;
connection_id_seed = 0;
}
};
struct SignalingMutexGuard {
SignalingMutexGuard();
~SignalingMutexGuard();
~SignalingMutexGuard() = default;
SignalingMutexGuard(const SignalingMutexGuard&) = delete;
SignalingMutexGuard& operator=(const SignalingMutexGuard&) = delete;
private:
std::unique_lock<std::recursive_mutex> lock;
};
void InitSignalingMutex();
void DestroySignalingMutex();
extern std::multimap<std::chrono::steady_clock::time_point, QueuedDispatch> g_dispatch_queue;
extern std::mutex g_dispatch_mutex;
extern std::condition_variable g_dispatch_cv;
extern bool g_dispatch_stop;
long long NowMs();
s64 NowUs();
std::string OnlineIdToString(const OrbisNpOnlineId& online_id_in);
@@ -315,43 +340,11 @@ bool TakePeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_co
PeerNetInfoResult* out);
bool DropPeerNetInfoResultLocked(OrbisNpSignalingContextId ctx_id, s32 req_or_conn_id);
void RecordRttSampleLocked(OrbisNpSignalingConnectionId conn_id, s32 rtt_us);
void SendEchoPings();
void DispatchConnectionEvent(OrbisNpSignalingConnectionId conn_id, s32 event_type, s32 event_data);
void DispatchPeerActivatedEvent(OrbisNpSignalingConnectionId conn_id);
void CloseConnectionAndDispatchDead(OrbisNpSignalingConnectionId conn_id, s32 error_code);
void EstablishConnection(OrbisNpSignalingConnectionId conn_id, bool peer_activated_hint);
void DeactivateConnectionFaithful(OrbisNpSignalingConnectionId conn_id);
void TerminateConnectionFaithful(OrbisNpSignalingConnectionId conn_id);
void StartHandshakeInitiator(OrbisNpSignalingConnectionId conn_id);
void QueueActivationLocked(OrbisNpSignalingConnectionId conn_id, std::string_view peer_online_id,
bool start_handshake = true);
void ProcessPendingActivations();
void HandleHandshakePacket(u32 from_addr, u16 from_port, const SignalingHandshake& pkt);
void ArmConnectTimeoutLocked(OrbisNpSignalingConnectionId conn_id);
void ClearLingerAndTimeoutLocked(OrbisNpSignalingConnectionId conn_id);
void SendActivationRequestLocked(const ConnectionInfo& ci);
void SendControlCloseLocked(const ConnectionInfo& ci, ControlReason reason);
void HandleControlPacket(u32 from_addr, u16 from_port, const SignalingControl& pkt);
const char* SignalingEventName(u32 event_type);
bool ConsumeActivationBudgetLocked(NpSignalingContext& ctx);
void SendStunPing(OrbisNpSignalingContextId ctx_id);
void ClearActiveDeadlines();
void ClearConnectionDeadlineForPeer(std::string_view npid);
void SetRoomLeft();
} // namespace Libraries::Np::NpSignaling
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
#include "core/libraries/np/np_signaling/np_signaling_transport.h"
namespace Libraries::Np::NpSignaling::Stubs {
namespace Libraries::Np::NpSignaling::Transport {
namespace {
TransportHooks g_transport{};
@@ -81,4 +81,4 @@ u16 MmServerUdpPort() {
return g_mm_server_udp_port;
}
} // namespace Libraries::Np::NpSignaling::Stubs
} // namespace Libraries::Np::NpSignaling::Transport
@@ -4,9 +4,10 @@
#pragma once
#include <string_view>
#include "common/types.h"
namespace Libraries::Np::NpSignaling::Stubs {
namespace Libraries::Np::NpSignaling::Transport {
struct TransportHooks {
int (*signaling_send)(const void* data, u32 len, u32 dest_addr, u16 dest_port) = nullptr;
@@ -39,4 +40,4 @@ bool Matching2Enabled();
u32 MmServerAddr();
u16 MmServerUdpPort();
} // namespace Libraries::Np::NpSignaling::Stubs
} // namespace Libraries::Np::NpSignaling::Transport
+77 -5
View File
@@ -67,6 +67,7 @@ enum OrbisNpMatching2Event : u16 {
ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE = 0x0108,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL = 0x0109,
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_INTERNAL = 0x010A,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_MEMBER_DATA_INTERNAL = 0x010B,
ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_LOBBY = 0x0201,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO = 0x0E01,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED = 0x1101,
@@ -88,6 +89,7 @@ enum OrbisNpMatching2Event : u16 {
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_EXTERNAL_LIST_A = 0x7003,
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_DATA_EXTERNAL_LIST_A = 0x7005,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106,
ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_ROOM_MEMBER_DATA_INTERNAL_A = 0x710C,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED_A = 0x8101,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT_A = 0x8102,
ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_MEMBER_DATA_INTERNAL_A = 0x8107,
@@ -347,17 +349,24 @@ struct OrbisNpMatching2RoomMessageInfo {
void* msg;
u32 msgLen;
};
static_assert(sizeof(OrbisNpMatching2RoomMessageInfo) == 0x28);
struct OrbisNpMatching2RoomMessageInfoA {
bool filtered;
OrbisNpMatching2CastType castType;
u8 padding[2];
u8 padding[6];
Libraries::Np::OrbisNpPeerAddressA srcMember;
Libraries::Np::OrbisNpOnlineId srcMemberOnlineId;
OrbisNpMatching2RoomMessageDestination* dst;
Libraries::Np::OrbisNpPeerAddressA* srcMember;
Libraries::Np::OrbisNpOnlineId* srcOnlineId;
void* msg;
u32 msgLen;
const void* msg;
u64 msgLen;
};
static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, srcMember) == 0x08);
static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, srcMemberOnlineId) == 0x18);
static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, dst) == 0x30);
static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, msg) == 0x38);
static_assert(offsetof(OrbisNpMatching2RoomMessageInfoA, msgLen) == 0x40);
static_assert(sizeof(OrbisNpMatching2RoomMessageInfoA) == 0x48);
// Room-internal binary attribute
struct OrbisNpMatching2RoomBinAttrInternal {
@@ -812,6 +821,39 @@ struct OrbisNpMatching2GetRoomDataInternalRequest {
u64 attrIdNum;
};
// SetRoomMemberDataInternal request
struct OrbisNpMatching2SetRoomMemberDataInternalRequest {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2RoomMemberId memberId;
OrbisNpMatching2TeamId teamId;
u8 padding[5];
OrbisNpMatching2FlagAttr flagFilter;
OrbisNpMatching2FlagAttr flagAttr;
const OrbisNpMatching2BinAttr* roomMemberBinAttrInternal;
u64 roomMemberBinAttrInternalNum;
};
static_assert(sizeof(OrbisNpMatching2SetRoomMemberDataInternalRequest) == 0x28);
// GetRoomMemberDataInternal request
struct OrbisNpMatching2GetRoomMemberDataInternalRequest {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2RoomMemberId memberId;
u8 padding[6];
const OrbisNpMatching2AttributeId* attrId;
u64 attrIdNum;
};
static_assert(sizeof(OrbisNpMatching2GetRoomMemberDataInternalRequest) == 0x20);
// GetRoomMemberDataInternal response
struct OrbisNpMatching2GetRoomMemberDataInternalResponse {
OrbisNpMatching2RoomMemberDataInternal* roomMemberDataInternal;
};
// GetRoomMemberDataInternal response (account-id variant)
struct OrbisNpMatching2GetRoomMemberDataInternalResponseA {
OrbisNpMatching2RoomMemberDataInternalA* roomMemberDataInternal;
};
// SetUserInfo request
struct OrbisNpMatching2SetUserInfoRequest {
OrbisNpMatching2ServerId serverId;
@@ -890,6 +932,36 @@ struct OrbisNpMatching2RoomDataInternalUpdate {
u64 chgRoomBinAttrInternalNum;
};
// Room member data internal update information
struct OrbisNpMatching2RoomMemberDataInternalUpdate {
OrbisNpMatching2RoomMemberDataInternal* chgRoomMemberDataInternal;
OrbisNpMatching2FlagAttr* chgFlagAttr;
OrbisNpMatching2FlagAttr* prevFlagAttr;
OrbisNpMatching2TeamId* chgTeamId;
OrbisNpMatching2RoomMemberBinAttrInternal** chgRoomMemberBinAttrInternal;
u64 chgRoomMemberBinAttrInternalNum;
};
static_assert(sizeof(OrbisNpMatching2RoomMemberDataInternalUpdate) == 0x30);
static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdate,
chgRoomMemberBinAttrInternal) == 0x20);
static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdate,
chgRoomMemberBinAttrInternalNum) == 0x28);
// Room member data internal update information (account-id variant)
struct OrbisNpMatching2RoomMemberDataInternalUpdateA {
OrbisNpMatching2RoomMemberDataInternalA* chgRoomMemberDataInternal;
OrbisNpMatching2FlagAttr* chgFlagAttr;
OrbisNpMatching2FlagAttr* prevFlagAttr;
OrbisNpMatching2TeamId* chgTeamId;
OrbisNpMatching2RoomMemberBinAttrInternal** chgRoomMemberBinAttrInternal;
u64 chgRoomMemberBinAttrInternalNum;
};
static_assert(sizeof(OrbisNpMatching2RoomMemberDataInternalUpdateA) == 0x30);
static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdateA,
chgRoomMemberBinAttrInternal) == 0x20);
static_assert(offsetof(OrbisNpMatching2RoomMemberDataInternalUpdateA,
chgRoomMemberBinAttrInternalNum) == 0x28);
// Room update information
struct OrbisNpMatching2RoomUpdate {
OrbisNpMatching2EventCause eventCause;
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/np/np_types2.h"
namespace Libraries::Np::NpMatching2 {
struct ContextObject;
}
namespace Libraries::Np::SignalingHandler {
void Start();
void Stop();
void AddContextRef();
void ReleaseContextRef();
s32 ActivateSig1(NpSignaling::OrbisNpSignalingContextId ctx_id, const OrbisNpId& peer_npid,
const OrbisNpOnlineId& peer_online_id,
NpSignaling::OrbisNpSignalingConnectionId* out_conn_id);
void DeactivateSig1(NpSignaling::OrbisNpSignalingConnectionId conn_id);
void TerminateSig1(NpSignaling::OrbisNpSignalingConnectionId conn_id);
void StartMatching2(NpMatching2::ContextObject& ctx);
void StopMatching2(NpMatching2::ContextObject& ctx, s32 error_code);
void StopMatching2(NpMatching2::ContextObject& ctx, NpMatching2::OrbisNpMatching2RoomId room_id,
s32 error_code);
} // namespace Libraries::Np::SignalingHandler
File diff suppressed because it is too large Load Diff
+154
View File
@@ -0,0 +1,154 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <span>
#include <vector>
#include "core/net/host_net.h"
namespace Core::Net {
namespace P2P {
class Transport;
struct Endpoint;
} // namespace P2P
struct NetResult {
s64 value; // only valid when error == Ok
Host::Error error;
static NetResult Ok(s64 value = 0) {
return {value, Host::Error::Ok};
}
static NetResult Fail(Host::Error error) {
return {-1, error};
}
};
inline constexpr int MaxSockets = 128;
struct IdAllocator {
std::function<s32()> allocate; // -1 when out of ids
std::function<void(s32)> release;
};
void SetIdAllocator(IdAllocator allocator);
enum class NetObjectKind { Socket, Epoll, External };
std::optional<NetObjectKind> GetObjectKind(s32 id);
NetResult CloseObject(s32 id);
NetResult ReserveExternalId();
NetResult SignalExternal(s32 id, bool hangup = false);
NetResult ReleaseExternalId(s32 id);
std::vector<s32> ListSockets();
s32 ToOrbisReturn(NetResult result);
NetResult SocketCreate(int family, int type, int protocol);
NetResult SocketCreatePair(int family, int type, int protocol, s32 ids[2]);
NetResult SocketBind(s32 id, const sockaddr* addr, socklen_t len);
NetResult SocketListen(s32 id, int backlog);
NetResult SocketConnect(s32 id, const sockaddr* addr, socklen_t len);
NetResult SocketAccept(s32 id, sockaddr* addr, socklen_t* len);
NetResult SocketSendTo(s32 id, const void* buf, size_t len, int host_flags, bool dontwait,
const sockaddr* addr, socklen_t addr_len);
NetResult SocketRecvFrom(s32 id, void* buf, size_t len, int host_flags, bool dontwait,
sockaddr* addr, socklen_t* addr_len);
NetResult SocketShutdown(s32 id, int how);
NetResult SocketGetName(s32 id, sockaddr* addr, socklen_t* len);
NetResult SocketGetPeerName(s32 id, sockaddr* addr, socklen_t* len);
NetResult SocketSetHostOption(s32 id, int level, int name, const void* value, socklen_t len);
NetResult SocketGetHostOption(s32 id, int level, int name, void* value, socklen_t* len);
struct SocketInfo {
int type; // P2P sockets report their base type
bool p2p;
bool nonblocking;
bool reuse_addr;
s64 rcv_timeout_us;
s64 snd_timeout_us;
s64 connect_timeout_us;
s64 accept_timeout_us;
bool connected;
bool listening;
Host::Error pending_error;
};
NetResult SocketGetInfo(s32 id, SocketInfo* info);
struct SocketAttributes {
std::array<char, 32> name{}; // SO_NAME
s32 snd_buf = 0; // 0 = PS4 default
s32 rcv_buf = 0;
s32 policy = 0; // 0-15
s32 priority = 16; // TODO: find real value
// Stored for getsockopt only, keyed by (level, name).
std::map<std::pair<s32, s32>, s32> stored_options;
// A STREAM_P2P socket bound to a non-zero TCP port can listen, but connect gives EPROTO.
bool p2p_tcp_port_bound = false;
// ip_mreq joins issued before bind. Windows refuses them on an unbound socket, so they
// are retried after bind.
std::vector<std::array<u8, 8>> pending_memberships;
};
NetResult SocketGetAttributes(s32 id, SocketAttributes* out);
NetResult SocketUpdateAttributes(s32 id, const std::function<void(SocketAttributes&)>& update);
NetResult SocketSetNonBlocking(s32 id, bool enable);
NetResult SocketBytesReadable(s32 id);
NetResult SocketSetReuseAddr(s32 id, bool enable);
NetResult SocketSetReusePort(s32 id, bool enable);
NetResult SocketSetRecvTimeout(s32 id, std::chrono::microseconds value);
NetResult SocketSetSendTimeout(s32 id, std::chrono::microseconds value);
NetResult SocketSetConnectTimeout(s32 id, std::chrono::microseconds value);
NetResult SocketSetAcceptTimeout(s32 id, std::chrono::microseconds value);
inline constexpr u32 kSocketAbortPreserveRecv = 0x1;
inline constexpr u32 kSocketAbortPreserveSend = 0x2;
inline constexpr u32 kSocketAbortPreserveSendAgain = 0x4;
NetResult SocketAbort(s32 id, u32 flags);
NetResult SocketClose(s32 id);
void SetP2PTransport(int family, std::shared_ptr<P2P::Transport> transport);
NetResult P2PSocketCreate(int family, bool stream);
NetResult P2PSocketSetProtection(s32 id, bool crypto, bool signature);
NetResult P2PSocketGetProtection(s32 id, bool* crypto, bool* signature);
NetResult P2PSocketBind(s32 id, u16 vport); // 0 = any free vport
NetResult P2PSocketConnect(s32 id, const P2P::Endpoint& peer, u16 peer_vport);
NetResult P2PSocketAccept(s32 id, P2P::Endpoint* peer, u16* peer_vport);
struct P2PSendProtection {
bool crypto = false;
bool signature = false;
};
NetResult P2PSocketSendTo(s32 id, const void* buf, size_t len, bool dontwait,
const P2P::Endpoint* peer, u16 peer_vport,
P2PSendProtection protection = {});
NetResult P2PSocketRecvFrom(s32 id, void* buf, size_t len, bool peek, bool dontwait,
P2P::Endpoint* from, u16* from_vport, bool waitall = false);
NetResult P2PSocketGetName(s32 id, u16* udp_port, u16* vport);
NetResult P2PSocketGetPeerName(s32 id, P2P::Endpoint* peer, u16* peer_vport);
enum class EpollOp { Add, Modify, Delete };
struct GuestEpollEvent {
u32 events; // Host::EventBits
s32 ident; // socket id
u64 data;
};
NetResult EpollCreate();
NetResult EpollControl(s32 eid, EpollOp op, s32 socket_id, u32 events, u32 flags, u64 data);
NetResult EpollWait(s32 eid, std::span<GuestEpollEvent> out, s64 timeout_us);
inline constexpr u32 kEpollAbortPreserve = 0x1;
NetResult EpollAbort(s32 eid, u32 flags);
NetResult EpollDestroy(s32 eid);
struct SelectEntry {
s32 id;
u32 interest;
u32 ready;
};
NetResult SocketSelect(std::span<SelectEntry> entries, s64 timeout_us);
} // namespace Core::Net
+920
View File
@@ -0,0 +1,920 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <climits>
#include <cstdint>
#include "core/net/host_net.h"
#ifdef _WIN32
#include <mstcpip.h>
#include <wepoll.h>
#ifdef _MSC_VER
#pragma comment(lib, "Ws2_32.lib")
#endif
#ifndef SIO_UDP_CONNRESET
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
#endif
#define HOST_NET_EPOLL 1
#else
#include <cerrno>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/epoll.h>
#include <sys/eventfd.h>
#define HOST_NET_EPOLL 1
#else
#include <sys/event.h>
#include <sys/time.h>
#define HOST_NET_KQUEUE 1
#endif
#endif
namespace Core::Net::Host {
int TimeoutMs(const Deadline& deadline) {
if (!deadline) {
return -1;
}
const auto left = *deadline - Clock::now();
if (left <= Clock::duration::zero()) {
return 0;
}
// Round up to avoid waking early. Callers loop anyway.
const auto ms = std::chrono::ceil<std::chrono::milliseconds>(left).count();
return static_cast<int>(std::min<s64>(ms, INT_MAX));
}
bool Expired(const Deadline& deadline) {
return deadline && Clock::now() >= *deadline;
}
bool SetNonBlocking(NativeSocket s) {
#ifdef _WIN32
u_long on = 1;
return ioctlsocket(s, FIONBIO, &on) == 0;
#else
const int flags = fcntl(s, F_GETFL, 0);
return flags >= 0 && fcntl(s, F_SETFL, flags | O_NONBLOCK) == 0;
#endif
}
#ifndef _WIN32
bool SetCloseOnExec(int fd) {
const int flags = fcntl(fd, F_GETFD, 0);
return flags >= 0 && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0;
}
#endif
Deadline DeadlineFromSocketTimeout(std::chrono::microseconds timeout) {
if (timeout.count() <= 0) {
return std::nullopt;
}
return Clock::now() + timeout;
}
// Errors
Error TranslateNative(int code) {
struct Entry {
int host;
Error error;
};
#ifdef _WIN32
static constexpr Entry Table[] = {
{WSAEINTR, Error::Intr},
{WSAEBADF, Error::BadF},
{WSAEACCES, Error::Acces},
{WSAEFAULT, Error::Fault},
{WSAEINVAL, Error::Inval},
{WSAEMFILE, Error::MFile},
{WSAEWOULDBLOCK, Error::WouldBlock},
{WSAEINPROGRESS, Error::InProgress},
{WSAEALREADY, Error::Already},
{WSAENOTSOCK, Error::NotSock},
{WSAEDESTADDRREQ, Error::DestAddrReq},
{WSAEMSGSIZE, Error::MsgSize},
{WSAEPROTOTYPE, Error::ProtoType},
{WSAENOPROTOOPT, Error::NoProtoOpt},
{WSAEPROTONOSUPPORT, Error::ProtoNoSupport},
{WSAESOCKTNOSUPPORT, Error::SocktNoSupport},
{WSAEOPNOTSUPP, Error::OpNotSupp},
{WSAEPFNOSUPPORT, Error::PfNoSupport},
{WSAEAFNOSUPPORT, Error::AfNoSupport},
{WSAEADDRINUSE, Error::AddrInUse},
{WSAEADDRNOTAVAIL, Error::AddrNotAvail},
{WSAENETDOWN, Error::NetDown},
{WSAENETUNREACH, Error::NetUnreach},
{WSAENETRESET, Error::NetReset},
{WSAECONNABORTED, Error::ConnAborted},
{WSAECONNRESET, Error::ConnReset},
{WSAENOBUFS, Error::NoBufs},
{WSAEISCONN, Error::IsConn},
{WSAENOTCONN, Error::NotConn},
{WSAESHUTDOWN, Error::Pipe}, // BSD gives EPIPE after shutdown
{WSAETOOMANYREFS, Error::TooManyRefs},
{WSAETIMEDOUT, Error::TimedOut},
{WSAECONNREFUSED, Error::ConnRefused},
{WSAELOOP, Error::Loop},
{WSAENAMETOOLONG, Error::NameTooLong},
{WSAEHOSTDOWN, Error::HostDown},
{WSAEHOSTUNREACH, Error::HostUnreach},
{WSAENOTEMPTY, Error::NotEmpty},
{WSA_NOT_ENOUGH_MEMORY, Error::NoMem},
{WSAECANCELLED, Error::Canceled},
};
#else
static constexpr Entry Table[] = {
{EPERM, Error::Perm},
{ENOENT, Error::NoEnt},
{EINTR, Error::Intr},
{EIO, Error::Io},
{EBADF, Error::BadF},
{ENOMEM, Error::NoMem},
{EACCES, Error::Acces},
{EFAULT, Error::Fault},
{EBUSY, Error::Busy},
{EEXIST, Error::Exist},
{ENODEV, Error::NoDev},
{EINVAL, Error::Inval},
{ENFILE, Error::NFile},
{EMFILE, Error::MFile},
{ENOSPC, Error::NoSpc},
{EPIPE, Error::Pipe},
{EAGAIN, Error::WouldBlock},
#if EWOULDBLOCK != EAGAIN
{EWOULDBLOCK, Error::WouldBlock},
#endif
{EINPROGRESS, Error::InProgress},
{EALREADY, Error::Already},
{ENOTSOCK, Error::NotSock},
{EDESTADDRREQ, Error::DestAddrReq},
{EMSGSIZE, Error::MsgSize},
{EPROTOTYPE, Error::ProtoType},
{ENOPROTOOPT, Error::NoProtoOpt},
{EPROTONOSUPPORT, Error::ProtoNoSupport},
{ESOCKTNOSUPPORT, Error::SocktNoSupport},
{EOPNOTSUPP, Error::OpNotSupp},
#if ENOTSUP != EOPNOTSUPP
{ENOTSUP, Error::OpNotSupp},
#endif
{EPFNOSUPPORT, Error::PfNoSupport},
{EAFNOSUPPORT, Error::AfNoSupport},
{EADDRINUSE, Error::AddrInUse},
{EADDRNOTAVAIL, Error::AddrNotAvail},
{ENETDOWN, Error::NetDown},
{ENETUNREACH, Error::NetUnreach},
{ENETRESET, Error::NetReset},
{ECONNABORTED, Error::ConnAborted},
{ECONNRESET, Error::ConnReset},
{ENOBUFS, Error::NoBufs},
{EISCONN, Error::IsConn},
{ENOTCONN, Error::NotConn},
{ESHUTDOWN, Error::Pipe},
{ETOOMANYREFS, Error::TooManyRefs},
{ETIMEDOUT, Error::TimedOut},
{ECONNREFUSED, Error::ConnRefused},
{ELOOP, Error::Loop},
{ENAMETOOLONG, Error::NameTooLong},
{EHOSTDOWN, Error::HostDown},
{EHOSTUNREACH, Error::HostUnreach},
{ENOTEMPTY, Error::NotEmpty},
{ECANCELED, Error::Canceled},
};
#endif
if (code == 0) {
return Error::Ok;
}
for (const auto& entry : Table) {
if (entry.host == code) {
return entry.error;
}
}
return Error::Internal;
}
Error LastError() {
#ifdef _WIN32
return TranslateNative(WSAGetLastError());
#else
return TranslateNative(errno);
#endif
}
bool Initialize() {
#ifdef _WIN32
WSADATA data;
return WSAStartup(MAKEWORD(2, 2), &data) == 0;
#else
return true;
#endif
}
void Shutdown() {
#ifdef _WIN32
WSACleanup();
#endif
}
// Sockets
bool ConfigureSocket(NativeSocket s, int type) {
if (!SetNonBlocking(s)) {
return false;
}
#ifdef _WIN32
if (type == SOCK_DGRAM) {
// Otherwise an ICMP port-unreachable from an earlier sendto() makes the next
// recvfrom() fail with WSAECONNRESET. BSD doesn't do that for UDP.
BOOL report = FALSE;
DWORD bytes = 0;
WSAIoctl(s, SIO_UDP_CONNRESET, &report, sizeof(report), nullptr, 0, &bytes, nullptr,
nullptr);
}
#else
(void)type;
SetCloseOnExec(s);
#ifdef SO_NOSIGPIPE
// Don't let a dead peer SIGPIPE the emulator.
int on = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
#endif
#endif
return true;
}
NativeSocket CreateSocket(int family, int type, int protocol, Error* error) {
#ifdef _WIN32
NativeSocket s = WSASocketW(family, type, protocol, nullptr, 0,
WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
#elif defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
NativeSocket s = socket(family, type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol);
#else
NativeSocket s = socket(family, type, protocol);
#endif
if (s == InvalidSocket) {
*error = LastError();
return InvalidSocket;
}
if (!ConfigureSocket(s, type)) {
*error = LastError();
CloseSocket(s);
return InvalidSocket;
}
*error = Error::Ok;
return s;
}
Error CreateSocketPair(int family, int type, int protocol, NativeSocket out[2]) {
out[0] = out[1] = InvalidSocket;
#ifdef _WIN32
(void)family;
(void)protocol;
if (type != SOCK_STREAM) {
return Error::ProtoNoSupport;
}
// Connect through a loopback listener and make sure we accepted our own client.
Error e;
NativeSocket listener = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &e);
if (listener == InvalidSocket) {
return e;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
socklen_t len = sizeof(addr);
NativeSocket client = InvalidSocket;
NativeSocket server = InvalidSocket;
const auto fail = [&](Error error) {
CloseSocket(listener);
CloseSocket(client);
CloseSocket(server);
return error;
};
if (bind(listener, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0 ||
listen(listener, 1) != 0 ||
getsockname(listener, reinterpret_cast<sockaddr*>(&addr), &len) != 0) {
return fail(LastError());
}
client = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, &e);
if (client == InvalidSocket) {
return fail(e);
}
if (connect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0 &&
LastError() != Error::WouldBlock && LastError() != Error::InProgress) {
return fail(LastError());
}
WSAPOLLFD poll{listener, POLLRDNORM, 0};
if (WSAPoll(&poll, 1, 5000) != 1) {
return fail(Error::TimedOut);
}
server = Accept(listener, nullptr, nullptr, &e);
if (server == InvalidSocket) {
return fail(e);
}
sockaddr_in client_name{};
sockaddr_in server_peer{};
socklen_t client_len = sizeof(client_name);
socklen_t peer_len = sizeof(server_peer);
getsockname(client, reinterpret_cast<sockaddr*>(&client_name), &client_len);
getpeername(server, reinterpret_cast<sockaddr*>(&server_peer), &peer_len);
if (client_name.sin_port != server_peer.sin_port) {
return fail(Error::ConnAborted); // someone else got in first
}
WSAPOLLFD writable{client, POLLWRNORM, 0};
WSAPoll(&writable, 1, 5000);
CloseSocket(listener);
out[0] = client;
out[1] = server;
return Error::Ok;
#else
int fds[2];
if (socketpair(family, type, protocol, fds) != 0) {
return LastError();
}
if (!ConfigureSocket(fds[0], type) || !ConfigureSocket(fds[1], type)) {
const Error e = LastError();
CloseSocket(fds[0]);
CloseSocket(fds[1]);
return e;
}
out[0] = fds[0];
out[1] = fds[1];
return Error::Ok;
#endif
}
void CloseSocket(NativeSocket s) {
if (s == InvalidSocket) {
return;
}
#ifdef _WIN32
closesocket(s);
#else
close(s); // don't retry on EINTR, the fd may already be gone
#endif
}
Error PrepareBind(NativeSocket s, bool reuse_addr, bool reuse_port) {
#ifdef _WIN32
// Windows SO_REUSEADDR behaves like BSD SO_REUSEPORT, which is also what games want for
// multicast. Without reuse, SO_EXCLUSIVEADDRUSE is the closest match to BSD.
const int opt = (reuse_addr || reuse_port) ? SO_REUSEADDR : SO_EXCLUSIVEADDRUSE;
BOOL on = TRUE;
if (setsockopt(s, SOL_SOCKET, opt, reinterpret_cast<const char*>(&on), sizeof(on)) != 0) {
return LastError();
}
#else
int on = reuse_addr ? 1 : 0;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) {
return LastError();
}
#ifdef SO_REUSEPORT
on = reuse_port ? 1 : 0;
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) != 0) {
return LastError();
}
#endif
#endif
return Error::Ok;
}
void PrepareClose(NativeSocket s, bool guest_blocking) {
linger value{};
socklen_t len = sizeof(value);
if (getsockopt(s, SOL_SOCKET, LingerOption, reinterpret_cast<char*>(&value), &len) != 0 ||
value.l_onoff == 0 || value.l_linger <= 0) {
return; // no linger or linger 0, same in every mode
}
if (guest_blocking) {
#ifdef _WIN32
u_long off = 0;
ioctlsocket(s, FIONBIO, &off);
#else
const int flags = fcntl(s, F_GETFL, 0);
if (flags >= 0) {
fcntl(s, F_SETFL, flags & ~O_NONBLOCK);
}
#endif
return;
}
value.l_onoff = 0;
setsockopt(s, SOL_SOCKET, LingerOption, reinterpret_cast<const char*>(&value), sizeof(value));
}
Error Bind(NativeSocket s, const sockaddr* addr, socklen_t len) {
return bind(s, addr, len) == 0 ? Error::Ok : LastError();
}
Error Listen(NativeSocket s, int backlog) {
return listen(s, backlog) == 0 ? Error::Ok : LastError();
}
Error ShutdownSocket(NativeSocket s, int how) {
return shutdown(s, how) == 0 ? Error::Ok : LastError();
}
Error Connect(NativeSocket s, const sockaddr* addr, socklen_t len) {
if (connect(s, addr, len) == 0) {
return Error::Ok;
}
const Error e = LastError();
#ifdef _WIN32
// Winsock says WSAEWOULDBLOCK where BSD says EINPROGRESS.
if (e == Error::WouldBlock) {
return Error::InProgress;
}
#endif
return e;
}
Error PendingSocketError(NativeSocket s) {
int value = 0;
socklen_t len = sizeof(value);
if (getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&value), &len) != 0) {
return LastError();
}
return TranslateNative(value);
}
IoResult BytesReadable(NativeSocket s) {
#ifdef _WIN32
u_long n = 0;
if (ioctlsocket(s, FIONREAD, &n) != 0) {
return {0, LastError()};
}
#else
int n = 0;
if (ioctl(s, FIONREAD, &n) != 0) {
return {0, LastError()};
}
#endif
return {static_cast<s64>(n), Error::Ok};
}
NativeSocket Accept(NativeSocket s, sockaddr* addr, socklen_t* len, Error* error) {
for (;;) {
#if defined(__linux__) || defined(__FreeBSD__)
NativeSocket c = accept4(s, addr, len, SOCK_NONBLOCK | SOCK_CLOEXEC);
#else
NativeSocket c = accept(s, addr, len);
#endif
if (c == InvalidSocket) {
const Error e = LastError();
if (e == Error::Intr) {
continue;
}
*error = e;
return InvalidSocket;
}
// Whether accept() inherits O_NONBLOCK varies by platform.
if (!ConfigureSocket(c, SOCK_STREAM)) {
*error = LastError();
CloseSocket(c);
return InvalidSocket;
}
*error = Error::Ok;
return c;
}
}
IoResult SendTo(NativeSocket s, const void* buf, size_t len, int flags, const sockaddr* addr,
socklen_t addr_len) {
#ifdef MSG_NOSIGNAL
flags |= MSG_NOSIGNAL;
#endif
#ifdef _WIN32
const int n_len = static_cast<int>(std::min<size_t>(len, INT_MAX));
const char* p = static_cast<const char*>(buf);
const int n = addr ? sendto(s, p, n_len, flags, addr, addr_len) : send(s, p, n_len, flags);
#else
ssize_t n;
do {
n = addr ? sendto(s, buf, len, flags, addr, addr_len) : send(s, buf, len, flags);
} while (n < 0 && errno == EINTR);
#endif
if (n < 0) {
return {-1, LastError()};
}
return {static_cast<s64>(n), Error::Ok};
}
IoResult RecvFrom(NativeSocket s, void* buf, size_t len, int flags, sockaddr* addr,
socklen_t* addr_len) {
#ifdef _WIN32
const int n_len = static_cast<int>(std::min<size_t>(len, INT_MAX));
char* p = static_cast<char*>(buf);
const int n = addr ? recvfrom(s, p, n_len, flags, addr, addr_len) : recv(s, p, n_len, flags);
#else
ssize_t n;
do {
n = addr ? recvfrom(s, buf, len, flags, addr, addr_len) : recv(s, buf, len, flags);
} while (n < 0 && errno == EINTR);
#endif
if (n < 0) {
const Error e = LastError();
#ifdef _WIN32
// Winsock fails truncated datagrams with WSAEMSGSIZE. BSD returns the length read.
if (e == Error::MsgSize) {
return {n_len, Error::Ok};
}
#endif
return {-1, e};
}
return {static_cast<s64>(n), Error::Ok};
}
WakeHandle::WakeHandle() {
#if defined(__linux__)
read_ = write_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
#elif defined(_WIN32)
// WSAPoll and wepoll only take sockets, so use a loopback UDP pair.
read_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
write_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (read_ == InvalidSocket || write_ == InvalidSocket) {
Reset();
return;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int addr_len = sizeof(addr);
sockaddr_in peer{};
int peer_len = sizeof(peer);
const bool ok = bind(read_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0 &&
getsockname(read_, reinterpret_cast<sockaddr*>(&addr), &addr_len) == 0 &&
connect(write_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0 &&
getsockname(write_, reinterpret_cast<sockaddr*>(&peer), &peer_len) == 0 &&
// Only accept datagrams from our writer.
connect(read_, reinterpret_cast<sockaddr*>(&peer), sizeof(peer)) == 0 &&
ConfigureSocket(read_, SOCK_DGRAM) && ConfigureSocket(write_, SOCK_DGRAM);
if (!ok) {
Reset();
}
#else
int fds[2];
if (pipe(fds) != 0) {
return;
}
read_ = fds[0];
write_ = fds[1];
for (const int fd : fds) {
if (!SetNonBlocking(fd) || !SetCloseOnExec(fd)) {
Reset();
return;
}
}
#endif
}
WakeHandle::~WakeHandle() {
Reset();
}
void WakeHandle::Reset() {
#if defined(_WIN32)
CloseSocket(read_);
CloseSocket(write_);
#else
if (read_ >= 0) {
close(read_);
}
if (write_ >= 0 && write_ != read_) {
close(write_);
}
#endif
read_ = write_ = InvalidSocket;
}
void WakeHandle::Signal() const {
// Fails only when already full, which means already signalled.
#if defined(__linux__)
const u64 one = 1;
[[maybe_unused]] const auto r = write(write_, &one, sizeof(one));
#elif defined(_WIN32)
const char byte = 1;
send(write_, &byte, 1, 0);
#else
const char byte = 1;
[[maybe_unused]] const auto r = write(write_, &byte, 1);
#endif
}
void WakeHandle::Drain() const {
char buf[64]; // eventfd needs at least 8
#ifdef _WIN32
while (recv(read_, buf, sizeof(buf), 0) > 0) {
}
#else
while (read(read_, buf, sizeof(buf)) > 0) {
}
#endif
}
WaitResult WaitOne(NativeSocket s, u32 interest, const WakeHandle& wake, Deadline deadline) {
#ifdef _WIN32
WSAPOLLFD fds[2]{};
#else
pollfd fds[2]{};
#endif
fds[0].fd = s;
fds[0].events = static_cast<short>(((interest & Readable) ? POLLIN : 0) |
((interest & Writable) ? POLLOUT : 0));
fds[1].fd = wake.PollHandle();
fds[1].events = POLLIN;
for (;;) {
fds[0].revents = fds[1].revents = 0;
#ifdef _WIN32
const int n = WSAPoll(fds, 2, TimeoutMs(deadline));
#else
const int n = poll(fds, 2, TimeoutMs(deadline));
#endif
if (n < 0) {
#ifndef _WIN32
if (errno == EINTR) {
continue;
}
#endif
return WaitResult::Failed;
}
if (fds[1].revents != 0) {
return WaitResult::Woken; // abort wins
}
if (fds[0].revents != 0) {
return WaitResult::Ready; // the retry reports any error
}
if (Expired(deadline)) {
return WaitResult::TimedOut;
}
// Woke early, wait again.
}
}
#if HOST_NET_EPOLL // epoll or wepoll
u32 ToEpoll(u32 events, u32 flags) {
u32 ev = EPOLLRDHUP;
if (events & EvIn) {
ev |= EPOLLIN;
}
if (events & EvOut) {
ev |= EPOLLOUT;
}
if (flags & OneShot) {
ev |= EPOLLONESHOT;
}
return ev;
}
u32 FromEpoll(u32 ev) {
u32 out = 0;
if (ev & EPOLLIN) {
out |= EvIn;
}
if (ev & EPOLLOUT) {
out |= EvOut;
}
if (ev & EPOLLERR) {
out |= EvErr;
}
if (ev & (EPOLLHUP | EPOLLRDHUP)) {
out |= EvHup; // TODO: guest may need RDHUP kept apart from HUP
}
return out;
}
Error EpollError() {
#ifdef _WIN32
// wepoll gives Win32 codes, not WSA ones. EEXIST/ENOENT are checked by the guest layer.
return Error::Internal;
#else
return LastError();
#endif
}
HostEpoll::HostEpoll() {
#ifdef _WIN32
handle_ = epoll_create1(0);
#else
handle_ = epoll_create1(EPOLL_CLOEXEC);
#endif
if (!Valid() || !wake_.Valid()) {
return;
}
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.u64 = ReservedTag;
epoll_ctl(handle_, EPOLL_CTL_ADD, wake_.PollHandle(), &ev);
}
HostEpoll::~HostEpoll() {
if (!Valid()) {
return;
}
#ifdef _WIN32
epoll_close(handle_);
#else
close(handle_);
#endif
}
bool HostEpoll::Valid() const {
#ifdef _WIN32
return handle_ != nullptr;
#else
return handle_ >= 0;
#endif
}
Error HostEpoll::Add(NativeSocket s, u64 tag, u32 events, u32 flags) {
epoll_event ev{};
ev.events = ToEpoll(events, flags);
ev.data.u64 = tag;
return epoll_ctl(handle_, EPOLL_CTL_ADD, s, &ev) == 0 ? Error::Ok : EpollError();
}
Error HostEpoll::Modify(NativeSocket s, u64 tag, u32 events, u32 flags) {
epoll_event ev{};
ev.events = ToEpoll(events, flags);
ev.data.u64 = tag;
return epoll_ctl(handle_, EPOLL_CTL_MOD, s, &ev) == 0 ? Error::Ok : EpollError();
}
Error HostEpoll::Remove(NativeSocket s) {
epoll_event ev{}; // must be non-null before Linux 2.6.9
return epoll_ctl(handle_, EPOLL_CTL_DEL, s, &ev) == 0 ? Error::Ok : EpollError();
}
EpollWaitResult HostEpoll::Wait(std::span<EpollEvent> out, Deadline deadline) {
std::array<epoll_event, 64> buf;
const int cap = static_cast<int>(std::min(out.size(), buf.size()));
if (cap == 0) {
return {0, false, Error::Inval};
}
for (;;) {
const int n = epoll_wait(handle_, buf.data(), cap, TimeoutMs(deadline));
if (n < 0) {
#ifndef _WIN32
if (errno == EINTR) {
continue;
}
#endif
return {0, false, EpollError()};
}
EpollWaitResult r{0, false, Error::Ok};
for (int i = 0; i < n; ++i) {
if (buf[i].data.u64 == ReservedTag) {
r.woken = true;
continue;
}
out[r.count++] = {buf[i].data.u64, FromEpoll(buf[i].events)};
}
if (n > 0 || Expired(deadline)) {
return r;
}
}
}
#else // HOST_NET_KQUEUE (macOS, FreeBSD)
void* TagToUdata(u64 tag) {
static_assert(sizeof(void*) >= sizeof(u64), "tags need 64-bit udata");
return reinterpret_cast<void*>(static_cast<uintptr_t>(tag));
}
u64 UdataToTag(void* udata) {
return static_cast<u64>(reinterpret_cast<uintptr_t>(udata));
}
Error KqueueApply(int kq, int s, u64 tag, u32 events, u32 flags) {
u16 arm = EV_ADD | EV_ENABLE | EV_RECEIPT;
if (flags & OneShot) {
arm |= EV_DISPATCH; // like EPOLLONESHOT
}
const u16 disarm = EV_DELETE | EV_RECEIPT;
// TODO: with no EvIn/EvOut, epoll still reports ERR/HUP but kqueue reports nothing.
// A hidden EVFILT_READ would fix it but give spurious EvIn.
struct kevent changes[2];
struct kevent results[2];
EV_SET(&changes[0], s, EVFILT_READ, (events & EvIn) ? arm : disarm, 0, 0, TagToUdata(tag));
EV_SET(&changes[1], s, EVFILT_WRITE, (events & EvOut) ? arm : disarm, 0, 0, TagToUdata(tag));
const int n = kevent(kq, changes, 2, results, 2, nullptr);
if (n < 0) {
return LastError();
}
for (int i = 0; i < n; ++i) {
// EV_RECEIPT returns an EV_ERROR entry per change, data 0 on success. ENOENT is
// deleting a filter that was never added.
if ((results[i].flags & EV_ERROR) && results[i].data != 0 && results[i].data != ENOENT) {
return TranslateNative(static_cast<int>(results[i].data));
}
}
return Error::Ok;
}
HostEpoll::HostEpoll() {
handle_ = kqueue();
if (handle_ < 0 || !wake_.Valid()) {
return;
}
SetCloseOnExec(handle_);
struct kevent kev;
EV_SET(&kev, wake_.PollHandle(), EVFILT_READ, EV_ADD, 0, 0, TagToUdata(ReservedTag));
kevent(handle_, &kev, 1, nullptr, 0, nullptr);
}
HostEpoll::~HostEpoll() {
if (handle_ >= 0) {
close(handle_);
}
}
bool HostEpoll::Valid() const {
return handle_ >= 0;
}
// Guest layer checks EEXIST/ENOENT, so Add and Modify are the same here.
Error HostEpoll::Add(NativeSocket s, u64 tag, u32 events, u32 flags) {
return KqueueApply(handle_, s, tag, events, flags);
}
Error HostEpoll::Modify(NativeSocket s, u64 tag, u32 events, u32 flags) {
return KqueueApply(handle_, s, tag, events, flags);
}
Error HostEpoll::Remove(NativeSocket s) {
return KqueueApply(handle_, s, 0, 0, 0);
}
EpollWaitResult HostEpoll::Wait(std::span<EpollEvent> out, Deadline deadline) {
std::array<struct kevent, 64> buf;
const int cap = static_cast<int>(std::min(out.size(), buf.size()));
if (cap == 0) {
return {0, false, Error::Inval};
}
for (;;) {
timespec ts{};
timespec* pts = nullptr;
if (deadline) {
const auto left = std::max(*deadline - Clock::now(), Clock::duration::zero());
const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(left).count();
ts.tv_sec = static_cast<time_t>(ns / 1'000'000'000);
ts.tv_nsec = static_cast<long>(ns % 1'000'000'000);
pts = &ts;
}
const int n = kevent(handle_, nullptr, 0, buf.data(), cap, pts);
if (n < 0) {
if (errno == EINTR) {
continue;
}
return {0, false, LastError()};
}
EpollWaitResult r{0, false, Error::Ok};
for (int i = 0; i < n; ++i) {
const u64 tag = UdataToTag(buf[i].udata);
if (tag == ReservedTag) {
r.woken = true;
continue;
}
u32 ev = buf[i].filter == EVFILT_READ ? EvIn : EvOut;
if (buf[i].flags & EV_EOF) {
ev |= EvHup;
if (buf[i].fflags != 0) {
ev |= EvErr; // fflags holds the socket error
}
}
// kqueue reports read and write separately, merge them.
bool merged = false;
for (s32 j = 0; j < r.count; ++j) {
if (out[j].tag == tag) {
out[j].events |= ev;
merged = true;
break;
}
}
if (!merged) {
out[r.count++] = {tag, ev};
}
}
if (n > 0 || Expired(deadline)) {
return r;
}
}
}
#endif
void HostEpoll::Wake() {
wake_.Signal();
}
void HostEpoll::DrainWake() {
wake_.Drain();
}
} // namespace Core::Net::Host
+238
View File
@@ -0,0 +1,238 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Backends:
// * Linux: poll + eventfd, epoll
// * macOS/FreeBSD: poll + pipe, kqueue
// * Windows: WSAPoll + loopback UDP pair, wepoll
#pragma once
#include <chrono>
#include <cstddef>
#include <optional>
#include <span>
#include "common/types.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
namespace Core::Net::Host {
#ifdef _WIN32
using NativeSocket = SOCKET;
inline constexpr NativeSocket InvalidSocket = INVALID_SOCKET;
#else
using NativeSocket = int;
inline constexpr NativeSocket InvalidSocket = -1;
#endif
using Clock = std::chrono::steady_clock;
// nullopt = no deadline
using Deadline = std::optional<Clock::time_point>;
// BSD SO_RCVTIMEO semantics, 0 = wait forever.
Deadline DeadlineFromSocketTimeout(std::chrono::microseconds timeout);
enum class Error : s32 {
Ok = 0,
Perm = 1,
NoEnt = 2,
Intr = 4,
Io = 5,
BadF = 9,
NoMem = 12,
Acces = 13,
Fault = 14,
NotBlk = 15, // abort with nothing blocked
Busy = 16,
Exist = 17,
NoDev = 19,
Inval = 22,
NFile = 23,
MFile = 24,
NoSpc = 28,
Pipe = 32,
WouldBlock = 35, // also EAGAIN
InProgress = 36,
Already = 37,
NotSock = 38,
DestAddrReq = 39,
MsgSize = 40,
ProtoType = 41,
NoProtoOpt = 42,
ProtoNoSupport = 43,
SocktNoSupport = 44,
OpNotSupp = 45,
PfNoSupport = 46,
AfNoSupport = 47,
AddrInUse = 48,
AddrNotAvail = 49,
NetDown = 50,
NetUnreach = 51,
NetReset = 52,
ConnAborted = 53,
ConnReset = 54,
NoBufs = 55,
IsConn = 56,
NotConn = 57,
TooManyRefs = 59,
TimedOut = 60,
ConnRefused = 61,
Loop = 62,
NameTooLong = 63,
HostDown = 64,
HostUnreach = 65,
NotEmpty = 66,
Canceled = 85,
Internal = 204, // host errors with no FreeBSD equivalent
};
Error TranslateNative(int native_code);
Error LastError();
struct IoResult {
s64 value;
Error error;
};
bool Initialize();
void Shutdown();
NativeSocket CreateSocket(int family, int type, int protocol, Error* error);
bool ConfigureSocket(NativeSocket s, int type);
void CloseSocket(NativeSocket s);
// Windows has no socketpair(), so it uses a loopback TCP connection (stream only).
Error CreateSocketPair(int family, int type, int protocol, NativeSocket out[2]);
Error PrepareBind(NativeSocket s, bool reuse_addr, bool reuse_port);
// SO_LINGER in seconds. Darwin's SO_LINGER counts clock ticks, SO_LINGER_SEC is the BSD one.
#ifdef __APPLE__
inline constexpr int LingerOption = SO_LINGER_SEC;
#else
inline constexpr int LingerOption = SO_LINGER;
#endif
// Call before closing a stream socket. Sorts out SO_LINGER for the guest's blocking mode.
void PrepareClose(NativeSocket s, bool guest_blocking);
Error Bind(NativeSocket s, const sockaddr* addr, socklen_t len);
Error Listen(NativeSocket s, int backlog);
Error ShutdownSocket(NativeSocket s, int how);
// Returns InProgress, never WouldBlock, while connecting.
Error Connect(NativeSocket s, const sockaddr* addr, socklen_t len);
// Reads and clears SO_ERROR.
Error PendingSocketError(NativeSocket s);
// Bytes ready to read (FIONREAD).
IoResult BytesReadable(NativeSocket s);
NativeSocket Accept(NativeSocket s, sockaddr* addr, socklen_t* len, Error* error);
// addr == nullptr uses send()/recv().
IoResult SendTo(NativeSocket s, const void* buf, size_t len, int flags, const sockaddr* addr,
socklen_t addr_len);
IoResult RecvFrom(NativeSocket s, void* buf, size_t len, int flags, sockaddr* addr,
socklen_t* addr_len);
// Pollable object another thread can signal to interrupt a wait.
class WakeHandle {
public:
WakeHandle();
~WakeHandle();
WakeHandle(const WakeHandle&) = delete;
WakeHandle& operator=(const WakeHandle&) = delete;
bool Valid() const {
return read_ != InvalidSocket;
}
// Thread-safe. Stays signalled until Drain().
void Signal() const;
void Drain() const;
NativeSocket PollHandle() const {
return read_;
}
private:
void Reset();
NativeSocket read_ = InvalidSocket;
NativeSocket write_ = InvalidSocket; // same as read_ for eventfd
};
// Wait on a single socket, for blocking guest calls.
enum Interest : u32 {
Readable = 1u << 0,
Writable = 1u << 1,
};
enum class WaitResult {
Ready, // includes error/hangup, retry the operation
TimedOut,
Woken, // abort or close
Failed, // see LastError()
};
WaitResult WaitOne(NativeSocket s, u32 interest, const WakeHandle& wake, Deadline deadline);
// Host epoll/kqueue backing one guest epoll.
enum EventBits : u32 {
EvIn = 1u << 0,
EvOut = 1u << 1,
EvErr = 1u << 2,
EvHup = 1u << 3,
// Guest only. A non-socket object such as a resolver has finished.
EvDescId = 1u << 4,
};
// Always level-triggered. The PS4 has no edge-triggered mode and wepoll ignores EPOLLET.
enum EpollFlags : u32 {
OneShot = 1u << 0, // re-arm with Modify()
};
// Used for the wake handle, not a valid caller tag.
inline constexpr u64 ReservedTag = ~0ull;
struct EpollEvent {
u64 tag;
u32 events; // EventBits
};
struct EpollWaitResult {
s32 count;
bool woken; // not drained, call DrainWake()
Error error;
};
class HostEpoll {
public:
HostEpoll();
~HostEpoll();
HostEpoll(const HostEpoll&) = delete;
HostEpoll& operator=(const HostEpoll&) = delete;
bool Valid() const;
Error Add(NativeSocket s, u64 tag, u32 events, u32 flags);
Error Modify(NativeSocket s, u64 tag, u32 events, u32 flags);
Error Remove(NativeSocket s);
// At most 64 events per call.
EpollWaitResult Wait(std::span<EpollEvent> out, Deadline deadline);
// Thread-safe. Stays signalled until DrainWake().
void Wake();
void DrainWake();
private:
#ifdef _WIN32
void* handle_ = nullptr; // wepoll HANDLE
#else
int handle_ = -1;
#endif
WakeHandle wake_;
};
} // namespace Core::Net::Host
+266
View File
@@ -0,0 +1,266 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include "core/net/p2p_codec.h"
namespace Core::Net::P2P {
using namespace Wire;
void Keyring::AddPeerKey(const Endpoint& peer, const KeyEntry& entry) {
std::scoped_lock lock{mutex};
const auto it = peer_keys.find(peer);
if (it != peer_keys.end() && it->second.entry.key == entry.key) {
++it->second.references;
} else {
peer_keys[peer] = {entry, 1};
}
}
bool Keyring::ReleasePeerKey(const Endpoint& peer, const P2PKey& key, u8 id) {
std::scoped_lock lock{mutex};
const auto it = peer_keys.find(peer);
if (it == peer_keys.end() || !(it->second.entry.key == key) || it->second.entry.id != id) {
return false;
}
if (--it->second.references == 0) {
peer_keys.erase(it);
}
return true;
}
void Keyring::RemoveKeysWithId(u8 id) {
std::scoped_lock lock{mutex};
std::erase_if(peer_keys, [id](const auto& entry) { return entry.second.entry.id == id; });
}
void Keyring::SetPeerKey(const Endpoint& peer, const P2PKey& key) {
std::scoped_lock lock{mutex};
peer_keys[peer] = {KeyEntry{key}, 1};
}
void Keyring::RemovePeerKey(const Endpoint& peer) {
std::scoped_lock lock{mutex};
peer_keys.erase(peer);
}
void Keyring::SetWildcardKey(std::optional<P2PKey> key) {
std::scoped_lock lock{mutex};
wildcard_key = key;
}
std::optional<P2PKey> Keyring::GetWildcardKey() const {
std::scoped_lock lock{mutex};
return wildcard_key;
}
void Keyring::SetDefaultKey(std::optional<P2PKey> key) {
std::scoped_lock lock{mutex};
default_key = key;
}
std::optional<P2PKey> Keyring::GetDefaultKey() const {
std::scoped_lock lock{mutex};
return default_key;
}
std::optional<P2PKey> Keyring::Find(const Endpoint& peer) const {
std::scoped_lock lock{mutex};
if (const auto it = peer_keys.find(peer); it != peer_keys.end()) {
return it->second.entry.key;
}
return wildcard_key ? wildcard_key : default_key;
}
std::optional<KeyEntry> Keyring::FindPeerEntry(const Endpoint& peer) const {
std::scoped_lock lock{mutex};
if (const auto it = peer_keys.find(peer); it != peer_keys.end()) {
return it->second.entry;
}
return std::nullopt;
}
void Keyring::SetCommunicationId(std::optional<CommunicationId> id) {
std::scoped_lock lock{mutex};
communication_id = id;
}
std::optional<CommunicationId> Keyring::GetCommunicationId() const {
std::scoped_lock lock{mutex};
return communication_id;
}
FramingCodec::FramingCodec(std::shared_ptr<Keyring> keyring, BroadcastObserver broadcast_observer)
: keyring(std::move(keyring)), broadcast_observer(std::move(broadcast_observer)) {}
Codec::Decoded FramingCodec::Decode(std::span<const u8> packet, const Endpoint& from) {
Decoded out;
if (packet.size() < 2 || packet[0] != Marker || (packet[1] & FlagValid) == 0) {
return out;
}
const u8 flags = packet[1];
const u8 kind = flags & 0x0F;
if (kind < KindDatagram || kind > KindStream + 3) {
return out;
}
const bool stream = kind >= KindStream;
size_t offset = 2;
u16 src_vport = 0;
u16 dst_vport = 0;
if (flags & FlagBroadcast) {
// Layout unconfirmed, report a few so we can check
if (broadcast_observer &&
reported_broadcasts.fetch_add(1, std::memory_order_relaxed) < MaxReportedBroadcasts) {
broadcast_observer(packet, from);
}
if (stream) {
return out; // no broadcast TCP
}
offset = 4;
if (packet.size() < offset) {
return out;
}
dst_vport = static_cast<u16>((packet[2] << 8) | packet[3]);
src_vport = dst_vport;
} else if (!stream) {
offset = 6;
if (packet.size() < offset) {
return out;
}
src_vport = static_cast<u16>((packet[2] << 8) | packet[3]);
dst_vport = static_cast<u16>((packet[4] << 8) | packet[5]);
}
std::optional<CommunicationId> communication_id;
if (flags & FlagDigest) {
if (packet.size() < offset + 4) {
return out;
}
communication_id.emplace();
std::memcpy(communication_id->data(), packet.data() + offset, 4);
offset += 4;
}
const bool signaling = !stream && dst_vport == SignalingVport;
// TODO: drop signaling once the communication ID is set?
if (!signaling) {
if (const auto expected = keyring->GetCommunicationId();
expected && communication_id != expected) {
return out;
}
}
const u8 mode = kind - (stream ? KindStream : KindDatagram);
const auto body = packet.subspan(offset);
if (mode != 0) {
const auto key = keyring->Find(from);
if (!key || !UnprotectPayload(body, (mode & 1) != 0, (mode & 2) != 0, *key, out.payload)) {
return out; // no key or bad signature
}
} else {
out.payload.assign(body.begin(), body.end());
}
out.kind = stream ? Kind::Stream : signaling ? Kind::Signaling : Kind::Datagram;
out.src_vport = src_vport;
out.dst_vport = dst_vport;
return out;
}
std::vector<u8> FramingCodec::Protect(std::span<const u8> data, const Endpoint& to,
Protection protection, u8& mode) const {
mode = 0;
if (protection.crypto || protection.signature) {
if (const auto key = keyring->Find(to)) {
std::vector<u8> out;
if (ProtectPayload(data, protection.crypto, protection.signature, *key, out)) {
mode =
static_cast<u8>((protection.crypto ? 1 : 0) | (protection.signature ? 2 : 0));
return out;
}
}
// TODO: this sends plain. Should we drop instead?
}
return {data.begin(), data.end()};
}
std::vector<u8> FramingCodec::EncodeStream(std::span<const u8> tcp_segment, const Endpoint& to,
Protection protection) {
u8 mode = 0;
const auto body = Protect(tcp_segment, to, protection, mode);
const auto communication_id = keyring->GetCommunicationId();
std::vector<u8> packet{Marker, static_cast<u8>(FlagValid | (KindStream + mode) |
(communication_id ? FlagDigest : 0))};
if (communication_id) {
packet.insert(packet.end(), communication_id->begin(), communication_id->end());
}
packet.insert(packet.end(), body.begin(), body.end());
return packet;
}
std::vector<u8> FrameDatagram(u16 src_vport, u16 dst_vport, u8 kind,
const std::optional<CommunicationId>& communication_id,
std::span<const u8> body) {
std::vector<u8> packet{Marker,
static_cast<u8>(FlagValid | kind | (communication_id ? FlagDigest : 0)),
static_cast<u8>(src_vport >> 8),
static_cast<u8>(src_vport),
static_cast<u8>(dst_vport >> 8),
static_cast<u8>(dst_vport)};
if (communication_id) {
packet.insert(packet.end(), communication_id->begin(), communication_id->end());
}
packet.insert(packet.end(), body.begin(), body.end());
return packet;
}
std::vector<u8> FramingCodec::EncodeDatagram(u16 src_vport, u16 dst_vport,
std::span<const u8> payload, const Endpoint& to,
Protection protection) {
u8 mode = 0;
const auto body = Protect(payload, to, protection, mode);
return FrameDatagram(src_vport, dst_vport, KindDatagram + mode, keyring->GetCommunicationId(),
body);
}
std::vector<u8> FramingCodec::EncodeSignaling(std::span<const u8> data, const Endpoint&) {
return FrameDatagram(SignalingVport, SignalingVport, KindDatagram, std::nullopt, data);
}
PseudoHeader FramingCodec::StreamPseudoHeader(const Endpoint& peer) {
std::scoped_lock lock{pseudo_mutex};
if (const auto it = pseudo_cache.find(peer); it != pseudo_cache.end()) {
return it->second;
}
PseudoHeader pseudo;
pseudo.family = peer.Family();
const bool v6 = peer.Family() == AF_INET6;
const size_t size = v6 ? 16 : 4;
const auto address_of = [v6](const sockaddr_storage& ss) -> const void* {
return v6 ? static_cast<const void*>(&reinterpret_cast<const sockaddr_in6*>(&ss)->sin6_addr)
: static_cast<const void*>(&reinterpret_cast<const sockaddr_in*>(&ss)->sin_addr);
};
std::memcpy(pseudo.remote.data(), address_of(peer.addr), size);
Host::Error error;
const auto probe = Host::CreateSocket(peer.Family(), SOCK_DGRAM, 0, &error);
if (probe != Host::InvalidSocket) {
sockaddr_storage local{};
socklen_t local_len = sizeof(local);
if (Host::Connect(probe, peer.Sockaddr(), peer.Length()) == Host::Error::Ok &&
getsockname(probe, reinterpret_cast<sockaddr*>(&local), &local_len) == 0) {
std::memcpy(pseudo.local.data(), address_of(local), size);
}
Host::CloseSocket(probe);
}
if (pseudo_cache.size() >= 256) {
pseudo_cache.clear();
}
pseudo_cache.emplace(peer, pseudo);
return pseudo;
}
} // namespace Core::Net::P2P
+108
View File
@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// PS4 P2P wire format. vports are big endian.
//
// Datagram: [0xFF][flags|kind][src vport 2][dst vport 2][digest 4?][payload]
// Broadcast: [0xFF][flags|0x40|kind][port 2][digest 4?][payload], datagrams only.
// Broadcast layout is assumed, not confirmed yet.
// Stream: [0xFF][flags|kind][digest 4?][TCP segment]
// Signaling: plain datagram from and to vport 0xFFFF.
//
// flags: 0x80 valid, 0x40 broadcast, 0x20 digest.
// kind: UDP 3-6, TCP 7-10. Base +1 for crypto, +2 for signature.
// digest: truncated HMAC-SHA1 of the 16-byte value NP installs (ioctl 0xd8).
// We call it the communication ID.
#pragma once
#include <map>
#include <mutex>
#include <optional>
#include "core/net/p2p_crypto.h"
#include "core/net/p2p_transport.h"
namespace Core::Net::P2P {
namespace Wire {
constexpr u8 Marker = 0xFF;
constexpr u8 FlagValid = 0x80;
constexpr u8 FlagBroadcast = 0x40;
constexpr u8 FlagDigest = 0x20; // communication ID follows
constexpr u8 KindDatagram = 3;
constexpr u8 KindStream = 7;
constexpr u16 SignalingVport = 0xFFFF;
} // namespace Wire
using CommunicationId = std::array<u8, 4>;
struct KeyEntry {
P2PKey key;
u16 local_port = 0; // network byte order
u8 flags = 0;
u8 id = 0; // groups keys for removal
};
class Keyring {
public:
// Replaces the peer's key. The same key again just adds a reference,
// since NP installs it once per user.
void AddPeerKey(const Endpoint& peer, const KeyEntry& entry);
// Drops a reference. False if that key and id aren't installed.
bool ReleasePeerKey(const Endpoint& peer, const P2PKey& key, u8 id);
void RemoveKeysWithId(u8 id);
void SetPeerKey(const Endpoint& peer, const P2PKey& key);
void RemovePeerKey(const Endpoint& peer);
void SetWildcardKey(std::optional<P2PKey> key);
std::optional<P2PKey> GetWildcardKey() const;
void SetDefaultKey(std::optional<P2PKey> key);
std::optional<P2PKey> GetDefaultKey() const;
std::optional<P2PKey> Find(const Endpoint& peer) const;
std::optional<KeyEntry> FindPeerEntry(const Endpoint& peer) const;
void SetCommunicationId(std::optional<CommunicationId> id);
std::optional<CommunicationId> GetCommunicationId() const;
private:
struct Installed {
KeyEntry entry;
int references = 1;
};
mutable std::mutex mutex;
std::map<Endpoint, Installed> peer_keys;
std::optional<P2PKey> wildcard_key;
std::optional<P2PKey> default_key;
std::optional<CommunicationId> communication_id;
};
class FramingCodec final : public Codec {
public:
// Gets dropped broadcast packets, for debugging.
using BroadcastObserver = std::function<void(std::span<const u8>, const Endpoint&)>;
static constexpr int MaxReportedBroadcasts = 16; // per session
explicit FramingCodec(std::shared_ptr<Keyring> keyring,
BroadcastObserver broadcast_observer = {});
Decoded Decode(std::span<const u8> packet, const Endpoint& from) override;
std::vector<u8> EncodeStream(std::span<const u8> tcp_segment, const Endpoint& to,
Protection protection) override;
std::vector<u8> EncodeDatagram(u16 src_vport, u16 dst_vport, std::span<const u8> payload,
const Endpoint& to, Protection protection) override;
std::vector<u8> EncodeSignaling(std::span<const u8> data, const Endpoint& to) override;
PseudoHeader StreamPseudoHeader(const Endpoint& peer) override;
private:
// Sends plain if there is no key. mode gets the kind offset used.
std::vector<u8> Protect(std::span<const u8> data, const Endpoint& to, Protection protection,
u8& mode) const;
std::shared_ptr<Keyring> keyring;
BroadcastObserver broadcast_observer;
std::atomic<int> reported_broadcasts{0};
std::mutex pseudo_mutex;
std::map<Endpoint, PseudoHeader> pseudo_cache;
};
} // namespace Core::Net::P2P
+108
View File
@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include "core/net/p2p_crypto.h"
namespace Core::Net::P2P {
constexpr size_t IvSeedSize = 4;
constexpr size_t SignatureSize = 4;
bool Crypt(std::vector<u8>& payload, const P2PKey& key, const std::array<u8, 4>& iv_seed,
bool encrypt) {
std::array<u8, 16> iv;
for (size_t i = 0; i < iv.size(); ++i) {
iv[i] = iv_seed[i & 3];
}
EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (!context) {
return false;
}
int output_size = 0;
int final_size = 0;
const bool ok = EVP_CipherInit_ex(context, EVP_aes_128_cfb128(), nullptr, key.value.data(),
iv.data(), encrypt ? 1 : 0) == 1 &&
EVP_CipherUpdate(context, payload.data(), &output_size, payload.data(),
static_cast<int>(payload.size())) == 1 &&
EVP_CipherFinal_ex(context, payload.data() + output_size, &final_size) == 1;
EVP_CIPHER_CTX_free(context);
return ok && static_cast<size_t>(output_size + final_size) == payload.size();
}
std::array<u8, SignatureSize> Sign(std::span<const u8> body, const P2PKey& key) {
std::array<u8, EVP_MAX_MD_SIZE> digest{};
unsigned int digest_size = 0;
HMAC(EVP_sha1(), key.value.data(), static_cast<int>(key.value.size()), body.data(), body.size(),
digest.data(), &digest_size);
std::array<u8, SignatureSize> signature{};
std::copy_n(digest.begin(), signature.size(), signature.begin());
return signature;
}
std::array<u8, 4> DeriveCommunicationId(std::span<const u8, 16> value) {
std::array<u8, EVP_MAX_MD_SIZE> digest{};
unsigned int digest_size = 0;
const u8 empty = 0;
HMAC(EVP_sha1(), value.data(), static_cast<int>(value.size()), &empty, 0, digest.data(),
&digest_size);
std::array<u8, 4> id{};
std::copy_n(digest.begin(), id.size(), id.begin());
return id;
}
bool ProtectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
std::vector<u8>& output) {
std::vector<u8> body(data.begin(), data.end());
std::array<u8, IvSeedSize> iv{};
if (encrypt &&
(RAND_bytes(iv.data(), static_cast<int>(iv.size())) != 1 || !Crypt(body, key, iv, true))) {
return false;
}
output.clear();
output.reserve(body.size() + (encrypt ? IvSeedSize : 0) + (sign ? SignatureSize : 0));
if (encrypt) {
output.insert(output.end(), iv.begin(), iv.end());
}
if (sign) {
const auto signature = Sign(body, key); // signs ciphertext
output.insert(output.end(), signature.begin(), signature.end());
}
output.insert(output.end(), body.begin(), body.end());
return true;
}
bool UnprotectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
std::vector<u8>& output) {
const size_t prefix = (encrypt ? IvSeedSize : 0) + (sign ? SignatureSize : 0);
if (data.size() < prefix) {
return false;
}
std::array<u8, IvSeedSize> iv{};
std::array<u8, SignatureSize> signature{};
size_t offset = 0;
if (encrypt) {
std::copy_n(data.begin(), iv.size(), iv.begin());
offset += iv.size();
}
if (sign) {
std::copy_n(data.begin() + offset, signature.size(), signature.begin());
offset += signature.size();
}
output.assign(data.begin() + offset, data.end());
if (sign) {
const auto expected = Sign(output, key);
if (CRYPTO_memcmp(signature.data(), expected.data(), signature.size()) != 0) {
return false;
}
}
return !encrypt || Crypt(output, key, iv, false);
}
} // namespace Core::Net::P2P
+29
View File
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include <vector>
#include "common/types.h"
namespace Core::Net::P2P {
struct P2PKey {
std::array<u8, 16> value{};
bool operator==(const P2PKey&) const = default;
};
bool ProtectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
std::vector<u8>& output);
// Fails on short packet, bad signature or cipher error.
bool UnprotectPayload(std::span<const u8> data, bool encrypt, bool sign, const P2PKey& key,
std::vector<u8>& output);
// First 4 bytes of HMAC-SHA1 over an empty message, keyed with the title's 16-byte value.
std::array<u8, 4> DeriveCommunicationId(std::span<const u8, 16> value);
} // namespace Core::Net::P2P
+878
View File
@@ -0,0 +1,878 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "core/net/p2p_tcp.h"
namespace Core::Net::P2P {
using namespace TcpFlag;
namespace {
u16 Read16(const u8* p) {
return static_cast<u16>((p[0] << 8) | p[1]);
}
u32 Read32(const u8* p) {
return (static_cast<u32>(p[0]) << 24) | (static_cast<u32>(p[1]) << 16) |
(static_cast<u32>(p[2]) << 8) | p[3];
}
void Write16(u8* p, u16 v) {
p[0] = static_cast<u8>(v >> 8);
p[1] = static_cast<u8>(v);
}
void Write32(u8* p, u32 v) {
p[0] = static_cast<u8>(v >> 24);
p[1] = static_cast<u8>(v >> 16);
p[2] = static_cast<u8>(v >> 8);
p[3] = static_cast<u8>(v);
}
u64 SumWords(u64 sum, const u8* data, size_t size) {
size_t i = 0;
for (; i + 1 < size; i += 2) {
sum += Read16(data + i);
}
if (i < size) {
sum += static_cast<u64>(data[i]) << 8;
}
return sum;
}
} // namespace
const char* ToString(TcpState state) {
switch (state) {
case TcpState::Closed:
return "Closed";
case TcpState::SynSent:
return "SynSent";
case TcpState::SynReceived:
return "SynReceived";
case TcpState::Established:
return "Established";
case TcpState::FinWait1:
return "FinWait1";
case TcpState::FinWait2:
return "FinWait2";
case TcpState::CloseWait:
return "CloseWait";
case TcpState::Closing:
return "Closing";
case TcpState::LastAck:
return "LastAck";
case TcpState::TimeWait:
return "TimeWait";
}
return "?";
}
u16 TcpChecksum(int family, const u8* src_addr, const u8* dst_addr, std::span<const u8> segment) {
const size_t address_size = family == AF_INET6 ? 16 : 4;
u64 sum = 0;
sum = SumWords(sum, src_addr, address_size);
sum = SumWords(sum, dst_addr, address_size);
const u64 length = segment.size();
sum += (length >> 16) + (length & 0xffff);
sum += 6; // IPPROTO_TCP
sum = SumWords(sum, segment.data(), segment.size());
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
return static_cast<u16>(~sum);
}
std::optional<TcpSegment> ParseTcpSegment(std::span<const u8> bytes, const PseudoHeader* pseudo) {
if (bytes.size() < 20) {
return std::nullopt;
}
const u8* b = bytes.data();
const size_t header_size = static_cast<size_t>(b[12] >> 4) * 4;
if (header_size < 20 || header_size > bytes.size()) {
return std::nullopt;
}
if (pseudo != nullptr &&
TcpChecksum(pseudo->family, pseudo->remote.data(), pseudo->local.data(), bytes) != 0) {
return std::nullopt;
}
TcpSegment seg{};
seg.src_port = Read16(b);
seg.dst_port = Read16(b + 2);
seg.seq = Read32(b + 4);
seg.ack = Read32(b + 8);
seg.flags = b[13];
seg.window = Read16(b + 14);
for (size_t i = 20; i < header_size;) {
const u8 kind = b[i];
if (kind == 0) {
break;
}
if (kind == 1) {
++i;
continue;
}
if (i + 1 >= header_size) {
break;
}
const u8 length = b[i + 1];
if (length < 2 || i + length > header_size) {
break;
}
if (kind == 2 && length == 4) {
seg.mss = Read16(b + i + 2);
}
i += length;
}
seg.payload = bytes.subspan(header_size);
return seg;
}
std::vector<u8> BuildTcpSegment(const PseudoHeader& pseudo, u16 src_port, u16 dst_port, u32 seq,
u32 ack, u8 flags, u16 window, std::optional<u16> mss,
std::span<const u8> payload) {
const size_t header_size = mss ? 24 : 20;
std::vector<u8> s(header_size + payload.size());
Write16(s.data(), src_port);
Write16(s.data() + 2, dst_port);
Write32(s.data() + 4, seq);
Write32(s.data() + 8, ack);
s[12] = static_cast<u8>((header_size / 4) << 4);
s[13] = flags;
Write16(s.data() + 14, window);
if (mss) {
s[20] = 2;
s[21] = 4;
Write16(s.data() + 22, *mss);
}
std::copy(payload.begin(), payload.end(), s.begin() + header_size);
Write16(s.data() + 16,
TcpChecksum(pseudo.family, pseudo.local.data(), pseudo.remote.data(), s));
return s;
}
std::optional<std::vector<u8>> BuildResetFor(const PseudoHeader& pseudo, const TcpSegment& seg) {
if (seg.flags & Rst) {
return std::nullopt;
}
if (seg.flags & Ack) {
return BuildTcpSegment(pseudo, seg.dst_port, seg.src_port, seg.ack, 0, Rst, 0, std::nullopt,
{});
}
const u32 length = static_cast<u32>(seg.payload.size()) + ((seg.flags & Syn) ? 1 : 0) +
((seg.flags & Fin) ? 1 : 0);
return BuildTcpSegment(pseudo, seg.dst_port, seg.src_port, 0, seg.seq + length, Rst | Ack, 0,
std::nullopt, {});
}
TcpConnection::TcpConnection(const TcpConfig& config, const PseudoHeader& pseudo, u16 local_port,
u16 remote_port, u32 iss, Emit emit)
: config_(config), pseudo_(pseudo), local_port_(local_port), remote_port_(remote_port),
emit_(std::move(emit)), iss_(iss), snd_una_(iss), snd_nxt_(iss), snd_max_(iss),
buf_seq_(iss + 1), rto_(config.initial_rto) {}
bool TcpConnection::Synchronized() const {
switch (state_) {
case TcpState::Closed:
case TcpState::SynSent:
case TcpState::SynReceived:
return false;
default:
return true;
}
}
u16 TcpConnection::ReceiveWindow() const {
const size_t used = recv_buf_.size();
if (used >= config_.recv_buffer) {
return 0;
}
return static_cast<u16>(std::min<size_t>(config_.recv_buffer - used, 65535));
}
u32 TcpConnection::FinSeq() const {
return buf_seq_ + static_cast<u32>(send_buf_.size());
}
bool TcpConnection::FinSent() const {
return fin_queued_ && SeqGt(snd_max_, FinSeq());
}
bool TcpConnection::FinAcked() const {
return fin_queued_ && SeqGt(snd_una_, FinSeq());
}
size_t TcpConnection::SendSpace() const {
return send_buf_.size() >= config_.send_buffer ? 0 : config_.send_buffer - send_buf_.size();
}
bool TcpConnection::Readable() const {
return !recv_buf_.empty() || fin_received_ || error_ != Error::Ok || state_ == TcpState::Closed;
}
bool TcpConnection::Writable() const {
if (error_ != Error::Ok || state_ == TcpState::Closed) {
return true; // wake waiters so they see the error
}
return (state_ == TcpState::Established || state_ == TcpState::CloseWait) && !fin_queued_ &&
SendSpace() > 0;
}
void TcpConnection::EmitSegment(u32 seq, u8 flags, std::span<const u8> payload, bool with_mss) {
const u16 window = ReceiveWindow();
const u32 ack = (flags & Ack) ? rcv_nxt_ : 0;
auto segment =
BuildTcpSegment(pseudo_, local_port_, remote_port_, seq, ack, flags, window,
with_mss ? std::optional<u16>{config_.mss} : std::nullopt, payload);
if (flags & Ack) {
last_advertised_window_ = window;
unacked_segments_ = 0;
delayed_ack_deadline_.reset();
}
emit_(segment);
}
void TcpConnection::SendAck() {
// Not snd_nxt_, which is rewound after a timeout. BSD drops ACKs with an old seq.
EmitSegment(snd_max_, Ack, {});
}
void TcpConnection::EmitReset(u32 seq) {
emit_(BuildTcpSegment(pseudo_, local_port_, remote_port_, seq, 0, Rst, 0, std::nullopt, {}));
}
void TcpConnection::SendData(u32 seq, u32 len) {
std::vector<u8> payload(len);
const auto begin = send_buf_.begin() + static_cast<std::ptrdiff_t>(seq - buf_seq_);
std::copy(begin, begin + len, payload.begin());
const bool last = seq + len == FinSeq();
EmitSegment(seq, Ack | (last ? Psh : 0), payload);
}
void TcpConnection::ArmRetransmit(Clock::time_point now) {
if (!retransmit_deadline_) {
retransmit_deadline_ = now + rto_;
}
}
void TcpConnection::TrySend(Clock::time_point now) {
switch (state_) {
case TcpState::Established:
case TcpState::CloseWait:
case TcpState::FinWait1:
case TcpState::Closing:
case TcpState::LastAck:
break;
default:
return;
}
for (;;) {
if (FinSent() && snd_nxt_ == FinSeq() + 1) {
return; // FIN in flight
}
const u32 offset = snd_nxt_ - buf_seq_;
const u32 available =
offset < send_buf_.size() ? static_cast<u32>(send_buf_.size()) - offset : 0;
const u32 flight = snd_nxt_ - snd_una_;
const u32 window = std::min(snd_wnd_, cwnd_);
const u32 usable = window > flight ? window - flight : 0;
const u32 len = std::min({available, usable, static_cast<u32>(snd_mss_)});
if (len == 0) {
if (available == 0 && fin_queued_ && snd_nxt_ == FinSeq()) {
EmitSegment(snd_nxt_, Fin | Ack, {});
++snd_nxt_;
if (SeqGt(snd_nxt_, snd_max_)) {
snd_max_ = snd_nxt_;
}
ArmRetransmit(now);
} else if (available > 0 && snd_wnd_ == 0 && snd_max_ == snd_una_ &&
!persist_deadline_) {
persist_interval_ = rto_;
persist_deadline_ = now + persist_interval_;
}
return;
}
SendData(snd_nxt_, len);
if (!rtt_active_ && snd_nxt_ == snd_max_) {
rtt_active_ = true; // don't time retransmits
rtt_seq_ = snd_nxt_;
rtt_start_ = now;
}
snd_nxt_ += len;
if (SeqGt(snd_nxt_, snd_max_)) {
snd_max_ = snd_nxt_;
}
ArmRetransmit(now);
}
}
void TcpConnection::RetransmitFirst() {
++retransmissions_;
rtt_active_ = false;
const u32 offset = snd_una_ - buf_seq_;
if (offset < send_buf_.size()) {
const u32 len = std::min<u32>(static_cast<u32>(send_buf_.size()) - offset, snd_mss_);
SendData(snd_una_, len);
} else if (FinSent()) {
EmitSegment(FinSeq(), Fin | Ack, {});
}
}
void TcpConnection::Connect(Clock::time_point now) {
state_ = TcpState::SynSent;
snd_nxt_ = snd_max_ = iss_ + 1;
EmitSegment(iss_, Syn, {}, true);
rtt_active_ = true;
rtt_seq_ = iss_;
rtt_start_ = now;
ArmRetransmit(now);
}
void TcpConnection::AcceptSyn(const TcpSegment& syn, Clock::time_point now) {
irs_ = syn.seq;
rcv_nxt_ = irs_ + 1;
snd_mss_ = std::min<u16>(syn.mss.value_or(536), config_.mss);
cwnd_ = std::min<u32>(4u * snd_mss_, std::max<u32>(2u * snd_mss_, 4380));
snd_wnd_ = syn.window;
snd_wl1_ = syn.seq;
snd_wl2_ = iss_;
state_ = TcpState::SynReceived;
snd_nxt_ = snd_max_ = iss_ + 1;
EmitSegment(iss_, Syn | Ack, {}, true);
rtt_active_ = true;
rtt_seq_ = iss_;
rtt_start_ = now;
ArmRetransmit(now);
}
void TcpConnection::HandleSynSent(const TcpSegment& seg, Clock::time_point now) {
const bool ack_ok = SeqGt(seg.ack, iss_) && SeqLe(seg.ack, snd_max_);
if ((seg.flags & Ack) && !ack_ok) {
if (!(seg.flags & Rst)) {
EmitReset(seg.ack);
}
return;
}
if (seg.flags & Rst) {
if (seg.flags & Ack) {
Terminate(Error::ConnRefused);
}
return;
}
if (!(seg.flags & Syn)) {
return;
}
irs_ = seg.seq;
rcv_nxt_ = irs_ + 1;
snd_mss_ = std::min<u16>(seg.mss.value_or(536), config_.mss);
cwnd_ = std::min<u32>(4u * snd_mss_, std::max<u32>(2u * snd_mss_, 4380));
snd_wnd_ = seg.window;
snd_wl1_ = seg.seq;
snd_wl2_ = seg.ack;
if (seg.flags & Ack) {
snd_una_ = seg.ack;
if (rtt_active_ && SeqGt(seg.ack, rtt_seq_)) {
SampleRtt(now - rtt_start_);
rtt_active_ = false;
}
retries_ = 0;
retransmit_deadline_.reset();
state_ = TcpState::Established;
SendAck();
TrySend(now);
} else {
state_ = TcpState::SynReceived;
EmitSegment(iss_, Syn | Ack, {}, true);
}
}
bool TcpConnection::Acceptable(u32 seq, u32 len) const {
const u32 window = ReceiveWindow();
const auto in_window = [&](u32 s) { return SeqLe(rcv_nxt_, s) && SeqLt(s, rcv_nxt_ + window); };
if (len == 0) {
return window == 0 ? seq == rcv_nxt_ : in_window(seq);
}
if (window == 0) {
return false;
}
return in_window(seq) || in_window(seq + len - 1);
}
void TcpConnection::OnSegment(const TcpSegment& seg, Clock::time_point now) {
switch (state_) {
case TcpState::Closed:
return;
case TcpState::SynSent:
HandleSynSent(seg, now);
return;
case TcpState::TimeWait:
if ((seg.flags & Fin) && !(seg.flags & Rst)) {
SendAck();
time_wait_deadline_ = now + config_.time_wait;
}
return;
default:
break;
}
// Peer resent its SYN, so our SYN-ACK was lost.
if (state_ == TcpState::SynReceived && (seg.flags & (Syn | Ack | Rst)) == Syn &&
seg.seq == irs_) {
++retransmissions_;
EmitSegment(iss_, Syn | Ack, {}, true);
return;
}
const u32 length = static_cast<u32>(seg.payload.size()) + ((seg.flags & Syn) ? 1 : 0) +
((seg.flags & Fin) ? 1 : 0);
bool ack_only = false;
if (!Acceptable(seg.seq, length)) {
if (ReceiveWindow() == 0 && seg.seq == rcv_nxt_ && (seg.flags & Ack) &&
!(seg.flags & (Rst | Syn))) {
ack_only = true;
} else {
if (!(seg.flags & Rst)) {
SendAck();
}
return;
}
}
if (seg.flags & Rst) {
if (seg.seq == rcv_nxt_) {
const bool closing = state_ == TcpState::Closing || state_ == TcpState::LastAck;
Terminate(closing ? Error::Ok
: state_ == TcpState::SynReceived ? Error::ConnRefused
: Error::ConnReset);
} else {
SendAck();
}
return;
}
if (seg.flags & Syn) {
SendAck();
return;
}
if (!(seg.flags & Ack)) {
return;
}
if (state_ == TcpState::SynReceived) {
if (!(SeqGt(seg.ack, snd_una_) && SeqLe(seg.ack, snd_max_))) {
EmitReset(seg.ack);
return;
}
state_ = TcpState::Established;
snd_wnd_ = seg.window;
snd_wl1_ = seg.seq;
snd_wl2_ = seg.ack;
}
if (!ProcessAck(seg, now)) {
return;
}
if (ack_only) {
SendAck(); // window still closed
return;
}
ProcessData(seg, now);
TrySend(now);
}
bool TcpConnection::ProcessAck(const TcpSegment& seg, Clock::time_point now) {
if (SeqGt(seg.ack, snd_max_)) {
SendAck(); // acks unsent data
return false;
}
if (SeqGt(seg.ack, snd_una_)) {
OnNewAck(seg.ack, now);
} else if (seg.ack == snd_una_ && seg.payload.empty() && !(seg.flags & (Syn | Fin)) &&
seg.window == snd_wnd_ && snd_max_ != snd_una_) {
OnDuplicateAck();
}
if (SeqLt(snd_wl1_, seg.seq) || (snd_wl1_ == seg.seq && SeqLe(snd_wl2_, seg.ack))) {
snd_wnd_ = seg.window;
snd_wl1_ = seg.seq;
snd_wl2_ = seg.ack;
if (snd_wnd_ > 0) {
persist_deadline_.reset();
}
}
if (FinAcked()) {
switch (state_) {
case TcpState::FinWait1:
state_ = TcpState::FinWait2;
break;
case TcpState::Closing:
EnterTimeWait(now);
return false;
case TcpState::LastAck:
state_ = TcpState::Closed;
ClearTimers();
return false;
default:
break;
}
}
return true;
}
void TcpConnection::OnNewAck(u32 ack, Clock::time_point now) {
const u32 acked = ack - snd_una_;
const u32 data_acked = SeqGt(ack, buf_seq_)
? std::min<u32>(ack - buf_seq_, static_cast<u32>(send_buf_.size()))
: 0;
send_buf_.erase(send_buf_.begin(), send_buf_.begin() + data_acked);
buf_seq_ += data_acked;
snd_una_ = ack;
if (SeqLt(snd_nxt_, snd_una_)) {
snd_nxt_ = snd_una_;
}
if (rtt_active_ && SeqGt(ack, rtt_seq_)) {
SampleRtt(now - rtt_start_);
rtt_active_ = false;
}
retries_ = 0;
if (in_recovery_) {
if (SeqGe(ack, recover_)) {
in_recovery_ = false;
cwnd_ = ssthresh_;
} else {
// NewReno partial ACK
RetransmitFirst();
cwnd_ = cwnd_ > acked ? cwnd_ - acked + snd_mss_ : snd_mss_;
}
} else if (cwnd_ < ssthresh_) {
cwnd_ += std::min<u32>(acked, snd_mss_); // slow start
} else {
cwnd_ += std::max<u32>(1, snd_mss_ * snd_mss_ / std::max<u32>(cwnd_, 1));
}
cwnd_ = std::min<u32>(cwnd_, 1u << 30);
dupacks_ = 0;
retransmit_deadline_.reset();
if (snd_una_ != snd_max_) {
retransmit_deadline_ = now + rto_;
}
}
void TcpConnection::OnDuplicateAck() {
if (in_recovery_) {
cwnd_ += snd_mss_;
return;
}
if (++dupacks_ == 3) {
const u32 flight = snd_max_ - snd_una_;
ssthresh_ = std::max<u32>(flight / 2, 2u * snd_mss_);
recover_ = snd_max_;
in_recovery_ = true;
RetransmitFirst();
cwnd_ = ssthresh_ + 3u * snd_mss_;
}
}
void TcpConnection::ProcessData(const TcpSegment& seg, Clock::time_point now) {
if (state_ != TcpState::Established && state_ != TcpState::FinWait1 &&
state_ != TcpState::FinWait2) {
// Peer already sent FIN, this is a retransmission.
if (seg.flags & Fin) {
SendAck();
}
return;
}
u32 seq = seg.seq;
std::span<const u8> data = seg.payload;
if (seg.flags & Fin) {
peer_fin_seq_ = seg.seq + static_cast<u32>(seg.payload.size());
}
if (SeqLt(seq, rcv_nxt_)) {
const u32 skip = rcv_nxt_ - seq;
data = skip >= data.size() ? std::span<const u8>{} : data.subspan(skip);
seq = rcv_nxt_;
}
const u32 window = ReceiveWindow();
const u32 room = seq - rcv_nxt_ < window ? rcv_nxt_ + window - seq : 0;
if (data.size() > room) {
data = data.first(room);
}
bool ack_now = false;
if (!data.empty()) {
if (seq == rcv_nxt_) {
recv_buf_.insert(recv_buf_.end(), data.begin(), data.end());
rcv_nxt_ += static_cast<u32>(data.size());
rcv_offset_ += data.size();
if (!out_of_order_.empty()) {
PullOutOfOrder();
ack_now = true; // filled a hole
}
++unacked_segments_;
} else {
const u64 offset = rcv_offset_ + (seq - rcv_nxt_);
if (out_of_order_bytes_ + data.size() <= config_.recv_buffer) {
auto& slot = out_of_order_[offset];
if (slot.size() < data.size()) {
out_of_order_bytes_ += data.size() - slot.size();
slot.assign(data.begin(), data.end());
}
}
ack_now = true; // dup ACK for fast retransmit
}
}
if (peer_fin_seq_ && *peer_fin_seq_ == rcv_nxt_ && !fin_received_) {
++rcv_nxt_;
fin_received_ = true;
ack_now = true;
switch (state_) {
case TcpState::Established:
state_ = TcpState::CloseWait;
break;
case TcpState::FinWait1:
if (FinAcked()) {
EnterTimeWait(now);
} else {
state_ = TcpState::Closing;
}
break;
case TcpState::FinWait2:
EnterTimeWait(now);
break;
default:
break;
}
}
if (ack_now || unacked_segments_ >= 2) {
SendAck();
} else if (unacked_segments_ > 0 && !delayed_ack_deadline_) {
delayed_ack_deadline_ = now + config_.delayed_ack;
}
}
void TcpConnection::PullOutOfOrder() {
while (!out_of_order_.empty()) {
auto it = out_of_order_.begin();
if (it->first > rcv_offset_) {
return;
}
const u64 end = it->first + it->second.size();
if (end > rcv_offset_) {
const auto skip = static_cast<std::ptrdiff_t>(rcv_offset_ - it->first);
recv_buf_.insert(recv_buf_.end(), it->second.begin() + skip, it->second.end());
const u32 added = static_cast<u32>(end - rcv_offset_);
rcv_nxt_ += added;
rcv_offset_ += added;
}
out_of_order_bytes_ -= it->second.size();
out_of_order_.erase(it);
}
}
std::optional<Clock::time_point> TcpConnection::NextDeadline() const {
std::optional<Clock::time_point> next;
for (const auto& deadline :
{retransmit_deadline_, persist_deadline_, delayed_ack_deadline_, time_wait_deadline_}) {
if (deadline && (!next || *deadline < *next)) {
next = deadline;
}
}
return next;
}
void TcpConnection::OnTimer(Clock::time_point now) {
if (time_wait_deadline_ && now >= *time_wait_deadline_) {
state_ = TcpState::Closed;
ClearTimers();
return;
}
if (delayed_ack_deadline_ && now >= *delayed_ack_deadline_) {
SendAck();
}
if (retransmit_deadline_ && now >= *retransmit_deadline_) {
retransmit_deadline_.reset();
OnRetransmitTimeout(now);
}
if (persist_deadline_ && now >= *persist_deadline_) {
EmitSegment(snd_una_ - 1, Ack, {});
persist_interval_ = std::min<Clock::duration>(persist_interval_ * 2, config_.max_rto);
persist_deadline_ = now + persist_interval_;
}
}
void TcpConnection::OnRetransmitTimeout(Clock::time_point now) {
const bool handshake = state_ == TcpState::SynSent || state_ == TcpState::SynReceived;
if (++retries_ > (handshake ? config_.syn_retries : config_.data_retries)) {
if (Synchronized()) {
EmitReset(snd_max_);
}
Terminate(Error::TimedOut);
return;
}
++retransmissions_;
rtt_active_ = false;
rto_ = std::min<Clock::duration>(rto_ * 2, config_.max_rto);
if (state_ == TcpState::SynSent) {
EmitSegment(iss_, Syn, {}, true);
} else if (state_ == TcpState::SynReceived) {
EmitSegment(iss_, Syn | Ack, {}, true);
} else {
const u32 flight = snd_max_ - snd_una_;
ssthresh_ = std::max<u32>(flight / 2, 2u * snd_mss_);
cwnd_ = snd_mss_;
in_recovery_ = false;
dupacks_ = 0;
snd_nxt_ = snd_una_;
TrySend(now);
}
retransmit_deadline_ = now + rto_;
}
void TcpConnection::SampleRtt(Clock::duration rtt) {
if (!have_rtt_) {
srtt_ = rtt;
rttvar_ = rtt / 2;
have_rtt_ = true;
} else {
const auto delta = srtt_ > rtt ? srtt_ - rtt : rtt - srtt_;
rttvar_ = (3 * rttvar_ + delta) / 4;
srtt_ = (7 * srtt_ + rtt) / 8;
}
const auto variance = std::max<Clock::duration>(4 * rttvar_, std::chrono::milliseconds(10));
rto_ = std::clamp<Clock::duration>(srtt_ + variance, config_.min_rto, config_.max_rto);
}
void TcpConnection::EnterTimeWait(Clock::time_point now) {
ClearTimers();
state_ = TcpState::TimeWait;
time_wait_deadline_ = now + config_.time_wait;
}
void TcpConnection::Terminate(Error error) {
error_ = error;
state_ = TcpState::Closed;
ClearTimers();
send_buf_.clear();
out_of_order_.clear();
out_of_order_bytes_ = 0;
// Received data stays readable, like BSD.
}
void TcpConnection::ClearTimers() {
retransmit_deadline_.reset();
persist_deadline_.reset();
delayed_ack_deadline_.reset();
time_wait_deadline_.reset();
}
TcpConnection::IoResult TcpConnection::Send(std::span<const u8> data, Clock::time_point now) {
if (error_ != Error::Ok) {
return {0, error_};
}
switch (state_) {
case TcpState::SynSent:
case TcpState::SynReceived:
return {0, Error::WouldBlock};
case TcpState::Established:
case TcpState::CloseWait:
break;
default:
return {0, fin_queued_ ? Error::Pipe : Error::NotConn};
}
if (fin_queued_) {
return {0, Error::Pipe};
}
const size_t n = std::min(SendSpace(), data.size());
if (n == 0) {
return {0, data.empty() ? Error::Ok : Error::WouldBlock};
}
send_buf_.insert(send_buf_.end(), data.begin(), data.begin() + n);
TrySend(now);
return {n, Error::Ok};
}
TcpConnection::IoResult TcpConnection::Recv(std::span<u8> out, bool peek, Clock::time_point) {
if (!recv_buf_.empty()) {
const size_t n = std::min(out.size(), recv_buf_.size());
std::copy_n(recv_buf_.begin(), n, out.begin());
if (!peek) {
recv_buf_.erase(recv_buf_.begin(), recv_buf_.begin() + n);
MaybeSendWindowUpdate();
}
return {n, Error::Ok};
}
if (fin_received_) {
return {0, Error::Ok}; // EOF
}
if (error_ != Error::Ok) {
return {0, error_};
}
if (state_ == TcpState::Closed) {
return {0, Error::NotConn};
}
return {0, Error::WouldBlock};
}
void TcpConnection::MaybeSendWindowUpdate() {
if (!Synchronized() || fin_received_) {
return;
}
// Only update once the window opened enough to avoid silly window syndrome.
const u32 threshold = std::min<u32>(config_.recv_buffer / 2, 2u * snd_mss_);
if (static_cast<s32>(ReceiveWindow()) - static_cast<s32>(last_advertised_window_) >=
static_cast<s32>(threshold)) {
SendAck();
}
}
void TcpConnection::Shutdown(Clock::time_point now) {
if (fin_queued_) {
return;
}
switch (state_) {
case TcpState::SynSent:
Terminate(Error::Ok);
return;
case TcpState::SynReceived:
Abort(); // not accepted yet
return;
case TcpState::Established:
state_ = TcpState::FinWait1;
break;
case TcpState::CloseWait:
state_ = TcpState::LastAck;
break;
default:
return;
}
fin_queued_ = true;
TrySend(now);
}
void TcpConnection::Close(Clock::time_point now) {
if (state_ == TcpState::SynSent || (Synchronized() && !recv_buf_.empty())) {
Abort(); // BSD resets on close with unread data
return;
}
Shutdown(now);
}
void TcpConnection::Abort() {
if (state_ != TcpState::Closed && state_ != TcpState::SynSent && state_ != TcpState::TimeWait) {
EmitReset(snd_max_);
}
Terminate(Error::Ok);
}
} // namespace Core::Net::P2P
+233
View File
@@ -0,0 +1,233 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <deque>
#include <functional>
#include <map>
#include <optional>
#include <span>
#include <vector>
#include "core/net/host_net.h"
namespace Core::Net::P2P {
using Clock = std::chrono::steady_clock;
using Host::Error;
// Sequence-number comparisons modulo 2^32.
constexpr bool SeqLt(u32 a, u32 b) {
return static_cast<s32>(a - b) < 0;
}
constexpr bool SeqLe(u32 a, u32 b) {
return static_cast<s32>(a - b) <= 0;
}
constexpr bool SeqGt(u32 a, u32 b) {
return SeqLt(b, a);
}
constexpr bool SeqGe(u32 a, u32 b) {
return SeqLe(b, a);
}
namespace TcpFlag {
constexpr u8 Fin = 0x01;
constexpr u8 Syn = 0x02;
constexpr u8 Rst = 0x04;
constexpr u8 Psh = 0x08;
constexpr u8 Ack = 0x10;
} // namespace TcpFlag
struct PseudoHeader {
int family = AF_INET;
std::array<u8, 16> local{}; // IPv4 uses first 4 bytes
std::array<u8, 16> remote{};
};
struct TcpSegment {
u16 src_port; // host byte order
u16 dst_port;
u32 seq;
u32 ack;
u8 flags;
u16 window;
std::optional<u16> mss;
std::span<const u8> payload;
};
u16 TcpChecksum(int family, const u8* src_addr, const u8* dst_addr, std::span<const u8> segment);
std::optional<TcpSegment> ParseTcpSegment(std::span<const u8> bytes,
const PseudoHeader* pseudo = nullptr);
std::vector<u8> BuildTcpSegment(const PseudoHeader& pseudo, u16 src_port, u16 dst_port, u32 seq,
u32 ack, u8 flags, u16 window, std::optional<u16> mss,
std::span<const u8> payload);
std::optional<std::vector<u8>> BuildResetFor(const PseudoHeader& pseudo, const TcpSegment& seg);
struct TcpConfig {
// PS4 defaults for TCP over UDPP2P
u32 recv_buffer = 65535;
u32 send_buffer = 32 * 1024;
u16 mss = 1200; // fits one P2P datagram after overhead
std::chrono::milliseconds initial_rto{1000};
std::chrono::milliseconds min_rto{200};
std::chrono::milliseconds max_rto{60000};
std::chrono::milliseconds delayed_ack{40};
std::chrono::milliseconds time_wait{2000}; // TODO: FreeBSD 2MSL is 60 s
int syn_retries = 6;
int data_retries = 12;
};
enum class TcpState {
Closed,
SynSent,
SynReceived,
Established,
FinWait1,
FinWait2,
CloseWait,
Closing,
LastAck,
TimeWait,
};
const char* ToString(TcpState state);
class TcpConnection {
public:
using Emit = std::function<void(std::span<const u8> segment)>;
struct IoResult {
size_t bytes;
Error error;
};
TcpConnection(const TcpConfig& config, const PseudoHeader& pseudo, u16 local_port,
u16 remote_port, u32 iss, Emit emit);
void Connect(Clock::time_point now);
void AcceptSyn(const TcpSegment& syn, Clock::time_point now);
void OnSegment(const TcpSegment& seg, Clock::time_point now);
void OnTimer(Clock::time_point now);
std::optional<Clock::time_point> NextDeadline() const;
IoResult Send(std::span<const u8> data, Clock::time_point now);
IoResult Recv(std::span<u8> out, bool peek, Clock::time_point now);
void Shutdown(Clock::time_point now);
void Close(Clock::time_point now);
void Abort();
TcpState State() const {
return state_;
}
Error PendingError() const {
return error_;
}
bool Readable() const;
bool Writable() const;
bool ReceivedFin() const {
return fin_received_;
}
size_t SendSpace() const;
size_t BytesToRead() const {
return recv_buf_.size();
}
u16 LocalPort() const {
return local_port_;
}
u16 RemotePort() const {
return remote_port_;
}
u32 Retransmissions() const {
return retransmissions_;
}
private:
bool Synchronized() const;
u16 ReceiveWindow() const;
bool FinSent() const;
bool FinAcked() const;
u32 FinSeq() const;
void EmitSegment(u32 seq, u8 flags, std::span<const u8> payload, bool with_mss = false);
void SendAck();
void EmitReset(u32 seq);
void SendData(u32 seq, u32 len);
void TrySend(Clock::time_point now);
void RetransmitFirst();
bool Acceptable(u32 seq, u32 len) const;
void HandleSynSent(const TcpSegment& seg, Clock::time_point now);
bool ProcessAck(const TcpSegment& seg, Clock::time_point now);
void OnNewAck(u32 ack, Clock::time_point now);
void OnDuplicateAck();
void ProcessData(const TcpSegment& seg, Clock::time_point now);
void PullOutOfOrder();
void MaybeSendWindowUpdate();
void OnRetransmitTimeout(Clock::time_point now);
void SampleRtt(Clock::duration rtt);
void ArmRetransmit(Clock::time_point now);
void EnterTimeWait(Clock::time_point now);
void Terminate(Error error);
void ClearTimers();
const TcpConfig config_;
const PseudoHeader pseudo_;
const u16 local_port_;
const u16 remote_port_;
Emit emit_;
TcpState state_ = TcpState::Closed;
Error error_ = Error::Ok;
u32 iss_;
u32 snd_una_;
u32 snd_nxt_;
u32 snd_max_;
u32 buf_seq_;
u32 snd_wnd_ = 0;
u32 snd_wl1_ = 0;
u32 snd_wl2_ = 0;
u16 snd_mss_ = 536;
std::deque<u8> send_buf_;
bool fin_queued_ = false;
u32 cwnd_ = 0;
u32 ssthresh_ = 0x7fffffff;
int dupacks_ = 0;
bool in_recovery_ = false;
u32 recover_ = 0;
Clock::duration srtt_{};
Clock::duration rttvar_{};
Clock::duration rto_;
bool have_rtt_ = false;
bool rtt_active_ = false;
u32 rtt_seq_ = 0;
Clock::time_point rtt_start_{};
int retries_ = 0;
Clock::duration persist_interval_{};
std::optional<Clock::time_point> retransmit_deadline_;
std::optional<Clock::time_point> persist_deadline_;
std::optional<Clock::time_point> delayed_ack_deadline_;
std::optional<Clock::time_point> time_wait_deadline_;
u32 irs_ = 0;
u32 rcv_nxt_ = 0;
u64 rcv_offset_ = 0; // stream offset of rcv_nxt_
std::deque<u8> recv_buf_;
std::map<u64, std::vector<u8>> out_of_order_;
size_t out_of_order_bytes_ = 0;
std::optional<u32> peer_fin_seq_;
bool fin_received_ = false;
int unacked_segments_ = 0;
u16 last_advertised_window_ = 0;
u32 retransmissions_ = 0;
};
} // namespace Core::Net::P2P
+866
View File
@@ -0,0 +1,866 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
#include <tuple>
#include "core/net/p2p_transport.h"
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
namespace Core::Net::P2P {
struct DatagramBinding {
struct Packet {
std::vector<u8> payload;
Endpoint from;
u16 src_vport;
};
u16 vport = 0;
std::shared_ptr<Readiness> readiness;
std::optional<std::pair<Endpoint, u16>> peer; // set by SetPeer
std::deque<Packet> queue;
size_t bytes = 0;
};
struct Connection {
ConnectionKey key;
Protection protection;
std::unique_ptr<TcpConnection> tcp;
std::shared_ptr<Readiness> readiness;
std::weak_ptr<ListenerState> listener; // during handshake
bool pending_accept = false; // not in accept queue yet
bool released = true; // no socket owns it
};
struct ListenerState {
u16 vport = 0;
int backlog = 1;
Protection protection;
std::shared_ptr<Readiness> readiness;
std::deque<std::shared_ptr<Connection>> accept_queue;
int half_open = 0;
bool closed = false;
};
namespace {
auto Canonical(const Endpoint& e) {
std::array<u8, 16> address{};
u16 port = 0;
u32 scope = 0;
if (e.Family() == AF_INET) {
const auto* in = reinterpret_cast<const sockaddr_in*>(&e.addr);
std::memcpy(address.data(), &in->sin_addr, 4);
port = in->sin_port;
} else if (e.Family() == AF_INET6) {
const auto* in6 = reinterpret_cast<const sockaddr_in6*>(&e.addr);
std::memcpy(address.data(), &in6->sin6_addr, 16);
port = in6->sin6_port;
scope = in6->sin6_scope_id;
}
return std::tuple{e.Family(), address, port, scope};
}
} // namespace
Endpoint Endpoint::FromSockaddr(const sockaddr* sa, socklen_t len) {
Endpoint e;
std::memcpy(&e.addr, sa, std::min<size_t>(static_cast<size_t>(len), sizeof(e.addr)));
return e;
}
Endpoint Endpoint::IPv4(const char* dotted, u16 port) {
Endpoint e;
auto* in = reinterpret_cast<sockaddr_in*>(&e.addr);
in->sin_family = AF_INET;
in->sin_port = htons(port);
inet_pton(AF_INET, dotted, &in->sin_addr);
return e;
}
socklen_t Endpoint::Length() const {
return Family() == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
}
u16 Endpoint::Port() const {
return ntohs(std::get<2>(Canonical(*this)));
}
bool Endpoint::operator<(const Endpoint& other) const {
return Canonical(*this) < Canonical(other);
}
bool Endpoint::operator==(const Endpoint& other) const {
return Canonical(*this) == Canonical(other);
}
bool ConnectionKey::operator<(const ConnectionKey& other) const {
if (!(peer == other.peer)) {
return peer < other.peer;
}
return std::tie(peer_vport, local_vport) < std::tie(other.peer_vport, other.local_vport);
}
void ReadinessFlag::Set(bool ready) {
if (ready == ready_) {
return;
}
ready_ = ready;
if (ready) {
wake_.Signal();
} else {
wake_.Drain();
}
}
Transport::Transport(int family, std::unique_ptr<Codec> codec, const TransportConfig& config)
: family_(family), codec_(std::move(codec)), config_(config),
signaling_(std::max<size_t>(config.signaling_channels, 1)),
iss_state_(static_cast<u64>(Clock::now().time_since_epoch().count()) ^
reinterpret_cast<uintptr_t>(this)),
receive_buffer_(65536) {}
std::shared_ptr<Transport> Transport::Create(int family, const Endpoint& bind_address,
std::unique_ptr<Codec> codec,
const TransportConfig& config, Error* error) {
std::shared_ptr<Transport> t(new Transport(family, std::move(codec), config));
if (!t->kick_.Valid()) {
*error = Error::NoBufs;
return nullptr;
}
t->socket_ = Host::CreateSocket(family, SOCK_DGRAM, 0, error);
if (t->socket_ == Host::InvalidSocket) {
return nullptr;
}
// Exclusive port. Nobody else should get the port we advertise.
if ((*error = Host::PrepareBind(t->socket_, false, false)) != Error::Ok ||
(*error = Host::Bind(t->socket_, bind_address.Sockaddr(), bind_address.Length())) !=
Error::Ok) {
return nullptr;
}
const int buffer = 1 << 20; // absorb bursts
setsockopt(t->socket_, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&buffer),
sizeof(buffer));
sockaddr_storage actual{};
socklen_t actual_len = sizeof(actual);
getsockname(t->socket_, reinterpret_cast<sockaddr*>(&actual), &actual_len);
t->bound_port_ =
Endpoint::FromSockaddr(reinterpret_cast<sockaddr*>(&actual), actual_len).Port();
t->thread_ = std::thread([raw = t.get()] { raw->ThreadMain(); });
*error = Error::Ok;
return t;
}
Transport::~Transport() {
stop_.store(true);
kick_.Signal();
if (thread_.joinable()) {
thread_.join();
}
Host::CloseSocket(socket_); // thread is stopped by now
}
void Transport::SetOutgoingLossForTesting(double probability) {
std::scoped_lock lock{mutex_};
loss_for_testing_ = probability;
}
std::shared_ptr<DatagramSocket> Transport::CreateDatagram() {
return std::shared_ptr<DatagramSocket>(new DatagramSocket(shared_from_this()));
}
std::shared_ptr<StreamSocket> Transport::CreateStream() {
return std::shared_ptr<StreamSocket>(new StreamSocket(shared_from_this(), nullptr));
}
Error Transport::SendSignaling(std::span<const u8> data, const Endpoint& to) {
std::scoped_lock lock{mutex_};
return SendPacket(codec_->EncodeSignaling(data, to), to);
}
bool Transport::RecvSignaling(size_t channel, std::vector<u8>* data, Endpoint* from) {
std::scoped_lock lock{mutex_};
if (channel >= signaling_.size() || signaling_[channel].empty()) {
return false;
}
auto& queue = signaling_[channel];
*data = std::move(queue.front().first);
*from = queue.front().second;
queue.pop_front();
return true;
}
void Transport::ThreadMain() {
while (!stop_.load()) {
std::optional<Clock::time_point> deadline;
{
std::scoped_lock lock{mutex_};
deadline = EarliestDeadline();
sleeping_until_ = deadline.value_or(Clock::time_point::max());
}
const auto result = Host::WaitOne(socket_, Host::Readable, kick_, deadline);
if (stop_.load()) {
return;
}
if (result == Host::WaitResult::Woken) {
kick_.Drain();
}
std::scoped_lock lock{mutex_};
sleeping_until_ = Clock::time_point::min();
const auto now = Clock::now();
for (int i = 0; i < 1024; ++i) { // don't starve timers
sockaddr_storage from{};
socklen_t from_len = sizeof(from);
const auto r = Host::RecvFrom(socket_, receive_buffer_.data(), receive_buffer_.size(),
0, reinterpret_cast<sockaddr*>(&from), &from_len);
if (r.error != Error::Ok) {
break;
}
HandlePacket({receive_buffer_.data(), static_cast<size_t>(r.value)},
Endpoint::FromSockaddr(reinterpret_cast<sockaddr*>(&from), from_len), now);
}
RunTimers(now);
}
}
std::optional<Clock::time_point> Transport::EarliestDeadline() const {
std::optional<Clock::time_point> earliest;
for (const auto& [_, connection] : connections_) {
const auto d = connection->tcp->NextDeadline();
if (d && (!earliest || *d < *earliest)) {
earliest = d;
}
}
return earliest;
}
void Transport::RunTimers(Clock::time_point now) {
std::vector<std::shared_ptr<Connection>> due;
for (const auto& [_, connection] : connections_) {
if (const auto d = connection->tcp->NextDeadline(); d && *d <= now) {
due.push_back(connection);
}
}
for (const auto& connection : due) {
connection->tcp->OnTimer(now);
Update(connection);
}
}
void Transport::Reschedule(const Connection& connection) {
if (const auto d = connection.tcp->NextDeadline(); d && *d < sleeping_until_) {
sleeping_until_ = *d;
kick_.Signal();
}
}
Error Transport::SendPacket(std::span<const u8> packet, const Endpoint& to) {
if (loss_for_testing_ > 0) {
loss_rng_ ^= loss_rng_ << 13;
loss_rng_ ^= loss_rng_ >> 7;
loss_rng_ ^= loss_rng_ << 17;
if (static_cast<double>(loss_rng_ >> 11) / static_cast<double>(1ull << 53) <
loss_for_testing_) {
return Error::Ok;
}
}
const auto r =
Host::SendTo(socket_, packet.data(), packet.size(), 0, to.Sockaddr(), to.Length());
return r.error;
}
u32 Transport::NewIss() {
iss_state_ += 0x9e3779b97f4a7c15ull;
u64 z = iss_state_;
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ull;
z = (z ^ (z >> 27)) * 0x94d049bb133111ebull;
z ^= z >> 31;
const auto clock =
std::chrono::duration_cast<std::chrono::microseconds>(Clock::now().time_since_epoch())
.count() /
4;
return static_cast<u32>(z) + static_cast<u32>(clock);
}
u16 Transport::AllocateVport(bool stream) {
const u16 first = stream ? 49152 : 32768;
const u16 last = stream ? 65535 : 49999;
u16& next = stream ? next_stream_port_ : next_datagram_vport_;
for (int attempt = 0; attempt <= last - first; ++attempt) {
const u16 vport = next;
next = next == last ? first : static_cast<u16>(next + 1);
const bool used =
stream ? (stream_vports_in_use_.contains(vport) || listeners_.contains(vport))
: datagram_bindings_.contains(vport);
if (!used) {
return vport;
}
}
return 0;
}
void Transport::HandlePacket(std::span<const u8> packet, const Endpoint& from,
Clock::time_point now) {
Codec::Decoded decoded = codec_->Decode(packet, from);
switch (decoded.kind) {
case Codec::Kind::Stream:
HandleStreamSegment(decoded.payload, from, now);
break;
case Codec::Kind::Datagram:
HandleDatagram(decoded, from);
break;
case Codec::Kind::Signaling: {
const size_t channel =
config_.classify_signaling ? config_.classify_signaling(decoded.payload) : 0;
if (channel < signaling_.size() &&
signaling_[channel].size() < config_.signaling_queue_packets) {
signaling_[channel].emplace_back(std::move(decoded.payload), from);
}
break;
}
case Codec::Kind::Invalid:
break;
}
}
void Transport::HandleDatagram(Codec::Decoded& decoded, const Endpoint& from) {
const auto it = datagram_bindings_.find(decoded.dst_vport);
if (it == datagram_bindings_.end()) {
return;
}
DatagramBinding& binding = *it->second;
if (binding.peer &&
!(binding.peer->first == from && binding.peer->second == decoded.src_vport)) {
return; // not from the connected peer
}
if (binding.bytes + decoded.payload.size() > config_.datagram_queue_bytes) {
return; // queue full, drop like UDP
}
binding.bytes += decoded.payload.size();
binding.queue.push_back({std::move(decoded.payload), from, decoded.src_vport});
binding.readiness->readable.Set(true);
}
void Transport::HandleStreamSegment(std::span<const u8> bytes, const Endpoint& from,
Clock::time_point now) {
std::optional<PseudoHeader> pseudo;
if (config_.verify_stream_checksum) {
pseudo = codec_->StreamPseudoHeader(from);
}
const auto seg = ParseTcpSegment(bytes, pseudo ? &*pseudo : nullptr);
if (!seg) {
return;
}
const ConnectionKey key{from, seg->src_port, seg->dst_port};
if (const auto it = connections_.find(key); it != connections_.end()) {
const auto connection = it->second;
if (connection->tcp->State() != TcpState::Closed) {
connection->tcp->OnSegment(*seg, now);
Update(connection);
return;
}
if (connection->released) {
connections_.erase(it);
}
}
const bool bare_syn =
(seg->flags & (TcpFlag::Syn | TcpFlag::Ack | TcpFlag::Rst)) == TcpFlag::Syn;
if (bare_syn && !connections_.contains(key)) {
const auto lit = listeners_.find(seg->dst_port);
if (lit != listeners_.end()) {
const auto listener = lit->second;
if (static_cast<int>(listener->accept_queue.size()) + listener->half_open >=
listener->backlog) {
return; // backlog full, peer will retry
}
const auto connection =
NewConnection(key, listener->protection, std::make_shared<Readiness>());
connection->listener = listener;
connection->pending_accept = true;
++listener->half_open;
connection->tcp->AcceptSyn(*seg, now);
Update(connection);
return;
}
}
// Nobody listening, RST so the peer fails fast.
if (!pseudo) {
pseudo = codec_->StreamPseudoHeader(from);
}
if (const auto rst = BuildResetFor(*pseudo, *seg)) {
SendPacket(codec_->EncodeStream(*rst, from, {}), from);
}
}
std::shared_ptr<Connection> Transport::NewConnection(const ConnectionKey& key,
Protection protection,
std::shared_ptr<Readiness> readiness) {
auto connection = std::make_shared<Connection>();
connection->key = key;
connection->protection = protection;
connection->readiness = std::move(readiness);
const Endpoint peer = key.peer;
connection->tcp = std::make_unique<TcpConnection>(
config_.tcp, codec_->StreamPseudoHeader(peer), key.local_vport, key.peer_vport, NewIss(),
[this, peer, protection](std::span<const u8> segment) {
SendPacket(codec_->EncodeStream(segment, peer, protection), peer);
});
connections_[key] = connection;
return connection;
}
void Transport::Update(const std::shared_ptr<Connection>& connection) {
TcpConnection& tcp = *connection->tcp;
if (connection->pending_accept) {
const auto listener = connection->listener.lock();
const TcpState state = tcp.State();
if (!listener || listener->closed) {
connection->pending_accept = false;
tcp.Abort(); // listener is gone
} else if (state != TcpState::SynReceived) {
connection->pending_accept = false;
--listener->half_open;
connection->listener.reset();
if (state != TcpState::Closed) {
listener->accept_queue.push_back(connection);
listener->readiness->readable.Set(true);
}
}
}
connection->readiness->readable.Set(tcp.Readable());
connection->readiness->writable.Set(tcp.Writable());
if (tcp.State() == TcpState::Closed && connection->released) {
const auto it = connections_.find(connection->key);
if (it != connections_.end() && it->second == connection) {
connections_.erase(it);
}
}
}
DatagramSocket::DatagramSocket(std::shared_ptr<Transport> transport)
: transport_(std::move(transport)), readiness_(std::make_shared<Readiness>()) {
readiness_->writable.Set(true); // UDP is always writable
}
DatagramSocket::~DatagramSocket() {
Close();
}
Error DatagramSocket::Bind(u16 vport) {
std::scoped_lock lock{transport_->mutex_};
return BindLocked(vport);
}
Error DatagramSocket::BindLocked(u16 vport) {
if (binding_) {
return Error::Inval;
}
if (vport == 0 && (vport = transport_->AllocateVport(false)) == 0) {
return Error::AddrNotAvail;
}
if (transport_->datagram_bindings_.contains(vport)) {
return Error::AddrInUse;
}
binding_ = std::make_shared<DatagramBinding>();
binding_->vport = vport;
binding_->readiness = readiness_;
binding_->peer = peer_;
transport_->datagram_bindings_[vport] = binding_;
return Error::Ok;
}
Error DatagramSocket::SetPeer(const Endpoint& peer, u16 vport) {
std::scoped_lock lock{transport_->mutex_};
if (vport == 0) {
return Error::AddrNotAvail;
}
peer_ = std::pair{peer, vport};
if (binding_) {
binding_->peer = peer_;
}
return Error::Ok;
}
void DatagramSocket::SetProtection(Protection protection) {
std::scoped_lock lock{transport_->mutex_};
protection_ = protection;
}
IoResult DatagramSocket::SendTo(std::span<const u8> data, const Endpoint* to, u16 to_vport,
Protection extra) {
std::scoped_lock lock{transport_->mutex_};
if (!binding_ && BindLocked(0) != Error::Ok) { // implicit bind
return {0, Error::AddrNotAvail};
}
if (to == nullptr) {
if (!peer_) {
return {0, Error::DestAddrReq};
}
to = &peer_->first;
to_vport = peer_->second;
}
if (to_vport == 0) {
return {0, Error::AddrNotAvail};
}
const Protection protection{protection_.crypto || extra.crypto,
protection_.signature || extra.signature};
const auto packet =
transport_->codec_->EncodeDatagram(binding_->vport, to_vport, data, *to, protection);
const Error e = transport_->SendPacket(packet, *to);
if (e == Error::WouldBlock || e == Error::NoBufs) {
// PS4 drops instead of returning ENOBUFS
return {data.size(), Error::Ok};
}
return e == Error::Ok ? IoResult{data.size(), Error::Ok} : IoResult{0, e};
}
IoResult DatagramSocket::RecvFrom(std::span<u8> out, bool peek, Endpoint* from, u16* from_vport) {
std::scoped_lock lock{transport_->mutex_};
if (!binding_ || binding_->queue.empty()) {
return {0, Error::WouldBlock};
}
auto& packet = binding_->queue.front();
const size_t n = std::min(out.size(), packet.payload.size()); // rest is discarded
std::copy_n(packet.payload.begin(), n, out.begin());
if (from) {
*from = packet.from;
}
if (from_vport) {
*from_vport = packet.src_vport;
}
if (!peek) {
binding_->bytes -= packet.payload.size();
binding_->queue.pop_front();
if (binding_->queue.empty()) {
readiness_->readable.Set(false);
}
}
return {n, Error::Ok};
}
u32 DatagramSocket::Events() const {
std::scoped_lock lock{transport_->mutex_};
return Host::EvOut | (binding_ && !binding_->queue.empty() ? static_cast<u32>(Host::EvIn) : 0u);
}
u16 DatagramSocket::BoundVport() const {
std::scoped_lock lock{transport_->mutex_};
return binding_ ? binding_->vport : 0;
}
u16 DatagramSocket::TransportPort() const {
return transport_->BoundPort();
}
bool DatagramSocket::GetPeer(Endpoint* peer, u16* peer_vport) const {
std::scoped_lock lock{transport_->mutex_};
if (!peer_) {
return false;
}
*peer = peer_->first;
*peer_vport = peer_->second;
return true;
}
Protection DatagramSocket::GetProtection() const {
std::scoped_lock lock{transport_->mutex_};
return protection_;
}
void DatagramSocket::Close() {
std::scoped_lock lock{transport_->mutex_};
if (!binding_) {
return;
}
const auto it = transport_->datagram_bindings_.find(binding_->vport);
if (it != transport_->datagram_bindings_.end() && it->second == binding_) {
transport_->datagram_bindings_.erase(it);
}
binding_.reset();
}
StreamSocket::StreamSocket(std::shared_ptr<Transport> transport,
std::shared_ptr<Readiness> readiness)
: transport_(std::move(transport)),
readiness_(readiness ? std::move(readiness) : std::make_shared<Readiness>()) {}
StreamSocket::~StreamSocket() {
Close();
}
void StreamSocket::SetProtection(Protection protection) {
std::scoped_lock lock{transport_->mutex_};
protection_ = protection;
}
Error StreamSocket::Bind(u16 vport) {
std::scoped_lock lock{transport_->mutex_};
return BindLocked(vport);
}
Error StreamSocket::BindLocked(u16 vport) {
if (bound_vport_ != 0 || closed_) {
return Error::Inval;
}
if (vport == 0 && (vport = transport_->AllocateVport(true)) == 0) {
return Error::AddrNotAvail;
}
if (transport_->stream_vports_in_use_.contains(vport)) {
return Error::AddrInUse;
}
++transport_->stream_vports_in_use_[vport];
bound_vport_ = vport;
owns_vport_ = true;
return Error::Ok;
}
Error StreamSocket::Listen(int backlog) {
std::scoped_lock lock{transport_->mutex_};
if (connection_ || closed_) {
return Error::Inval;
}
if (bound_vport_ == 0 && BindLocked(0) != Error::Ok) {
return Error::AddrNotAvail;
}
const int clamped = std::clamp(backlog, 1, transport_->config_.max_backlog);
if (listener_) {
listener_->backlog = clamped;
return Error::Ok;
}
listener_ = std::make_shared<ListenerState>();
listener_->vport = bound_vport_;
listener_->backlog = clamped;
listener_->protection = protection_;
listener_->readiness = readiness_;
transport_->listeners_[bound_vport_] = listener_;
return Error::Ok;
}
Error StreamSocket::Connect(const Endpoint& peer, u16 peer_vport) {
std::scoped_lock lock{transport_->mutex_};
if (listener_ || closed_) {
return Error::Inval;
}
if (connection_) {
const TcpState state = connection_->tcp->State();
return state == TcpState::SynSent || state == TcpState::SynReceived ? Error::Already
: Error::IsConn;
}
if (peer_vport == 0) {
return Error::AddrNotAvail;
}
if (bound_vport_ == 0 && BindLocked(0) != Error::Ok) {
return Error::AddrNotAvail;
}
const ConnectionKey key{peer, peer_vport, bound_vport_};
if (transport_->connections_.contains(key)) {
return Error::AddrInUse; // e.g. old connection in TIME-WAIT
}
connection_ = transport_->NewConnection(key, protection_, readiness_);
connection_->released = false;
connection_->tcp->Connect(Clock::now());
transport_->Update(connection_);
transport_->Reschedule(*connection_);
return Error::InProgress;
}
Error StreamSocket::ConnectResult() const {
std::scoped_lock lock{transport_->mutex_};
if (!connection_) {
return Error::NotConn;
}
const TcpConnection& tcp = *connection_->tcp;
if (tcp.PendingError() != Error::Ok) {
return tcp.PendingError();
}
switch (tcp.State()) {
case TcpState::SynSent:
case TcpState::SynReceived:
return Error::InProgress;
case TcpState::Closed:
return Error::NotConn;
default:
return Error::Ok;
}
}
std::shared_ptr<StreamSocket> StreamSocket::Accept(Endpoint* peer, u16* peer_vport, Error* error) {
std::scoped_lock lock{transport_->mutex_};
if (!listener_) {
*error = Error::Inval;
return nullptr;
}
if (listener_->accept_queue.empty()) {
*error = Error::WouldBlock;
return nullptr;
}
auto connection = std::move(listener_->accept_queue.front());
listener_->accept_queue.pop_front();
if (listener_->accept_queue.empty()) {
readiness_->readable.Set(false);
}
connection->released = false;
std::shared_ptr<StreamSocket> accepted(new StreamSocket(transport_, connection->readiness));
accepted->protection_ = connection->protection;
accepted->bound_vport_ = connection->key.local_vport; // listener owns the vport
accepted->connection_ = connection;
if (peer) {
*peer = connection->key.peer;
}
if (peer_vport) {
*peer_vport = connection->key.peer_vport;
}
transport_->Update(connection);
*error = Error::Ok;
return accepted;
}
IoResult StreamSocket::Send(std::span<const u8> data) {
std::scoped_lock lock{transport_->mutex_};
if (!connection_) {
return {0, Error::NotConn};
}
const auto r = connection_->tcp->Send(data, Clock::now());
transport_->Update(connection_);
transport_->Reschedule(*connection_);
return {r.bytes, r.error};
}
IoResult StreamSocket::Recv(std::span<u8> out, bool peek) {
std::scoped_lock lock{transport_->mutex_};
if (!connection_) {
return {0, Error::NotConn};
}
if (read_shutdown_) {
return {0, Error::Ok};
}
const auto r = connection_->tcp->Recv(out, peek, Clock::now());
transport_->Update(connection_);
return {r.bytes, r.error};
}
Error StreamSocket::Shutdown(int how) {
std::scoped_lock lock{transport_->mutex_};
if (!connection_) {
return Error::NotConn;
}
if (how < 0 || how > 2) {
return Error::Inval;
}
if (how != 1) {
read_shutdown_ = true;
readiness_->readable.Set(true); // reads return EOF now
}
if (how != 0) {
connection_->tcp->Shutdown(Clock::now());
transport_->Update(connection_);
transport_->Reschedule(*connection_);
}
return Error::Ok;
}
void StreamSocket::Close() {
std::scoped_lock lock{transport_->mutex_};
if (closed_) {
return;
}
closed_ = true;
if (listener_) {
listener_->closed = true;
const auto it = transport_->listeners_.find(listener_->vport);
if (it != transport_->listeners_.end() && it->second == listener_) {
transport_->listeners_.erase(it);
}
for (const auto& pending : listener_->accept_queue) {
pending->tcp->Abort(); // reset unaccepted, like BSD
transport_->Update(pending);
}
listener_->accept_queue.clear();
listener_.reset();
}
if (connection_) {
connection_->released = true;
connection_->tcp->Close(Clock::now()); // FIN, or RST if data is unread
transport_->Update(connection_);
transport_->Reschedule(*connection_);
connection_.reset();
}
if (owns_vport_) {
auto& count = transport_->stream_vports_in_use_[bound_vport_];
if (--count <= 0) {
transport_->stream_vports_in_use_.erase(bound_vport_);
}
owns_vport_ = false;
}
}
void StreamSocket::Abort() {
std::scoped_lock lock{transport_->mutex_};
if (connection_) {
connection_->tcp->Abort();
transport_->Update(connection_);
}
}
u32 StreamSocket::Events() const {
std::scoped_lock lock{transport_->mutex_};
if (listener_) {
return listener_->accept_queue.empty() ? 0u : static_cast<u32>(Host::EvIn);
}
if (!connection_) {
return 0;
}
const TcpConnection& tcp = *connection_->tcp;
u32 events = 0;
if (tcp.Readable() || read_shutdown_) {
events |= Host::EvIn;
}
if (tcp.Writable()) {
events |= Host::EvOut;
}
if (tcp.PendingError() != Error::Ok) {
events |= Host::EvErr;
}
if (tcp.State() == TcpState::Closed || tcp.State() == TcpState::TimeWait || tcp.ReceivedFin()) {
events |= Host::EvHup; // TODO: peer FIN alone is RDHUP on Linux, not HUP
}
return events;
}
std::optional<TcpState> StreamSocket::State() const {
std::scoped_lock lock{transport_->mutex_};
return connection_ ? std::optional{connection_->tcp->State()} : std::nullopt;
}
bool StreamSocket::GetPeer(Endpoint* peer, u16* peer_vport) const {
std::scoped_lock lock{transport_->mutex_};
if (!connection_) {
return false;
}
*peer = connection_->key.peer;
*peer_vport = connection_->key.peer_vport;
return true;
}
u16 StreamSocket::BoundVport() const {
std::scoped_lock lock{transport_->mutex_};
return bound_vport_;
}
u16 StreamSocket::TransportPort() const {
return transport_->BoundPort();
}
Protection StreamSocket::GetProtection() const {
std::scoped_lock lock{transport_->mutex_};
return protection_;
}
} // namespace Core::Net::P2P
+256
View File
@@ -0,0 +1,256 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// One UDP socket shared by P2P datagrams, P2P TCP streams and signaling.
#pragma once
#include <atomic>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include "core/net/p2p_tcp.h"
namespace Core::Net::P2P {
struct Endpoint {
sockaddr_storage addr{};
static Endpoint FromSockaddr(const sockaddr* sa, socklen_t len);
static Endpoint IPv4(const char* dotted, u16 port);
int Family() const {
return addr.ss_family;
}
socklen_t Length() const;
const sockaddr* Sockaddr() const {
return reinterpret_cast<const sockaddr*>(&addr);
}
u16 Port() const; // host byte order
bool operator<(const Endpoint& other) const;
bool operator==(const Endpoint& other) const;
};
struct Protection {
bool crypto = false;
bool signature = false;
};
class Codec {
public:
enum class Kind { Invalid, Stream, Datagram, Signaling };
struct Decoded {
Kind kind = Kind::Invalid;
u16 src_vport = 0; // datagrams only
u16 dst_vport = 0;
std::vector<u8> payload;
};
virtual ~Codec() = default;
virtual Decoded Decode(std::span<const u8> packet, const Endpoint& from) = 0;
virtual std::vector<u8> EncodeStream(std::span<const u8> tcp_segment, const Endpoint& to,
Protection protection) = 0;
virtual std::vector<u8> EncodeDatagram(u16 src_vport, u16 dst_vport,
std::span<const u8> payload, const Endpoint& to,
Protection protection) = 0;
virtual std::vector<u8> EncodeSignaling(std::span<const u8> data, const Endpoint& to) = 0;
virtual PseudoHeader StreamPseudoHeader(const Endpoint& peer) = 0;
};
struct TransportConfig {
TcpConfig tcp;
size_t datagram_queue_bytes = 40 * 1024; // per vport, PS4's SO_RCVBUF
size_t signaling_queue_packets = 256; // per channel
size_t signaling_channels = 1;
std::function<size_t(std::span<const u8>)> classify_signaling;
int max_backlog = 64;
// TODO: NAT changes the addresses in the pseudo-header, so peer checksums may not
// match. Off until tested.
bool verify_stream_checksum = false;
};
class ReadinessFlag {
public:
bool Valid() const {
return wake_.Valid();
}
void Set(bool ready);
Host::NativeSocket PollHandle() const {
return wake_.PollHandle();
}
private:
Host::WakeHandle wake_;
bool ready_ = false;
};
struct Readiness {
ReadinessFlag readable; // data, EOF, error or accept
ReadinessFlag writable; // space or error
};
struct IoResult {
size_t bytes;
Error error;
};
struct ConnectionKey {
Endpoint peer;
u16 peer_vport;
u16 local_vport;
bool operator<(const ConnectionKey& other) const;
};
class Transport;
struct DatagramBinding;
struct Connection;
struct ListenerState;
class DatagramSocket {
public:
~DatagramSocket();
Error Bind(u16 vport); // 0 = any free vport
Error SetPeer(const Endpoint& peer, u16 vport);
void SetProtection(Protection protection);
// to == nullptr sends to the SetPeer peer. extra is per-message protection.
IoResult SendTo(std::span<const u8> data, const Endpoint* to, u16 to_vport,
Protection extra = {});
IoResult RecvFrom(std::span<u8> out, bool peek, Endpoint* from, u16* from_vport);
u32 Events() const; // Host::EventBits
const Readiness& Handles() const {
return *readiness_;
}
u16 BoundVport() const;
u16 TransportPort() const;
bool GetPeer(Endpoint* peer, u16* peer_vport) const;
Protection GetProtection() const;
void Close();
private:
friend class Transport;
explicit DatagramSocket(std::shared_ptr<Transport> transport);
Error BindLocked(u16 vport);
std::shared_ptr<Transport> transport_;
std::shared_ptr<Readiness> readiness_;
std::shared_ptr<DatagramBinding> binding_;
Protection protection_;
std::optional<std::pair<Endpoint, u16>> peer_;
};
class StreamSocket {
public:
~StreamSocket();
void SetProtection(Protection protection);
Error Bind(u16 vport);
Error Listen(int backlog);
Error Connect(const Endpoint& peer, u16 peer_vport);
Error ConnectResult() const;
std::shared_ptr<StreamSocket> Accept(Endpoint* peer, u16* peer_vport, Error* error);
IoResult Send(std::span<const u8> data);
IoResult Recv(std::span<u8> out, bool peek);
Error Shutdown(int how); // 0 = read, 1 = write, 2 = both
void Close();
void Abort();
u32 Events() const;
const Readiness& Handles() const {
return *readiness_;
}
std::optional<TcpState> State() const;
bool GetPeer(Endpoint* peer, u16* peer_vport) const;
u16 BoundVport() const;
u16 TransportPort() const;
Protection GetProtection() const;
private:
friend class Transport;
StreamSocket(std::shared_ptr<Transport> transport, std::shared_ptr<Readiness> readiness);
Error BindLocked(u16 vport); // transport mutex held
std::shared_ptr<Transport> transport_;
std::shared_ptr<Readiness> readiness_;
Protection protection_;
u16 bound_vport_ = 0;
bool owns_vport_ = false;
bool read_shutdown_ = false;
bool closed_ = false;
std::shared_ptr<ListenerState> listener_;
std::shared_ptr<Connection> connection_;
};
class Transport : public std::enable_shared_from_this<Transport> {
public:
static std::shared_ptr<Transport> Create(int family, const Endpoint& bind_address,
std::unique_ptr<Codec> codec,
const TransportConfig& config, Error* error);
~Transport();
u16 BoundPort() const {
return bound_port_;
}
int Family() const {
return family_;
}
std::shared_ptr<DatagramSocket> CreateDatagram();
std::shared_ptr<StreamSocket> CreateStream();
Error SendSignaling(std::span<const u8> data, const Endpoint& to);
bool RecvSignaling(size_t channel, std::vector<u8>* data, Endpoint* from);
void SetOutgoingLossForTesting(double probability);
private:
friend class DatagramSocket;
friend class StreamSocket;
Transport(int family, std::unique_ptr<Codec> codec, const TransportConfig& config);
void ThreadMain();
void HandlePacket(std::span<const u8> packet, const Endpoint& from, Clock::time_point now);
void HandleStreamSegment(std::span<const u8> bytes, const Endpoint& from,
Clock::time_point now);
void HandleDatagram(Codec::Decoded& decoded, const Endpoint& from);
void RunTimers(Clock::time_point now);
std::optional<Clock::time_point> EarliestDeadline() const;
std::shared_ptr<Connection> NewConnection(const ConnectionKey& key, Protection protection,
std::shared_ptr<Readiness> readiness);
void Update(const std::shared_ptr<Connection>& connection);
void Reschedule(const Connection& connection);
Error SendPacket(std::span<const u8> packet, const Endpoint& to);
u16 AllocateVport(bool stream);
u32 NewIss();
const int family_;
std::unique_ptr<Codec> codec_;
const TransportConfig config_;
Host::NativeSocket socket_ = Host::InvalidSocket;
u16 bound_port_ = 0;
mutable std::mutex mutex_;
std::map<u16, std::shared_ptr<DatagramBinding>> datagram_bindings_;
std::map<u16, std::shared_ptr<ListenerState>> listeners_;
std::map<ConnectionKey, std::shared_ptr<Connection>> connections_;
std::map<u16, int> stream_vports_in_use_;
std::vector<std::deque<std::pair<std::vector<u8>, Endpoint>>> signaling_;
u16 next_stream_port_ = 49152; // 49152-65535
u16 next_datagram_vport_ = 32768; // 32768-49999
u64 iss_state_;
double loss_for_testing_ = 0.0;
u64 loss_rng_ = 0x9e3779b97f4a7c15ull;
std::thread thread_;
std::atomic<bool> stop_{false};
Host::WakeHandle kick_;
Clock::time_point sleeping_until_ = Clock::time_point::min();
std::vector<u8> receive_buffer_;
};
} // namespace Core::Net::P2P
+1
View File
@@ -479,6 +479,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
LOG_INFO(Config, "General isRedZonePatchingEnabled: {}",
EmulatorSettings.IsRedZonePatchingEnabled());
#endif
LOG_INFO(Config, "General p2pPort: {}", EmulatorSettings.GetP2PPort());
LOG_INFO(Config, "Log sync: {}", EmulatorSettings.IsLogSync());
LOG_INFO(Config, "Log skipDuplicate: {}", EmulatorSettings.IsLogSkipDuplicate());
#ifdef _WIN32
+12
View File
@@ -217,8 +217,17 @@ void ShadNetClient::ConnectThread() {
LOG_INFO(ShadNet, "Login packet sent for '{}'", m_npid);
}
namespace {
thread_local bool t_reader_thread = false;
}
bool ShadNetClient::OnReaderThread() {
return t_reader_thread;
}
void ShadNetClient::ReaderThread() {
Common::SetCurrentThreadName("ShadNet:Reader");
t_reader_thread = true;
while (!m_terminate) {
u8 hdr[SHAD_HEADER_SIZE];
if (!RecvN(hdr, SHAD_HEADER_SIZE)) {
@@ -932,6 +941,7 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
case NotificationType::RoomMessage: {
shadnet::NotifyRoomMessage pb;
if (!pb.ParseFromString(blob)) {
LOG_WARNING(ShadNet, "RoomMessage parse error");
break;
}
NotifyRoomMessage n;
@@ -947,6 +957,8 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
n.src_account_id = pb.src_account_id();
n.src_platform = pb.src_platform();
n.msg.assign(pb.msg().begin(), pb.msg().end());
LOG_DEBUG(ShadNet, "RoomMessage room_id={} src={} event={:#x} bytes={}", n.room_id,
n.src_member_id, n.event, n.msg.size());
if (onRoomMessage)
onRoomMessage(n);
break;
+3
View File
@@ -97,6 +97,7 @@ enum class CommandType : u16 {
GetUserInfoList = 113,
GetRoomMemberDataExternalList = 114,
SendRoomMessage = 115,
SetRoomMemberDataInternal = 116,
// Title User Storage (TUS)
TusSetData = 201,
TusGetData = 202,
@@ -304,6 +305,8 @@ public:
u32 GetAddrLocal() const;
u32 GetAddrServer() const;
bool IsMatching2Enabled() const;
// True on the thread that reads replies. Code running there must not wait for a reply.
static bool OnReaderThread();
bool IsTrophiesEnabled() const;
u64 ReportClientVersion();
static std::string BuildVersionString();
+11
View File
@@ -374,6 +374,16 @@ message SetRoomDataInternalRequest {
uint64 passwd_slot_mask = 7;
}
message SetRoomMemberDataInternalRequest {
uint32 req_id = 1;
uint64 room_id = 2;
uint32 member_id = 3;
uint32 team_id = 4;
uint32 flag_filter = 5;
uint32 flag_attr = 6;
repeated MatchingBinAttr bin_attrs = 7;
}
message SetRoomDataExternalRequest {
uint32 req_id = 1;
uint64 room_id = 2;
@@ -510,6 +520,7 @@ message RequestSignalingInfosReply {
message LeaveRoomReply { uint64 room_id = 1; }
message SetRoomDataInternalReply { uint64 room_id = 1; }
message SetRoomMemberDataInternalReply { uint64 room_id = 1; }
message SetRoomDataExternalReply { uint64 room_id = 1; }
message KickoutRoomMemberReply { uint64 room_id = 1; }
+215
View File
@@ -9,6 +9,12 @@ include(FetchContent)
)
FetchContent_MakeAvailable(googletest)
foreach(gtest_target gtest gtest_main gmock gmock_main)
if (TARGET ${gtest_target})
target_compile_options(${gtest_target} PRIVATE $<IF:$<BOOL:${MSVC}>,/WX-,-Wno-error>)
endif()
endforeach()
set(TEST_TARGETS
shadps4_settings_test
shadps4_ngs2_test
@@ -340,3 +346,212 @@ gtest_discover_tests(shadps4_http_test
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DISCOVERY_TIMEOUT 60
)
# ===========================================================================
# Core::Net tests (host sockets, guest layer, P2P transport, TCP, wire codec)
# ===========================================================================
set(NET_CORE_SOURCES
${CMAKE_SOURCE_DIR}/src/core/net/guest_net.cpp
${CMAKE_SOURCE_DIR}/src/core/net/host_net.cpp
${CMAKE_SOURCE_DIR}/src/core/net/p2p_codec.cpp
${CMAKE_SOURCE_DIR}/src/core/net/p2p_crypto.cpp
${CMAKE_SOURCE_DIR}/src/core/net/p2p_transport.cpp
${CMAKE_SOURCE_DIR}/src/core/net/p2p_tcp.cpp
)
# libcrypto for p2p_crypto.cpp:
get_directory_property(NET_TEST_SSL_LIBS DIRECTORY ${CMAKE_SOURCE_DIR}/externals
DEFINITION SSL_DEPENDENCIES)
if (NOT NET_TEST_SSL_LIBS)
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
set(NET_TEST_SSL_LIBS OpenSSL::Crypto)
endif()
add_executable(shadps4_net_test
${NET_CORE_SOURCES}
network/test_net_host.cpp
network/test_net_p2p_codec.cpp
network/test_net_p2p_guest.cpp
network/test_net_p2p_tcp.cpp
)
# Our TCP against the Linux kernel's TCP through a TUN device. Needs root or CAP_NET_ADMIN
# and skips itself otherwise (and on other platforms). Separate target so it can run serially:
# every test creates the same TUN interface.
add_executable(shadps4_net_interop_test
${CMAKE_SOURCE_DIR}/src/core/net/p2p_tcp.cpp
network/test_net_p2p_tcp_interop.cpp
)
foreach(t shadps4_net_test shadps4_net_interop_test)
list(APPEND TEST_TARGETS ${t})
target_include_directories(${t} PRIVATE
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}
)
target_compile_features(${t} PRIVATE cxx_std_23)
target_link_libraries(${t} PRIVATE GTest::gtest_main)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP)
if (LIBCPP)
target_compile_options(${t} PRIVATE -fexperimental-library)
endif()
endif()
if (WIN32)
target_link_libraries(${t} PRIVATE onecore ws2_32)
target_compile_definitions(${t} PRIVATE
NOMINMAX
WIN32_LEAN_AND_MEAN
NTDDI_VERSION=0x0A000006
_WIN32_WINNT=0x0A00
WINVER=0x0A00
)
if (MSVC)
target_compile_definitions(${t} PRIVATE
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_DEPRECATE
_SCL_SECURE_NO_WARNINGS
_TIMESPEC_DEFINED
)
endif()
endif()
endforeach()
target_link_libraries(shadps4_net_test PRIVATE ${NET_TEST_SSL_LIBS})
if (WIN32)
target_link_libraries(shadps4_net_test PRIVATE wepoll)
endif()
gtest_discover_tests(shadps4_net_test
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DISCOVERY_TIMEOUT 60
PROPERTIES TIMEOUT 300
)
gtest_discover_tests(shadps4_net_interop_test
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DISCOVERY_TIMEOUT 60
PROPERTIES RUN_SERIAL TRUE TIMEOUT 600
)
# ===========================================================================
# New libSceNet (src/core/libraries/net) on top of Core::Net
# ===========================================================================
# The library with the same logging/HLE support as the HTTP test; its kernel glue
# (net_kernel.cpp) is tested separately against a stub of the kernel.
set(NET_LIB_TEST_SOURCES
# Under test
${NET_CORE_SOURCES}
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_log.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_p2p.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_p2p_ioctl.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_resolver.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_translate.cpp
# Logging and RegisterLib support
${CMAKE_SOURCE_DIR}/src/core/emulator_settings.cpp
${CMAKE_SOURCE_DIR}/src/core/emulator_state.cpp
${CMAKE_SOURCE_DIR}/src/common/path_util.cpp
${CMAKE_SOURCE_DIR}/src/common/assert.cpp
${CMAKE_SOURCE_DIR}/src/common/error.cpp
${CMAKE_SOURCE_DIR}/src/common/string_util.cpp
${CMAKE_SOURCE_DIR}/src/common/logging/classes.cpp
${CMAKE_SOURCE_DIR}/src/common/logging/log.cpp
# Stubs that replace dependencies
stubs/common_stub.cpp
stubs/scm_rev_stub.cpp
stubs/sdl_stub.cpp
stubs/loader_stub.cpp
stubs/core_stub.cpp
stubs/signals_stub.cpp
stubs/file_sys_stub.cpp
stubs/net_lib_kernel_stub.cpp
)
# Both targets below share this setup.
function(add_net_lib_test target)
add_executable(${target} ${ARGN})
list(APPEND TEST_TARGETS ${target})
target_include_directories(${target} PRIVATE
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}
)
target_compile_features(${target} PRIVATE cxx_std_23)
target_compile_definitions(${target} PRIVATE BOOST_ASIO_STANDALONE)
target_link_libraries(${target} PRIVATE
GTest::gtest_main
fmt::fmt
magic_enum::magic_enum
nlohmann_json::nlohmann_json
toml11::toml11
SDL3::SDL3
spdlog::spdlog
${NET_TEST_SSL_LIBS}
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP)
if (LIBCPP)
target_compile_options(${target} PRIVATE -fexperimental-library)
endif()
endif()
if (WIN32)
target_link_libraries(${target} PRIVATE onecore ws2_32 wepoll)
target_compile_definitions(${target} PRIVATE
NOMINMAX
WIN32_LEAN_AND_MEAN
NTDDI_VERSION=0x0A000006
_WIN32_WINNT=0x0A00
WINVER=0x0A00
)
if (MSVC)
target_compile_definitions(${target} PRIVATE
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_DEPRECATE
_SCL_SECURE_NO_WARNINGS
_TIMESPEC_DEFINED
)
endif()
endif()
gtest_discover_tests(${target}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DISCOVERY_TIMEOUT 60
PROPERTIES TIMEOUT 300
)
set(TEST_TARGETS ${TEST_TARGETS} PARENT_SCOPE)
endfunction()
add_net_lib_test(shadps4_net_lib_test
${NET_LIB_TEST_SOURCES}
network/test_net_lib.cpp
network/test_net_lib_p2p_ioctl.cpp
network/test_net_lib_translate.cpp
network/test_net_sample_sockets.cpp
)
# The kernel glue: net ids as kernel file descriptors, the BSD socket calls. The kernel's
# errno and file table come from a stub; everything else is the real code.
add_net_lib_test(shadps4_net_kernel_test
${NET_LIB_TEST_SOURCES}
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_kernel.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_upnp.cpp
${CMAKE_SOURCE_DIR}/src/core/libraries/net/net_util.cpp
stubs/net_kernel_stub.cpp
network/test_net_kernel.cpp
)
target_link_libraries(shadps4_net_kernel_test PRIVATE
tsl::robin_map # fs.h
miniupnpc::miniupnpc # UPnP forwarding of the P2P port
)
+593
View File
@@ -0,0 +1,593 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Native sockets through Core::Net's guest layer: blocking semantics enforced on non-blocking
// host sockets, SO_RCVTIMEO, abort, epoll and error translation.
#include <chrono>
#include <cstring>
#include <mutex>
#include <thread>
#include <vector>
#include <gtest/gtest.h>
#include "core/net/guest_net.h"
using namespace Core::Net;
using namespace std::chrono_literals;
using Host::Error;
namespace {
class NetHostTest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
ASSERT_TRUE(Host::Initialize());
}
static sockaddr_in Loopback(u16 port = 0) {
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(port);
return addr;
}
/// A listening TCP socket on an ephemeral loopback port.
static s32 MakeListener(sockaddr_in* bound) {
const s32 listener = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
EXPECT_GT(listener, 0);
auto addr = Loopback();
EXPECT_EQ(SocketBind(listener, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
Error::Ok);
EXPECT_EQ(SocketListen(listener, 4).error, Error::Ok);
socklen_t len = sizeof(*bound);
EXPECT_EQ(SocketGetName(listener, reinterpret_cast<sockaddr*>(bound), &len).error,
Error::Ok);
return listener;
}
static double Millis(std::chrono::steady_clock::time_point since) {
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - since)
.count();
}
};
TEST_F(NetHostTest, BlockingConnectAcceptSendRecv) {
sockaddr_in addr{};
const s32 listener = MakeListener(&addr);
NetResult accepted{};
std::thread server([&] { accepted = SocketAccept(listener, nullptr, nullptr); });
const s32 client = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
ASSERT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
Error::Ok);
server.join();
ASSERT_EQ(accepted.error, Error::Ok);
const s32 conn = static_cast<s32>(accepted.value);
EXPECT_EQ(SocketSendTo(client, "hello", 5, 0, false, nullptr, 0).value, 5);
char buf[16]{};
EXPECT_EQ(SocketRecvFrom(conn, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 5);
EXPECT_EQ(std::memcmp(buf, "hello", 5), 0);
// Peer close is end-of-stream (0), not an error.
SocketClose(client);
EXPECT_EQ(SocketRecvFrom(conn, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 0);
SocketClose(conn);
SocketClose(listener);
}
TEST_F(NetHostTest, RecvTimeoutIsEagain) {
sockaddr_in addr{};
const s32 listener = MakeListener(&addr);
const s32 client = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
ASSERT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
Error::Ok);
SocketSetRecvTimeout(client, 200ms);
char buf[4];
const auto start = std::chrono::steady_clock::now();
const auto r = SocketRecvFrom(client, buf, sizeof(buf), 0, false, nullptr, nullptr);
// BSD: an expired SO_RCVTIMEO is EAGAIN, not ETIMEDOUT.
EXPECT_EQ(r.error, Error::WouldBlock);
EXPECT_GE(Millis(start), 190.0);
EXPECT_LT(Millis(start), 2000.0);
SocketClose(client);
SocketClose(listener);
}
TEST_F(NetHostTest, AbortWakesBlockedRecv) {
sockaddr_in addr{};
const s32 listener = MakeListener(&addr);
const s32 client = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
ASSERT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
Error::Ok);
std::thread aborter([&] {
std::this_thread::sleep_for(100ms);
SocketAbort(client, kSocketAbortPreserveRecv);
});
char buf[4];
const auto start = std::chrono::steady_clock::now();
EXPECT_EQ(SocketRecvFrom(client, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::Intr);
EXPECT_LT(Millis(start), 2000.0);
aborter.join();
SocketClose(client);
SocketClose(listener);
}
/// A bound UDP socket plus its own address, for abort tests that need a socket to wait on.
struct UdpPair {
s32 sock;
sockaddr_in addr;
};
UdpPair MakeUdp() {
UdpPair p{static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value), {}};
p.addr.sin_family = AF_INET;
p.addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
SocketBind(p.sock, reinterpret_cast<sockaddr*>(&p.addr), sizeof(p.addr));
socklen_t len = sizeof(p.addr);
SocketGetName(p.sock, reinterpret_cast<sockaddr*>(&p.addr), &len);
return p;
}
TEST_F(NetHostTest, AbortOnlyInterruptsCurrentWaits) {
auto [sock, addr] = MakeUdp();
char buf[4];
SocketSetRecvTimeout(sock, 5s); // bounds the test if the abort fired too early
// Wake a blocked recv.
std::thread aborter([&] {
std::this_thread::sleep_for(150ms);
SocketAbort(sock, 0);
});
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::Intr);
aborter.join();
// The socket is still usable: data sent now is received.
SocketSendTo(sock, "ok", 2, 0, false, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 2);
// Nothing blocked and no preservation flag: the abort has no lasting effect.
SocketAbort(sock, 0);
SocketSetRecvTimeout(sock, 100ms);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::WouldBlock); // timed out normally
SocketClose(sock);
}
TEST_F(NetHostTest, PreservedAbortHitsNextBlockingCallOnce) {
auto [sock, addr] = MakeUdp();
char buf[4];
SocketSetRecvTimeout(sock, 100ms);
// Preserved for the next receive of any kind, non-blocking included (sceNetSocketAbort:
// "the next receive or send function will return an error immediately").
EXPECT_EQ(SocketAbort(sock, kSocketAbortPreserveRecv).error, Error::Ok);
// Send-side calls do not consume a receive-side abort.
EXPECT_EQ(SocketSendTo(sock, "x", 1, 0, false, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))
.value,
1);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, true, nullptr, nullptr).error, Error::Intr);
// ... and only that one: the blocking receive after it gets the queued datagram.
const auto start = std::chrono::steady_clock::now();
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 1);
EXPECT_LT(Millis(start), 50.0);
// A blocking receive consumes it the same way, even with data queued.
SocketSendTo(sock, "z", 1, 0, false, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
SocketAbort(sock, kSocketAbortPreserveRecv);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::Intr);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 1);
// UDP sends never wait, but a preserved send abort still fails the next one.
SocketAbort(sock, kSocketAbortPreserveSend);
EXPECT_EQ(SocketSendTo(sock, "y", 1, 0, false, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))
.error,
Error::Intr);
EXPECT_EQ(SocketSendTo(sock, "y", 1, 0, false, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))
.value,
1);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).value, 1);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::WouldBlock);
SocketClose(sock);
// A send-side preservation hits the next blocking send-side call (here a connect, before it
// starts), not receives. UDP sends never wait on the PS4, so they are not affected.
sockaddr_in listen_addr{};
const s32 listener = MakeListener(&listen_addr);
const s32 tcp = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
SocketAbort(tcp, kSocketAbortPreserveSend);
EXPECT_EQ(SocketRecvFrom(tcp, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::NotConn);
const auto* to = reinterpret_cast<sockaddr*>(&listen_addr);
EXPECT_EQ(SocketConnect(tcp, to, sizeof(listen_addr)).error, Error::Intr);
EXPECT_EQ(SocketConnect(tcp, to, sizeof(listen_addr)).error, Error::Ok);
SocketClose(tcp);
SocketClose(listener);
}
TEST_F(NetHostTest, EpollAbortPreservation) {
const s32 ep = static_cast<s32>(EpollCreate().value);
GuestEpollEvent events[4];
// Nothing waiting, no flag: ENOTBLK, and no lasting effect.
EXPECT_EQ(EpollAbort(ep, 0).error, Error::NotBlk);
auto start = std::chrono::steady_clock::now();
EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0);
EXPECT_GE(Millis(start), 45.0);
// Preserved: the next wait fails at once, the one after waits normally.
EXPECT_EQ(EpollAbort(ep, kEpollAbortPreserve).error, Error::Ok);
EXPECT_EQ(EpollWait(ep, events, -1).error, Error::Intr);
start = std::chrono::steady_clock::now();
EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0);
EXPECT_GE(Millis(start), 45.0);
// After waking a wait, the wake is drained: later waits are not woken by it.
std::thread aborter([&] {
std::this_thread::sleep_for(100ms);
EpollAbort(ep, 0);
});
EXPECT_EQ(EpollWait(ep, events, 5'000'000).error, Error::Intr);
aborter.join();
start = std::chrono::steady_clock::now();
EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0);
EXPECT_GE(Millis(start), 45.0);
EpollDestroy(ep);
}
TEST_F(NetHostTest, CloseWakesBlockedAccept) {
sockaddr_in addr{};
const s32 listener = MakeListener(&addr);
NetResult r{};
std::thread t([&] { r = SocketAccept(listener, nullptr, nullptr); });
std::this_thread::sleep_for(100ms);
SocketClose(listener);
t.join();
EXPECT_EQ(r.error, Error::Intr);
}
TEST_F(NetHostTest, ConnectRefused) {
// Bind a port, then close it, so nothing listens there.
sockaddr_in addr{};
SocketClose(MakeListener(&addr));
const s32 client = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
EXPECT_EQ(SocketConnect(client, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
Error::ConnRefused);
SocketClose(client);
}
TEST_F(NetHostTest, NonBlockingRecvWouldBlock) {
const s32 sock = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
auto addr = Loopback();
SocketBind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
char buf[4];
// MSG_DONTWAIT on a blocking socket, then SO_NBIO.
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, true, nullptr, nullptr).error,
Error::WouldBlock);
SocketSetNonBlocking(sock, true);
EXPECT_EQ(SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr).error,
Error::WouldBlock);
SocketClose(sock);
}
TEST_F(NetHostTest, EpollLevelTriggeredAndAbort) {
const s32 sock = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
auto addr = Loopback();
SocketBind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
socklen_t len = sizeof(addr);
SocketGetName(sock, reinterpret_cast<sockaddr*>(&addr), &len);
const s32 ep = static_cast<s32>(EpollCreate().value);
ASSERT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0xabcd).error, Error::Ok);
EXPECT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0).error, Error::Exist);
GuestEpollEvent events[4];
EXPECT_EQ(EpollWait(ep, events, 0).value, 0); // nothing pending
const s32 sender = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
SocketSendTo(sender, "x", 1, 0, false, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
ASSERT_EQ(EpollWait(ep, events, 2'000'000).value, 1);
EXPECT_EQ(events[0].data, 0xabcdu);
EXPECT_EQ(events[0].ident, sock);
EXPECT_TRUE(events[0].events & Host::EvIn);
EXPECT_EQ(EpollWait(ep, events, 0).value, 1); // level-triggered: still pending
char buf[4];
SocketRecvFrom(sock, buf, sizeof(buf), 0, false, nullptr, nullptr);
EXPECT_EQ(EpollWait(ep, events, 0).value, 0);
// Deleting, then closing: no stale events.
EXPECT_EQ(EpollControl(ep, EpollOp::Delete, sock, 0, 0, 0).error, Error::Ok);
EXPECT_EQ(EpollControl(ep, EpollOp::Delete, sock, 0, 0, 0).error, Error::NoEnt);
std::thread aborter([&] {
std::this_thread::sleep_for(100ms);
EpollAbort(ep, kEpollAbortPreserve);
});
EXPECT_EQ(EpollWait(ep, events, -1).error, Error::Intr);
aborter.join();
SocketClose(sender);
SocketClose(sock);
EpollDestroy(ep);
}
TEST_F(NetHostTest, ClosedSocketLeavesEpoll) {
const s32 sock = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
const s32 ep = static_cast<s32>(EpollCreate().value);
EpollControl(ep, EpollOp::Add, sock, Host::EvOut, 0, 1);
GuestEpollEvent events[4];
EXPECT_EQ(EpollWait(ep, events, 0).value, 1); // UDP is writable
SocketClose(sock);
EXPECT_EQ(EpollWait(ep, events, 0).value, 0);
EXPECT_EQ(SocketRecvFrom(sock, nullptr, 0, 0, false, nullptr, nullptr).error, Error::BadF);
EpollDestroy(ep);
}
TEST_F(NetHostTest, SocketsAndEpollsShareOneIdSpace) {
const s32 sock = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
const s32 ep = static_cast<s32>(EpollCreate().value);
ASSERT_GE(sock, 0);
ASSERT_GE(ep, 0);
EXPECT_NE(sock, ep);
EXPECT_EQ(GetObjectKind(sock), NetObjectKind::Socket);
EXPECT_EQ(GetObjectKind(ep), NetObjectKind::Epoll);
// An id of the wrong kind is a bad descriptor, and is left alone.
EXPECT_EQ(SocketClose(ep).error, Error::BadF);
EXPECT_EQ(EpollDestroy(sock).error, Error::BadF);
EXPECT_EQ(EpollControl(ep, EpollOp::Add, ep, Host::EvIn, 0, 0).error, Error::BadF);
EXPECT_EQ(GetObjectKind(ep), NetObjectKind::Epoll);
// Lowest free id first, whatever kind takes it.
ASSERT_EQ(CloseObject(sock).error, Error::Ok);
EXPECT_FALSE(GetObjectKind(sock));
const s32 ep2 = static_cast<s32>(EpollCreate().value);
EXPECT_EQ(ep2, sock);
EXPECT_EQ(CloseObject(ep2).error, Error::Ok);
EXPECT_EQ(CloseObject(ep).error, Error::Ok);
EXPECT_EQ(CloseObject(ep).error, Error::BadF);
}
TEST_F(NetHostTest, SocketLimitIs128) {
std::vector<s32> sockets;
for (int i = 0; i < MaxSockets + 1; ++i) {
const auto r = SocketCreate(AF_INET, SOCK_DGRAM, 0);
if (r.error != Error::Ok) {
EXPECT_EQ(r.error, Error::MFile);
break;
}
sockets.push_back(static_cast<s32>(r.value));
}
EXPECT_EQ(sockets.size(), static_cast<size_t>(MaxSockets)); // no other test leaks sockets
// Epolls do not count against the limit.
const auto ep = EpollCreate();
EXPECT_EQ(ep.error, Error::Ok);
// Closing one makes room for one.
SocketClose(sockets.back());
sockets.pop_back();
const auto again = SocketCreate(AF_INET, SOCK_DGRAM, 0);
ASSERT_EQ(again.error, Error::Ok);
sockets.push_back(static_cast<s32>(again.value));
EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::MFile);
for (const s32 id : sockets) {
SocketClose(id);
}
EpollDestroy(static_cast<s32>(ep.value));
}
TEST_F(NetHostTest, IdAllocatorHook) {
// Stands in for the kernel's file table.
std::mutex mutex;
s32 next = 1000;
std::vector<s32> released;
SetIdAllocator({
[&] {
std::scoped_lock lock{mutex};
return next++;
},
[&](s32 id) {
std::scoped_lock lock{mutex};
released.push_back(id);
},
});
const s32 sock = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
const s32 ep = static_cast<s32>(EpollCreate().value);
EXPECT_EQ(sock, 1000);
EXPECT_EQ(ep, 1001);
CloseObject(sock);
CloseObject(ep);
EXPECT_EQ(released, (std::vector<s32>{1000, 1001}));
// A table that is full fails creation with ENFILE.
SetIdAllocator({[] { return -1; }, [](s32) {}});
EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::NFile);
EXPECT_EQ(EpollCreate().error, Error::NFile);
SetIdAllocator({}); // back to the built-in allocator
const s32 plain = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
EXPECT_LT(plain, 1000);
SocketClose(plain);
}
TEST_F(NetHostTest, HostErrorsKeepTheirMeaning) {
// Errors that used to fall through to EIO.
const s32 udp = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
EXPECT_EQ(SocketAccept(udp, nullptr, nullptr).error, Error::OpNotSupp);
auto broadcast = Loopback(9);
broadcast.sin_addr.s_addr = htonl(INADDR_BROADCAST);
EXPECT_EQ(SocketSendTo(udp, "x", 1, 0, false, reinterpret_cast<sockaddr*>(&broadcast),
sizeof(broadcast))
.error,
Error::Acces); // no SO_BROADCAST
SocketClose(udp);
EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, IPPROTO_TCP).error, Error::ProtoNoSupport);
// The enum carries FreeBSD's numbering, whatever the host's is.
#ifndef _WIN32
EXPECT_EQ(Host::TranslateNative(EACCES), Error::Acces);
EXPECT_EQ(Host::TranslateNative(EOPNOTSUPP), Error::OpNotSupp);
EXPECT_EQ(Host::TranslateNative(EMFILE), Error::MFile);
EXPECT_EQ(Host::TranslateNative(ENETRESET), Error::NetReset);
EXPECT_EQ(Host::TranslateNative(EHOSTDOWN), Error::HostDown);
#endif
EXPECT_EQ(static_cast<int>(Error::OpNotSupp), 45);
EXPECT_EQ(static_cast<int>(Error::NetReset), 52);
EXPECT_EQ(static_cast<int>(Error::NotEmpty), 66);
// Anything unknown is the PS4's EINTERNAL.
EXPECT_EQ(Host::TranslateNative(987654), Error::Internal);
EXPECT_EQ(static_cast<int>(Error::Internal), 204);
}
TEST_F(NetHostTest, ExternalIdsReportCompletionOnce) {
// What resolvers use: an id from the same space that an epoll can watch.
const s32 ep = static_cast<s32>(EpollCreate().value);
const auto reserved = ReserveExternalId();
ASSERT_EQ(reserved.error, Error::Ok);
const s32 id = static_cast<s32>(reserved.value);
EXPECT_EQ(GetObjectKind(id), NetObjectKind::External);
EXPECT_EQ(CloseObject(id).error, Error::BadF); // released by its owner only
ASSERT_EQ(EpollControl(ep, EpollOp::Add, id, Host::EvIn, 0, 0xABC).error, Error::Ok);
EXPECT_EQ(EpollControl(ep, EpollOp::Add, id, Host::EvIn, 0, 0).error, Error::Exist);
GuestEpollEvent events[4];
EXPECT_EQ(EpollWait(ep, events, 0).value, 0);
std::thread signaller([&] {
std::this_thread::sleep_for(50ms);
SignalExternal(id);
});
ASSERT_EQ(EpollWait(ep, events, 2'000'000).value, 1); // woken by the signal
signaller.join();
EXPECT_EQ(events[0].events, Host::EvIn | Host::EvDescId);
EXPECT_EQ(events[0].ident, id);
EXPECT_EQ(events[0].data, 0xABCu);
// Reported once: the next wait times out instead of spinning on the set flag.
const auto start = std::chrono::steady_clock::now();
EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0);
EXPECT_GE(Millis(start), 45.0);
// Releasing removes it from the epoll; the id is gone.
EXPECT_EQ(ReleaseExternalId(id).error, Error::Ok);
EXPECT_FALSE(GetObjectKind(id));
EXPECT_EQ(ReleaseExternalId(id).error, Error::BadF);
EXPECT_EQ(EpollControl(ep, EpollOp::Delete, id, 0, 0, 0).error, Error::BadF);
EpollDestroy(ep);
}
TEST_F(NetHostTest, ExternalIdsCountTowardsTheLimit) {
std::vector<s32> ids;
for (int i = 0; i < MaxSockets; ++i) {
ids.push_back(static_cast<s32>(ReserveExternalId().value));
}
EXPECT_EQ(SocketCreate(AF_INET, SOCK_DGRAM, 0).error, Error::MFile);
EXPECT_EQ(ReserveExternalId().error, Error::MFile);
for (const s32 id : ids) {
ReleaseExternalId(id);
}
}
TEST_F(NetHostTest, SocketList) {
const s32 a = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
const s32 ep = static_cast<s32>(EpollCreate().value);
const s32 b = static_cast<s32>(SocketCreate(AF_INET, SOCK_STREAM, 0).value);
EXPECT_EQ(ListSockets(), (std::vector<s32>{a, b})); // sockets only, ascending
SocketClose(a);
SocketClose(b);
EpollDestroy(ep);
EXPECT_TRUE(ListSockets().empty());
}
TEST_F(NetHostTest, SelectOnNativeSockets) {
const s32 udp = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ASSERT_EQ(SocketBind(udp, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error, Error::Ok);
socklen_t len = sizeof(addr);
SocketGetName(udp, reinterpret_cast<sockaddr*>(&addr), &len);
// Writable at once, not readable; the same socket listed twice is fine.
SelectEntry entries[] = {{udp, Host::EvIn, 0}, {udp, Host::EvOut, 0}};
auto r = SocketSelect(entries, 0);
ASSERT_EQ(r.error, Error::Ok);
EXPECT_EQ(r.value, 1);
EXPECT_EQ(entries[0].ready, 0u);
EXPECT_EQ(entries[1].ready, Host::EvOut);
// Readable once a datagram is queued; a wait with a timeout wakes for it.
SelectEntry read_only[] = {{udp, Host::EvIn, 0}};
const auto start = std::chrono::steady_clock::now();
EXPECT_EQ(SocketSelect(read_only, 50'000).value, 0); // times out
EXPECT_GE(std::chrono::steady_clock::now() - start, std::chrono::milliseconds{40});
std::thread sender([&] {
std::this_thread::sleep_for(std::chrono::milliseconds{100});
Error e;
const auto helper = Host::CreateSocket(AF_INET, SOCK_DGRAM, 0, &e);
Host::SendTo(helper, "y", 1, 0, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
Host::CloseSocket(helper);
});
r = SocketSelect(read_only, -1);
sender.join();
EXPECT_EQ(r.value, 1);
EXPECT_EQ(read_only[0].ready, Host::EvIn);
// Not a socket: EBADF. Nothing listed: nothing ready.
const s32 ep = static_cast<s32>(EpollCreate().value);
SelectEntry bad[] = {{ep, Host::EvIn, 0}};
EXPECT_EQ(SocketSelect(bad, 0).error, Error::BadF);
EXPECT_EQ(SocketSelect({}, 0).value, 0);
EpollDestroy(ep);
// Many sockets at once: every ready one is reported, beyond one host wait's 64.
std::vector<s32> many;
std::vector<SelectEntry> all;
for (int i = 0; i < 100; ++i) {
many.push_back(static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value));
all.push_back({many.back(), Host::EvOut, 0});
}
EXPECT_EQ(SocketSelect(all, 0).value, 100);
for (const s32 id : many) {
SocketClose(id);
}
SocketClose(udp);
}
TEST_F(NetHostTest, SocketPair) {
s32 ids[2];
#ifdef _WIN32
const int family = AF_INET; // emulated with a loopback TCP connection
#else
const int family = AF_UNIX;
#endif
ASSERT_EQ(SocketCreatePair(family, SOCK_STREAM, 0, ids).error, Error::Ok);
EXPECT_NE(ids[0], ids[1]);
EXPECT_EQ(SocketSendTo(ids[0], "ping", 4, 0, false, nullptr, 0).value, 4);
char buf[8]{};
EXPECT_EQ(SocketRecvFrom(ids[1], buf, sizeof(buf), 0, false, nullptr, nullptr).value, 4);
EXPECT_STREQ(buf, "ping");
EXPECT_EQ(SocketSendTo(ids[1], "pong", 4, 0, false, nullptr, 0).value, 4);
EXPECT_EQ(SocketRecvFrom(ids[0], buf, sizeof(buf), 0, false, nullptr, nullptr).value, 4);
// Both ends are ordinary sockets: closing one gives the other EOF.
SocketClose(ids[0]);
EXPECT_EQ(SocketRecvFrom(ids[1], buf, sizeof(buf), 0, false, nullptr, nullptr).value, 0);
SocketClose(ids[1]);
}
TEST_F(NetHostTest, OrbisReturnCodes) {
EXPECT_EQ(ToOrbisReturn(NetResult::Ok(7)), 7);
// ORBIS_NET_ERROR_EWOULDBLOCK / EINTR: 0x80410100 | FreeBSD errno.
EXPECT_EQ(static_cast<u32>(ToOrbisReturn(NetResult::Fail(Error::WouldBlock))), 0x80410123u);
EXPECT_EQ(static_cast<u32>(ToOrbisReturn(NetResult::Fail(Error::Intr))), 0x80410104u);
}
} // namespace
+162
View File
@@ -0,0 +1,162 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// The net library inside the kernel: net ids are file descriptors from the kernel's file table,
// the kernel's close/read/write/fstat reach them, and the libkernel / libScePosix socket calls
// report errors the POSIX way (-1 and the kernel errno).
#include <cstring>
#include <gtest/gtest.h>
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_kernel.h"
#include "core/libraries/net/net_p2p.h"
#include "core/net/guest_net.h"
using namespace Libraries::Net;
using FDTable = Common::Singleton<Core::FileSys::HandleTable>;
namespace {
class NetKernel : public ::testing::Test {
protected:
static void SetUpTestSuite() {
FDTable::Instance()->CreateStdHandles();
InstallKernelIntegration();
ASSERT_EQ(sceNetInit(), 0);
}
static int Errno() {
return *Libraries::Kernel::__Error();
}
static OrbisNetSockaddrIn Loopback() {
OrbisNetSockaddrIn in{};
in.sin_len = sizeof(in);
in.sin_family = ORBIS_NET_AF_INET;
in.sin_addr = sceNetHtonl(0x7F000001);
return in;
}
};
TEST_F(NetKernel, SocketsAreFileDescriptors) {
const int fd = sys_socket(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, 0);
ASSERT_GE(fd, 3); // after stdin, stdout, stderr
auto* file = FDTable::Instance()->GetFile(fd);
ASSERT_NE(file, nullptr);
EXPECT_EQ(file->type, Core::FileSys::FileType::Socket);
EXPECT_TRUE(file->is_opened);
// select() on it: a fresh UDP socket is writable, not readable.
Core::Net::SelectEntry sel[] = {{fd, Core::Net::Host::EvIn | Core::Net::Host::EvOut, 0}};
EXPECT_EQ(KernelSelect(sel, 0), 1);
EXPECT_EQ(sel[0].ready, Core::Net::Host::EvOut);
// write() and read() on the descriptor are send and recv.
auto addr = Loopback();
ASSERT_EQ(sys_bind(fd, reinterpret_cast<OrbisNetSockaddr*>(&addr), sizeof(addr)), 0);
u32 len = sizeof(addr);
ASSERT_EQ(sys_getsockname(fd, reinterpret_cast<OrbisNetSockaddr*>(&addr), &len), 0);
ASSERT_EQ(sys_connect(fd, reinterpret_cast<OrbisNetSockaddr*>(&addr), sizeof(addr)), 0);
EXPECT_EQ(KernelWrite(fd, "data", 4), 4);
char buf[8]{};
EXPECT_EQ(KernelRead(fd, buf, sizeof(buf)), 4);
EXPECT_STREQ(buf, "data");
Libraries::Kernel::OrbisKernelStat st{};
ASSERT_EQ(KernelFstat(fd, &st), 0);
EXPECT_EQ(st.st_mode & 0170000, 0140000); // S_IFSOCK
ASSERT_EQ(KernelClose(fd), 0);
EXPECT_EQ(FDTable::Instance()->GetFile(fd), nullptr);
EXPECT_FALSE(Core::Net::GetObjectKind(fd));
EXPECT_EQ(KernelClose(fd), -1);
EXPECT_EQ(Errno(), ORBIS_NET_EBADF);
EXPECT_EQ(sceNetSocketClose(fd), ORBIS_NET_ERROR_EBADF);
}
TEST_F(NetKernel, DescriptorsAreReused) {
const int a = sys_socket(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
ASSERT_EQ(sys_socketclose(a), 0); // the sceNet way frees the descriptor as well
EXPECT_EQ(FDTable::Instance()->GetFile(a), nullptr);
const int b = sys_socket(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
EXPECT_EQ(b, a); // lowest free descriptor, as the kernel hands them out
KernelClose(b);
}
TEST_F(NetKernel, PosixErrorsUseTheKernelErrno) {
*sceNetErrnoLoc() = 1234;
*Libraries::Kernel::__Error() = 0;
EXPECT_EQ(sys_listen(1234, 1), -1);
EXPECT_EQ(Errno(), ORBIS_NET_EBADF);
EXPECT_EQ(sys_socket(99, ORBIS_NET_SOCK_STREAM, 0), -1);
EXPECT_EQ(Errno(), ORBIS_NET_EPROTONOSUPPORT); // as sceNetSocket reports it
EXPECT_EQ(*sceNetErrnoLoc(), 1234);
char buf[4];
EXPECT_EQ(KernelRead(1234, buf, sizeof(buf)), -1);
EXPECT_EQ(*sceNetErrnoLoc(), 1234);
// The sceNet* call itself does set it.
EXPECT_EQ(sceNetListen(1234, 1), ORBIS_NET_ERROR_EBADF);
EXPECT_EQ(*sceNetErrnoLoc(), ORBIS_NET_EBADF);
EXPECT_EQ(sys_htons(0x1234), 0x3412);
}
TEST_F(NetKernel, SocketPair) {
int sv[2]{-1, -1};
EXPECT_EQ(sys_socketpair(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0, sv), -1);
EXPECT_EQ(Errno(), ORBIS_NET_EAFNOSUPPORT); // AF_UNIX only, as on FreeBSD
ASSERT_EQ(sys_socketpair(ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_STREAM, 0, sv), 0);
EXPECT_NE(FDTable::Instance()->GetFile(sv[0]), nullptr);
EXPECT_NE(FDTable::Instance()->GetFile(sv[1]), nullptr);
EXPECT_EQ(KernelWrite(sv[0], "abc", 3), 3);
char buf[4]{};
EXPECT_EQ(sys_recv(sv[1], buf, sizeof(buf), 0), 3);
EXPECT_STREQ(buf, "abc");
KernelClose(sv[0]);
KernelClose(sv[1]);
}
TEST_F(NetKernel, EpollsAndResolversCloseToo) {
const int ep = sceNetEpollCreate("ep", 0);
const s32 pool = sceNetPoolCreate("pool", 16 * 1024, 0);
const int rid = sceNetResolverCreate("rid", pool, 0);
ASSERT_GE(ep, 3);
ASSERT_GE(rid, 3);
EXPECT_NE(FDTable::Instance()->GetFile(ep), nullptr);
EXPECT_NE(FDTable::Instance()->GetFile(rid), nullptr);
// Descriptors, but nothing to select on: never ready, and not an error.
Core::Net::SelectEntry sel[] = {{ep, Core::Net::Host::EvIn, 0},
{rid, Core::Net::Host::EvIn, 0}};
EXPECT_EQ(KernelSelect(sel, 0), 0);
EXPECT_EQ(sel[0].ready, 0u);
char buf[4];
EXPECT_EQ(KernelRead(ep, buf, sizeof(buf)), -1); // not readable
EXPECT_EQ(KernelClose(ep), 0);
EXPECT_EQ(KernelClose(rid), 0);
EXPECT_EQ(FDTable::Instance()->GetFile(ep), nullptr);
EXPECT_EQ(FDTable::Instance()->GetFile(rid), nullptr);
EXPECT_EQ(sceNetResolverDestroy(rid), ORBIS_NET_ERROR_EBADF);
EXPECT_EQ(sceNetPoolDestroy(pool), ORBIS_OK); // close() gave the pool back
}
TEST_F(NetKernel, P2PPortComesFromTheSettings) {
EmulatorSettings.SetP2PPort(0); // any free port
EXPECT_EQ(GetP2PConfiguredPort(), 0);
ASSERT_TRUE(EnsureP2PTransport());
EXPECT_NE(GetP2PBoundPort(), 0);
EXPECT_EQ(GetP2PAdvertisedPort(), GetP2PBoundPort());
StopP2P(); // offline by default: no UPnP involved either way
EXPECT_FALSE(P2PTransportIsReady());
EmulatorSettings.SetP2PPort(70000); // not a port: the console's
EXPECT_EQ(GetP2PConfiguredPort(), 3658);
EmulatorSettings.SetP2PPort(3658);
}
} // namespace
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// The P2P key ioctl (sceNetIoctl on P2P sockets): how the NP libraries install keys and the
// communication ID. Tested on a Keyring directly, without sockets.
#include <array>
#include <cstring>
#include <gtest/gtest.h>
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_p2p_ioctl.h"
#include "core/libraries/net/net_types.h"
#include "core/net/p2p_codec.h"
using namespace Libraries::Net;
namespace P2P = Core::Net::P2P;
namespace {
constexpr u16 NpPort = 0x4a0e; // 3658 in network byte order
// ioctl numbers: group 'P' (0x50), 36-byte argument written by the caller (IOC_IN).
constexpr u64 SetDefault = 0x802450c9;
constexpr u64 GetDefault = 0xc02450ca;
constexpr u64 AddKey = 0x802450cb;
constexpr u64 ReleaseKey = 0x802450cc;
constexpr u64 SetWildcard = 0x802450cd;
constexpr u64 GetWildcard = 0xc02450ce;
constexpr u64 GetNpPort = 0xc00250cf;
constexpr u64 RemoveById = 0x802450c8;
constexpr u64 SetCommunicationIdCmd = 0x801050d8;
constexpr u64 ClearCommunicationId = 0x200050d9;
/// The 36-byte argument: IPv4 P2P sockaddr, key value, local port, flags, id.
struct Record {
std::array<u8, 36> bytes{};
Record(const char* ip, u16 port, u8 key_fill, u8 id, u16 local_port = 0) {
OrbisNetSockaddrIn in{};
in.sin_len = sizeof(in);
in.sin_family = ORBIS_NET_AF_INET;
in.sin_port = htons(port);
inet_pton(AF_INET, ip, &in.sin_addr);
std::memcpy(bytes.data(), &in, sizeof(in));
std::memset(bytes.data() + 16, key_fill, 16);
std::memcpy(bytes.data() + 32, &local_port, 2);
bytes[34] = 0x01; // flags
bytes[35] = id;
}
void* data() {
return bytes.data();
}
};
P2P::P2PKey Key(u8 fill) {
P2P::P2PKey key;
key.value.fill(fill);
return key;
}
const P2P::Endpoint Peer = P2P::Endpoint::IPv4("198.51.100.4", 3658);
TEST(NetLibP2PIoctl, CommunicationIdIsDerived) {
P2P::Keyring keys;
std::array<u8, 16> value;
for (u8 i = 0; i < 16; ++i) {
value[i] = i;
}
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, SetCommunicationIdCmd, value.data()), 0);
// HMAC-SHA1(key = value, message = "")[0..4], from an independent implementation (Python).
EXPECT_EQ(keys.GetCommunicationId(), (P2P::CommunicationId{0x54, 0x33, 0x12, 0x2f}));
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, ClearCommunicationId, nullptr), 0);
EXPECT_FALSE(keys.GetCommunicationId());
}
TEST(NetLibP2PIoctl, PeerKeysAreReferenceCounted) {
P2P::Keyring keys;
Record record("198.51.100.4", 3658, 0x11, 7);
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, record.data()), 0);
const auto entry = keys.FindPeerEntry(Peer);
ASSERT_TRUE(entry);
EXPECT_EQ(entry->key, Key(0x11));
EXPECT_EQ(entry->id, 7);
EXPECT_EQ(entry->flags, 1);
EXPECT_EQ(entry->local_port, NpPort); // 0 in the record means the NP port
// Installed twice, so it takes two releases to go away.
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, record.data()), 0);
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, record.data()), 0);
EXPECT_TRUE(keys.FindPeerEntry(Peer));
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, record.data()), 0);
EXPECT_FALSE(keys.FindPeerEntry(Peer));
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, record.data()), ORBIS_NET_ENOENT);
// A release must name the same key value and id.
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, record.data()), 0);
Record wrong_id("198.51.100.4", 3658, 0x11, 8);
Record wrong_key("198.51.100.4", 3658, 0x22, 7);
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, wrong_id.data()), ORBIS_NET_ENOENT);
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, ReleaseKey, wrong_key.data()), ORBIS_NET_ENOENT);
// A different value replaces the key; an explicit local port is kept.
Record other("198.51.100.4", 3658, 0x33, 7, 0x1234);
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, other.data()), 0);
EXPECT_EQ(keys.FindPeerEntry(Peer)->key, Key(0x33));
EXPECT_EQ(keys.FindPeerEntry(Peer)->local_port, 0x1234);
}
TEST(NetLibP2PIoctl, RemoveKeysById) {
P2P::Keyring keys;
Record a("198.51.100.4", 3658, 0x11, 5);
Record b("198.51.100.5", 3658, 0x22, 5);
Record c("198.51.100.6", 3658, 0x33, 6);
for (auto* r : {&a, &b, &c}) {
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, r->data()), 0);
}
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, RemoveById, a.data()), 0); // id 5
EXPECT_FALSE(keys.FindPeerEntry(P2P::Endpoint::IPv4("198.51.100.4", 3658)));
EXPECT_FALSE(keys.FindPeerEntry(P2P::Endpoint::IPv4("198.51.100.5", 3658)));
EXPECT_TRUE(keys.FindPeerEntry(P2P::Endpoint::IPv4("198.51.100.6", 3658)));
}
TEST(NetLibP2PIoctl, DefaultAndWildcardKeys) {
P2P::Keyring keys;
Record default_key("0.0.0.0", 0, 0x44, 1);
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, SetDefault, default_key.data()), 0);
EXPECT_EQ(keys.Find(Peer), Key(0x44)); // falls back to the default key
EXPECT_EQ(keys.FindPeerEntry(P2P::Endpoint::IPv4("127.0.0.1", 3658))->key, Key(0x44));
Record out("0.0.0.0", 0, 0x00, 0);
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetDefault, out.data()), 0);
EXPECT_EQ(out.bytes[16], 0x44);
EXPECT_EQ(out.bytes[31], 0x44);
// The wildcard key wins over the default key; reading it back works too.
Record out_wild("0.0.0.0", 0, 0xee, 0);
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetWildcard, out_wild.data()), 0);
EXPECT_EQ(out_wild.bytes[16], 0x00); // none yet: zeros
Record wildcard("0.0.0.0", 0, 0x55, 1);
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, SetWildcard, wildcard.data()), 0);
EXPECT_EQ(keys.Find(Peer), Key(0x55));
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetWildcard, out_wild.data()), 0);
EXPECT_EQ(out_wild.bytes[16], 0x55);
}
TEST(NetLibP2PIoctl, NpPortAndErrors) {
P2P::Keyring keys;
u16 port = 0;
ASSERT_EQ(P2PKeyIoctl(keys, NpPort, GetNpPort, &port), 0);
EXPECT_EQ(port, NpPort);
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, 0x8004667e /* FIONBIO */, &port), ORBIS_NET_EINVAL);
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, nullptr), ORBIS_NET_EFAULT);
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, 0x200050c8, nullptr), 0); // the no-op command
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, 0x802450ff, &port), ORBIS_NET_EINVAL);
// Adding a key needs an IPv4 P2P sockaddr.
Record bad_family("198.51.100.4", 3658, 0x11, 7);
bad_family.bytes[1] = ORBIS_NET_AF_INET6;
EXPECT_EQ(P2PKeyIoctl(keys, NpPort, AddKey, bad_family.data()), ORBIS_NET_EINVAL);
}
} // namespace
+360
View File
@@ -0,0 +1,360 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// The guest <-> host translation used by the new libSceNet (src/core/libraries/net): pure
// functions, tested without sockets.
#include <cstddef>
#include <cstring>
#include <gtest/gtest.h>
#ifndef _WIN32
#include <sys/un.h>
#else
#include <winsock2.h>
#include <afunix.h>
#endif
#include "core/libraries/net/net_error.h"
#include "core/libraries/net/net_translate.h"
#ifndef _WIN32
#include <netinet/tcp.h>
#endif
using namespace Libraries::Net;
namespace {
OrbisNetSockaddrIn GuestIn(const char* ip, u16 port, u16 vport = 0, u8 sa_len = 16) {
OrbisNetSockaddrIn in{};
in.sin_len = sa_len;
in.sin_family = ORBIS_NET_AF_INET;
in.sin_port = htons(port);
inet_pton(AF_INET, ip, &in.sin_addr);
in.sin_vport = htons(vport);
return in;
}
const OrbisNetSockaddr* AsGuest(const void* p) {
return static_cast<const OrbisNetSockaddr*>(p);
}
TEST(NetLibTranslate, SocketKinds) {
int error = 0;
auto k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0, &error);
ASSERT_TRUE(k);
EXPECT_EQ(k->family, AF_INET);
EXPECT_EQ(k->type, SOCK_STREAM);
EXPECT_FALSE(k->p2p);
k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, ORBIS_NET_IPPROTO_UDP, &error);
ASSERT_TRUE(k);
EXPECT_EQ(k->type, SOCK_DGRAM);
EXPECT_EQ(k->protocol, IPPROTO_UDP);
// IPv4-only stack: no IPv6 sockets, P2P included.
for (const int type : {ORBIS_NET_SOCK_STREAM, ORBIS_NET_SOCK_DGRAM, ORBIS_NET_SOCK_DGRAM_P2P}) {
error = 0;
EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_INET6, type, 0, &error));
EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT); // sceNetSocket's code for a bad family
}
k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM_P2P, 0, &error);
ASSERT_TRUE(k);
EXPECT_TRUE(k->p2p);
EXPECT_EQ(k->type, SOCK_STREAM);
k = ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P, 0, &error);
ASSERT_TRUE(k);
EXPECT_TRUE(k->p2p);
EXPECT_EQ(k->type, SOCK_DGRAM);
// AF_UNIX: stream and datagram, protocol 0, never P2P.
k = ToHostSocketKind(ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM, 0, &error);
ASSERT_TRUE(k);
EXPECT_EQ(k->family, AF_UNIX);
EXPECT_EQ(k->type, SOCK_DGRAM);
EXPECT_EQ(k->protocol, 0);
EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_UNIX, ORBIS_NET_SOCK_DGRAM_P2P, 0, &error));
EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT);
EXPECT_FALSE(ToHostSocketKind(99, ORBIS_NET_SOCK_STREAM, 0, &error));
EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT);
// Non-RAW types take 0 or their own protocol; anything else is EPROTOTYPE.
EXPECT_FALSE(
ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM, ORBIS_NET_IPPROTO_TCP, &error));
EXPECT_EQ(error, ORBIS_NET_EPROTOTYPE);
EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_DGRAM_P2P,
ORBIS_NET_IPPROTO_UDP, &error));
EXPECT_EQ(error, ORBIS_NET_EPROTOTYPE);
EXPECT_TRUE(
ToHostSocketKind(ORBIS_NET_AF_INET, ORBIS_NET_SOCK_RAW, ORBIS_NET_IPPROTO_ICMP, &error));
EXPECT_FALSE(ToHostSocketKind(ORBIS_NET_AF_INET, 77, 0, &error));
EXPECT_EQ(error, ORBIS_NET_EPROTONOSUPPORT);
EXPECT_EQ(ToOrbisSocketType(SOCK_STREAM, false), static_cast<int>(ORBIS_NET_SOCK_STREAM));
EXPECT_EQ(ToOrbisSocketType(SOCK_DGRAM, true), static_cast<int>(ORBIS_NET_SOCK_DGRAM_P2P));
EXPECT_EQ(ToOrbisSocketType(SOCK_STREAM, true), static_cast<int>(ORBIS_NET_SOCK_STREAM_P2P));
EXPECT_EQ(ToOrbisFamily(AF_INET6), static_cast<int>(ORBIS_NET_AF_INET6));
}
TEST(NetLibTranslate, Ipv4SockaddrBothWays) {
// sa_len is not trusted: games often leave it 0.
for (const u8 sa_len : {u8{16}, u8{0}}) {
const auto guest = GuestIn("192.0.2.7", 3658, 0, sa_len);
sockaddr_storage host{};
socklen_t len = 0;
ASSERT_EQ(ToHostSockaddr(AsGuest(&guest), sizeof(guest), &host, &len), 0);
ASSERT_EQ(len, static_cast<socklen_t>(sizeof(sockaddr_in)));
const auto* in = reinterpret_cast<const sockaddr_in*>(&host);
EXPECT_EQ(in->sin_family, AF_INET);
EXPECT_EQ(ntohs(in->sin_port), 3658);
char text[32];
inet_ntop(AF_INET, &in->sin_addr, text, sizeof(text));
EXPECT_STREQ(text, "192.0.2.7");
}
sockaddr_in host{};
host.sin_family = AF_INET;
host.sin_port = htons(80);
inet_pton(AF_INET, "10.1.2.3", &host.sin_addr);
OrbisNetSockaddrIn back{};
u32 len = sizeof(back);
ToOrbisSockaddr(reinterpret_cast<sockaddr*>(&host), sizeof(host),
reinterpret_cast<OrbisNetSockaddr*>(&back), &len);
EXPECT_EQ(len, 16u);
EXPECT_EQ(back.sin_len, 16);
EXPECT_EQ(back.sin_family, ORBIS_NET_AF_INET);
EXPECT_EQ(back.sin_port, htons(80));
EXPECT_EQ(back.sin_vport, 0);
}
TEST(NetLibTranslate, Ipv6Sockaddr) {
OrbisNetSockaddrIn6 guest{};
guest.sin6_len = sizeof(guest);
guest.sin6_family = ORBIS_NET_AF_INET6;
guest.sin6_port = htons(443);
guest.sin6_scope_id = 3;
inet_pton(AF_INET6, "2001:db8::1", guest.sin6_addr);
sockaddr_storage host{};
socklen_t len = 0;
ASSERT_EQ(ToHostSockaddr(AsGuest(&guest), sizeof(guest), &host, &len), 0);
const auto* in6 = reinterpret_cast<const sockaddr_in6*>(&host);
EXPECT_EQ(in6->sin6_family, AF_INET6);
EXPECT_EQ(ntohs(in6->sin6_port), 443);
EXPECT_EQ(in6->sin6_scope_id, 3u);
OrbisNetSockaddrIn6 back{};
u32 back_len = sizeof(back);
ToOrbisSockaddr(reinterpret_cast<sockaddr*>(&host), len,
reinterpret_cast<OrbisNetSockaddr*>(&back), &back_len);
EXPECT_EQ(back_len, 28u);
EXPECT_EQ(back.sin6_len, 28);
EXPECT_EQ(back.sin6_family, ORBIS_NET_AF_INET6);
EXPECT_EQ(std::memcmp(back.sin6_addr, guest.sin6_addr, 16), 0);
}
TEST(NetLibTranslate, SockaddrErrorsAndTruncation) {
sockaddr_storage host{};
socklen_t len = 0;
EXPECT_EQ(ToHostSockaddr(nullptr, 16, &host, &len), ORBIS_NET_EFAULT);
const auto guest = GuestIn("1.2.3.4", 1);
EXPECT_EQ(ToHostSockaddr(AsGuest(&guest), 8, &host, &len), ORBIS_NET_EINVAL); // too short
auto unknown = guest;
unknown.sin_family = 99;
EXPECT_EQ(ToHostSockaddr(AsGuest(&unknown), 16, &host, &len), ORBIS_NET_EAFNOSUPPORT);
sockaddr_in in{};
in.sin_family = AF_INET;
in.sin_port = htons(0x1234);
u8 buffer[4]{};
u32 cap = sizeof(buffer);
ToOrbisSockaddr(reinterpret_cast<sockaddr*>(&in), sizeof(in),
reinterpret_cast<OrbisNetSockaddr*>(buffer), &cap);
EXPECT_EQ(cap, 16u);
EXPECT_EQ(buffer[0], 16);
EXPECT_EQ(buffer[1], ORBIS_NET_AF_INET);
EXPECT_EQ(buffer[2], 0x12);
EXPECT_EQ(buffer[3], 0x34);
}
TEST(NetLibTranslate, UnixAddresses) {
OrbisNetSockaddrUn guest{};
guest.sun_len = sizeof(guest);
guest.sun_family = ORBIS_NET_AF_UNIX;
std::strcpy(guest.sun_path, "/tmp/shadps4.sock");
sockaddr_storage host{};
socklen_t len = 0;
ASSERT_EQ(
ToHostSockaddr(reinterpret_cast<OrbisNetSockaddr*>(&guest), sizeof(guest), &host, &len), 0);
const auto* un = reinterpret_cast<const sockaddr_un*>(&host);
EXPECT_EQ(un->sun_family, AF_UNIX);
EXPECT_STREQ(un->sun_path, "/tmp/shadps4.sock");
EXPECT_EQ(len, offsetof(sockaddr_un, sun_path) + std::strlen("/tmp/shadps4.sock") + 1);
OrbisNetSockaddrUn back{};
u32 back_len = sizeof(back);
ToOrbisSockaddr(reinterpret_cast<const sockaddr*>(&host), len,
reinterpret_cast<OrbisNetSockaddr*>(&back), &back_len);
EXPECT_EQ(back_len, sizeof(OrbisNetSockaddrUn));
EXPECT_EQ(back.sun_family, ORBIS_NET_AF_UNIX);
EXPECT_STREQ(back.sun_path, "/tmp/shadps4.sock");
// A path that does not fit the host's sun_path.
std::memset(guest.sun_path, 'a', sizeof(guest.sun_path));
EXPECT_EQ(
ToHostSockaddr(reinterpret_cast<OrbisNetSockaddr*>(&guest), sizeof(guest), &host, &len),
sizeof(guest.sun_path) >= sizeof(un->sun_path) ? ORBIS_NET_ENAMETOOLONG : 0);
}
TEST(NetLibTranslate, P2PDatagramAddresses) {
// sin_port = the peer's UDP port, sin_vport = the virtual port.
const auto guest = GuestIn("198.51.100.4", 3658, 5000);
Core::Net::P2P::Endpoint endpoint;
u16 vport = 0;
ASSERT_EQ(ToP2PAddress(AsGuest(&guest), sizeof(guest), P2PKind::Datagram, &endpoint, &vport),
0);
EXPECT_EQ(endpoint, Core::Net::P2P::Endpoint::IPv4("198.51.100.4", 3658));
EXPECT_EQ(vport, 5000); // host byte order
OrbisNetSockaddrIn back{};
u32 len = sizeof(back);
ToOrbisP2PAddress(endpoint, vport, P2PKind::Datagram,
reinterpret_cast<OrbisNetSockaddr*>(&back), &len);
EXPECT_EQ(len, 16u);
EXPECT_EQ(std::memcmp(&back, &guest, sizeof(back)), 0); // exact round trip
}
TEST(NetLibTranslate, P2PStreamAddresses) {
// sin_port = the TCP port inside the encapsulation, sin_vport = the peer's UDP port.
const auto guest = GuestIn("198.51.100.4", 49200, 3659);
Core::Net::P2P::Endpoint endpoint;
u16 tcp_port = 0;
ASSERT_EQ(ToP2PAddress(AsGuest(&guest), sizeof(guest), P2PKind::Stream, &endpoint, &tcp_port),
0);
EXPECT_EQ(endpoint, Core::Net::P2P::Endpoint::IPv4("198.51.100.4", 3659));
EXPECT_EQ(tcp_port, 49200);
OrbisNetSockaddrIn back{};
u32 len = sizeof(back);
ToOrbisP2PAddress(endpoint, tcp_port, P2PKind::Stream,
reinterpret_cast<OrbisNetSockaddr*>(&back), &len);
EXPECT_EQ(std::memcmp(&back, &guest, sizeof(back)), 0);
// No UDP port given: the standard PlayStation P2P port.
const auto no_udp = GuestIn("198.51.100.4", 49200, 0);
ASSERT_EQ(ToP2PAddress(AsGuest(&no_udp), sizeof(no_udp), P2PKind::Stream, &endpoint, &tcp_port),
0);
EXPECT_EQ(endpoint.Port(), DefaultP2PUdpPort);
}
TEST(NetLibTranslate, P2PAddressesAreIPv4Only) {
auto guest = GuestIn("198.51.100.4", 3658, 5000);
Core::Net::P2P::Endpoint endpoint;
u16 port = 0;
// Exactly 16 bytes: not shorter, and not the 32-byte IPv6 P2P sockaddr either.
EXPECT_EQ(ToP2PAddress(AsGuest(&guest), 15, P2PKind::Datagram, &endpoint, &port),
ORBIS_NET_EINVAL);
EXPECT_EQ(ToP2PAddress(AsGuest(&guest), 32, P2PKind::Datagram, &endpoint, &port),
ORBIS_NET_EINVAL);
guest.sin_family = ORBIS_NET_AF_INET6;
EXPECT_EQ(ToP2PAddress(AsGuest(&guest), sizeof(guest), P2PKind::Stream, &endpoint, &port),
ORBIS_NET_EAFNOSUPPORT);
EXPECT_EQ(ToP2PAddress(nullptr, sizeof(guest), P2PKind::Stream, &endpoint, &port),
ORBIS_NET_EFAULT);
}
TEST(NetLibTranslate, MessageFlags) {
auto f = ToHostMsgFlags(ORBIS_NET_MSG_PEEK | ORBIS_NET_MSG_DONTWAIT);
EXPECT_TRUE(f.peek);
EXPECT_TRUE(f.dontwait);
EXPECT_EQ(f.host, MSG_PEEK); // DONTWAIT is the guest layer's business
EXPECT_EQ(f.unsupported, 0);
f = ToHostMsgFlags(ORBIS_NET_MSG_WAITALL | 0x20000 /* MSG_NOSIGNAL */);
EXPECT_EQ(f.host, MSG_WAITALL); // 0x40 on the PS4, 0x100 on Linux, 0x8 on Windows
EXPECT_EQ(f.unsupported, 0);
f = ToHostMsgFlags(ORBIS_NET_MSG_USECRYPTO | ORBIS_NET_MSG_PEEKLEN);
EXPECT_TRUE(f.peek);
EXPECT_TRUE(f.peeklen);
EXPECT_TRUE(f.crypto); // per-message protection is understood now
EXPECT_FALSE(f.signature);
EXPECT_EQ(f.unsupported, 0);
EXPECT_TRUE(ToHostMsgFlags(ORBIS_NET_MSG_WAITALL).waitall);
EXPECT_EQ(ToHostMsgFlags(0x1 /* MSG_OOB */).unsupported, 0x1);
}
TEST(NetLibTranslate, OutOfBandIsNotPassedThrough) {
// The PS4's TCP has no urgent data: MSG_OOB is reported, not handed to the host.
const auto f = ToHostMsgFlags(0x1 | ORBIS_NET_MSG_DONTWAIT);
EXPECT_EQ(f.host & MSG_OOB, 0);
EXPECT_EQ(f.unsupported, 0x1);
EXPECT_TRUE(f.dontwait);
}
TEST(NetLibTranslate, ReservedPorts) {
// UDP, TCP and TCP over UDPP2P ports.
for (const u16 port : {1, 80, 1023, 5353, 8540, 8579, 9293, 9310, 40000, 49152, 65535}) {
EXPECT_TRUE(IsReservedPort(port)) << port;
}
for (const u16 port : {0, 1024, 3658, 5352, 5354, 8539, 8580, 9292, 9311, 39999}) {
EXPECT_FALSE(IsReservedPort(port)) << port;
}
// UDPP2P virtual ports.
for (const u16 vport : {5353, 32768, 49999, 65535}) {
EXPECT_TRUE(IsReservedP2PVport(vport)) << vport;
}
for (const u16 vport : {0, 1, 80, 3658, 32767}) {
EXPECT_FALSE(IsReservedP2PVport(vport)) << vport;
}
}
TEST(NetLibTranslate, DefaultBufferSizes) {
EXPECT_EQ(DefaultSendBuffer(SOCK_STREAM), 32768);
EXPECT_EQ(DefaultReceiveBuffer(SOCK_STREAM), 65536);
EXPECT_EQ(DefaultSendBuffer(SOCK_DGRAM), 9216);
EXPECT_EQ(DefaultReceiveBuffer(SOCK_DGRAM), 40960);
EXPECT_EQ(DefaultReceiveBuffer(SOCK_RAW), 40960);
}
TEST(NetLibTranslate, OptionsMapByName) {
// Host numbering differs (on Linux IP_TTL is 2, not 4): the mapping must use host names.
auto o = ToHostOption(ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_TTL);
ASSERT_TRUE(o);
EXPECT_EQ(o->level, IPPROTO_IP);
EXPECT_EQ(o->name, IP_TTL);
o = ToHostOption(ORBIS_NET_IPPROTO_IP, ORBIS_NET_IP_ADD_MEMBERSHIP);
ASSERT_TRUE(o);
EXPECT_EQ(o->name, IP_ADD_MEMBERSHIP);
EXPECT_EQ(o->value, OptionValue::Raw);
o = ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVBUF);
ASSERT_TRUE(o);
EXPECT_EQ(o->level, SOL_SOCKET);
EXPECT_EQ(o->name, SO_RCVBUF);
o = ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_LINGER);
ASSERT_TRUE(o);
EXPECT_EQ(o->value, OptionValue::Linger);
o = ToHostOption(ORBIS_NET_IPPROTO_TCP, ORBIS_NET_TCP_NODELAY);
ASSERT_TRUE(o);
EXPECT_EQ(o->name, TCP_NODELAY);
// Guest-owned options are not host options.
EXPECT_FALSE(ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO));
EXPECT_FALSE(ToHostOption(ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_RCVTIMEO));
EXPECT_FALSE(ToHostOption(0x1234, 1));
}
TEST(NetLibTranslate, EpollEventsAndErrors) {
const u32 all =
ORBIS_NET_EPOLLIN | ORBIS_NET_EPOLLOUT | ORBIS_NET_EPOLLERR | ORBIS_NET_EPOLLHUP;
EXPECT_EQ(ToOrbisEpollEvents(ToHostEpollEvents(all)), all);
EXPECT_EQ(ToHostEpollEvents(ORBIS_NET_EPOLLHUP), static_cast<u32>(Host::EvHup));
EXPECT_EQ(ToOrbisErrno(Host::Error::WouldBlock), ORBIS_NET_EWOULDBLOCK);
EXPECT_EQ(ToOrbisErrno(Host::Error::ConnRefused), ORBIS_NET_ECONNREFUSED);
EXPECT_EQ(ToOrbisErrno(Host::Error::NotSock), ORBIS_NET_EBADF);
EXPECT_EQ(ToOrbisErrno(Host::Error::Intr), ORBIS_NET_EINTR);
}
} // namespace
+263
View File
@@ -0,0 +1,263 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// The P2P wire format: exact bytes (pinning the reverse-engineered layout), communication-ID
// filtering, and payload protection (AES-128-CFB + truncated HMAC-SHA1).
#include <gtest/gtest.h>
#include "core/net/p2p_codec.h"
using namespace Core::Net;
using namespace Core::Net::P2P;
namespace {
const Endpoint kPeer = Endpoint::IPv4("192.0.2.10", 3658);
const Endpoint kOther = Endpoint::IPv4("192.0.2.11", 3658);
constexpr CommunicationId kComId{'N', 'P', 'W', 'R'};
std::vector<u8> Bytes(std::string_view s) {
return {s.begin(), s.end()};
}
P2PKey Key(u8 fill) {
P2PKey k;
k.value.fill(fill);
return k;
}
class NetP2PCodec : public ::testing::Test {
protected:
static void SetUpTestSuite() {
ASSERT_TRUE(Host::Initialize());
}
std::shared_ptr<Keyring> keys = std::make_shared<Keyring>();
FramingCodec codec{keys};
};
// ---------------------------------------------------------------------------------------------
// Exact bytes
// ---------------------------------------------------------------------------------------------
TEST_F(NetP2PCodec, DatagramHeader) {
// Vports are always two bytes, network byte order, even when small (0x40 is
// P2P_BROADCAST, not a short-vport flag).
EXPECT_EQ(codec.EncodeDatagram(3, 4, Bytes("ab"), kPeer, {}),
(std::vector<u8>{0xFF, 0x80 | 3, 0x00, 0x03, 0x00, 0x04, 'a', 'b'}));
EXPECT_EQ(codec.EncodeDatagram(0x0E4A, 4, Bytes("ab"), kPeer, {}),
(std::vector<u8>{0xFF, 0x80 | 3, 0x0E, 0x4A, 0x00, 0x04, 'a', 'b'}));
}
TEST_F(NetP2PCodec, DatagramWithDigest) {
keys->SetCommunicationId(kComId);
EXPECT_EQ(codec.EncodeDatagram(1, 2, Bytes("z"), kPeer, {}),
(std::vector<u8>{0xFF, 0x80 | 0x20 | 3, 0, 1, 0, 2, 'N', 'P', 'W', 'R', 'z'}));
}
TEST_F(NetP2PCodec, StreamHeader) {
const auto segment = Bytes("tcp-segment");
auto expected = std::vector<u8>{0xFF, 0x80 | 7};
expected.insert(expected.end(), segment.begin(), segment.end());
EXPECT_EQ(codec.EncodeStream(segment, kPeer, {}), expected);
keys->SetCommunicationId(kComId);
expected = {0xFF, 0x80 | 0x20 | 7, 'N', 'P', 'W', 'R'};
expected.insert(expected.end(), segment.begin(), segment.end());
EXPECT_EQ(codec.EncodeStream(segment, kPeer, {}), expected);
}
TEST_F(NetP2PCodec, SignalingHeader) {
EXPECT_EQ(codec.EncodeSignaling(Bytes("SHAD"), kPeer),
(std::vector<u8>{0xFF, 0x80 | 3, 0xFF, 0xFF, 0xFF, 0xFF, 'S', 'H', 'A', 'D'}));
}
// ---------------------------------------------------------------------------------------------
// Decoding
// ---------------------------------------------------------------------------------------------
TEST_F(NetP2PCodec, DecodeRoundTrips) {
for (const auto& [src, dst] : {std::pair<u16, u16>{3, 4}, {3658, 49152}, {255, 256}}) {
const auto d =
codec.Decode(codec.EncodeDatagram(src, dst, Bytes("payload"), kPeer, {}), kPeer);
EXPECT_EQ(d.kind, Codec::Kind::Datagram);
EXPECT_EQ(d.src_vport, src); // host byte order
EXPECT_EQ(d.dst_vport, dst);
EXPECT_EQ(d.payload, Bytes("payload"));
}
const auto s = codec.Decode(codec.EncodeStream(Bytes("seg"), kPeer, {}), kPeer);
EXPECT_EQ(s.kind, Codec::Kind::Stream);
EXPECT_EQ(s.payload, Bytes("seg"));
const auto g = codec.Decode(codec.EncodeSignaling(Bytes("hi"), kPeer), kPeer);
EXPECT_EQ(g.kind, Codec::Kind::Signaling);
EXPECT_EQ(g.payload, Bytes("hi"));
}
TEST_F(NetP2PCodec, DecodeRejectsMalformed) {
const auto invalid = [&](std::vector<u8> p) {
return codec.Decode(p, kPeer).kind == Codec::Kind::Invalid;
};
EXPECT_TRUE(invalid({}));
EXPECT_TRUE(invalid({0xFF}));
EXPECT_TRUE(invalid({0xFE, 0x80 | 3, 0, 1, 0, 2})); // bad marker
EXPECT_TRUE(invalid({0xFF, 3, 0, 1, 0, 2})); // P2P_VALID missing
EXPECT_TRUE(invalid({0xFF, 0x80 | 2, 0, 1, 0, 2})); // kind below P2P_UDP_SIMPLE
EXPECT_TRUE(invalid({0xFF, 0x80 | 11})); // kind above P2P_TCP_CRYPTO_SIGNTR
EXPECT_TRUE(invalid({0xFF, 0x80 | 0x40 | 7, 0, 1})); // P2P_BROADCAST on TCP
EXPECT_TRUE(invalid({0xFF, 0x80 | 3, 0, 1, 0})); // truncated header
EXPECT_TRUE(invalid({0xFF, 0x80 | 0x20 | 7, 'N', 'P'})); // truncated communication ID
}
TEST_F(NetP2PCodec, BroadcastPacketsAreDecodedAndReported) {
int reported = 0;
std::vector<u8> seen;
FramingCodec observed{keys, [&](std::span<const u8> packet, const Endpoint& from) {
++reported;
seen.assign(packet.begin(), packet.end());
EXPECT_EQ(from, kPeer);
}};
// [FF][valid|broadcast|digest|UDP_SIMPLE][port 0x0E4A][digest][payload]
const std::vector<u8> broadcast{
0xFF, 0x80 | 0x40 | 0x20 | 3, 0x0E, 0x4A, 'N', 'P', 'W', 'R', 'x', 'y'};
for (int i = 0; i < FramingCodec::MaxReportedBroadcasts + 5; ++i) {
const auto d = observed.Decode(broadcast, kPeer);
ASSERT_EQ(d.kind, Codec::Kind::Datagram);
EXPECT_EQ(d.dst_vport, 0x0E4A);
EXPECT_EQ(d.src_vport, 0x0E4A); // no source vport on the wire: the same port
EXPECT_EQ(d.payload, Bytes("xy"));
}
EXPECT_EQ(reported, FramingCodec::MaxReportedBroadcasts); // only the first few
EXPECT_EQ(seen, broadcast); // the whole packet
// Without a digest the payload follows the port.
const auto small = observed.Decode(std::vector<u8>{0xFF, 0x80 | 0x40 | 3, 0, 3, 'z'}, kPeer);
EXPECT_EQ(small.kind, Codec::Kind::Datagram);
EXPECT_EQ(small.dst_vport, 3);
EXPECT_EQ(small.payload, Bytes("z"));
// The digest filters broadcasts like everything else; no broadcast TCP; truncated.
keys->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'});
EXPECT_EQ(observed.Decode(broadcast, kPeer).kind, Codec::Kind::Invalid);
keys->SetCommunicationId(std::nullopt);
EXPECT_EQ(observed.Decode(std::vector<u8>{0xFF, 0x80 | 0x40 | 7, 0, 3}, kPeer).kind,
Codec::Kind::Invalid);
EXPECT_EQ(observed.Decode(std::vector<u8>{0xFF, 0x80 | 0x40 | 3, 0}, kPeer).kind,
Codec::Kind::Invalid);
// Ordinary packets are not reported.
const int before = reported;
observed.Decode(codec.EncodeDatagram(1, 2, Bytes("x"), kPeer, {}), kPeer);
EXPECT_EQ(reported, before);
}
TEST_F(NetP2PCodec, CommunicationIdFilter) {
FramingCodec sender{std::make_shared<Keyring>()};
const auto plain = sender.EncodeDatagram(1, 2, Bytes("x"), kPeer, {});
auto sender_keys = std::make_shared<Keyring>();
sender_keys->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'});
FramingCodec other_title{sender_keys};
const auto foreign = other_title.EncodeDatagram(1, 2, Bytes("x"), kPeer, {});
const auto signaling = other_title.EncodeSignaling(Bytes("s"), kPeer);
// No ID configured: everything is accepted.
EXPECT_EQ(codec.Decode(plain, kPeer).kind, Codec::Kind::Datagram);
EXPECT_EQ(codec.Decode(foreign, kPeer).kind, Codec::Kind::Datagram);
keys->SetCommunicationId(kComId);
EXPECT_EQ(codec.Decode(plain, kPeer).kind, Codec::Kind::Invalid); // missing ID
EXPECT_EQ(codec.Decode(foreign, kPeer).kind, Codec::Kind::Invalid); // other title
EXPECT_EQ(codec.Decode(codec.EncodeDatagram(1, 2, Bytes("x"), kPeer, {}), kPeer).kind,
Codec::Kind::Datagram);
EXPECT_EQ(codec.Decode(signaling, kPeer).kind, Codec::Kind::Signaling); // exempt
}
// ---------------------------------------------------------------------------------------------
// Protection
// ---------------------------------------------------------------------------------------------
TEST(NetP2PCrypto, LayoutAndRoundTrip) {
const auto data = Bytes("the quick brown fox");
for (const bool encrypt : {false, true}) {
for (const bool sign : {false, true}) {
std::vector<u8> protected_data, restored;
ASSERT_TRUE(ProtectPayload(data, encrypt, sign, Key(7), protected_data));
EXPECT_EQ(protected_data.size(), data.size() + (encrypt ? 4 : 0) + (sign ? 4 : 0));
if (encrypt) {
EXPECT_NE(std::vector<u8>(protected_data.end() - data.size(), protected_data.end()),
data);
}
ASSERT_TRUE(UnprotectPayload(protected_data, encrypt, sign, Key(7), restored));
EXPECT_EQ(restored, data);
}
}
}
TEST(NetP2PCrypto, SignatureDetectsTamperingAndWrongKey) {
std::vector<u8> protected_data, restored;
ASSERT_TRUE(ProtectPayload(Bytes("payload"), true, true, Key(7), protected_data));
EXPECT_FALSE(UnprotectPayload(protected_data, true, true, Key(8), restored));
protected_data.back() ^= 0x01;
EXPECT_FALSE(UnprotectPayload(protected_data, true, true, Key(7), restored));
EXPECT_FALSE(UnprotectPayload(std::vector<u8>(7, 0), true, true, Key(7), restored)); // short
}
TEST(NetP2PCrypto, RandomIvPerPacket) {
std::vector<u8> a, b;
ProtectPayload(Bytes("same"), true, false, Key(1), a);
ProtectPayload(Bytes("same"), true, false, Key(1), b);
EXPECT_NE(a, b);
}
TEST_F(NetP2PCodec, ProtectedTrafficNeedsMatchingKey) {
auto sender_keys = std::make_shared<Keyring>();
FramingCodec sender{sender_keys};
const Protection both{true, true};
// Sender without a key falls back to a plain packet (kind 3).
auto packet = sender.EncodeDatagram(1, 2, Bytes("secret"), kPeer, both);
EXPECT_EQ(packet[1] & 0x0F, 3);
sender_keys->SetPeerKey(kPeer, Key(9));
packet = sender.EncodeDatagram(1, 2, Bytes("secret"), kPeer, both);
EXPECT_EQ(packet[1] & 0x0F, 3 + 1 + 2);
const auto stream = sender.EncodeStream(Bytes("segment"), kPeer, both);
EXPECT_EQ(stream[1] & 0x0F, 7 + 1 + 2);
// The receiver looks up the key of whoever sent it (the sender is kPeer from its view).
EXPECT_EQ(codec.Decode(packet, kPeer).kind, Codec::Kind::Invalid); // no key: dropped
keys->SetPeerKey(kOther, Key(9));
EXPECT_EQ(codec.Decode(packet, kPeer).kind, Codec::Kind::Invalid); // key for someone else
keys->SetWildcardKey(Key(1));
EXPECT_EQ(codec.Decode(packet, kPeer).kind, Codec::Kind::Invalid); // wrong wildcard key
keys->SetPeerKey(kPeer, Key(9));
const auto d = codec.Decode(packet, kPeer);
EXPECT_EQ(d.kind, Codec::Kind::Datagram);
EXPECT_EQ(d.payload, Bytes("secret"));
EXPECT_EQ(codec.Decode(stream, kPeer).payload, Bytes("segment"));
}
TEST(NetP2PKeyring, LookupOrder) {
Keyring keys;
EXPECT_FALSE(keys.Find(kPeer));
keys.SetDefaultKey(Key(1));
EXPECT_EQ(keys.Find(kPeer), Key(1));
keys.SetWildcardKey(Key(2));
EXPECT_EQ(keys.Find(kPeer), Key(2));
keys.SetPeerKey(kPeer, Key(3));
EXPECT_EQ(keys.Find(kPeer), Key(3));
EXPECT_EQ(keys.Find(kOther), Key(2));
keys.RemovePeerKey(kPeer);
EXPECT_EQ(keys.Find(kPeer), Key(2));
}
TEST_F(NetP2PCodec, PseudoHeaderUsesInterfaceAddress) {
const auto p = codec.StreamPseudoHeader(Endpoint::IPv4("127.0.0.1", 9));
EXPECT_EQ(p.family, AF_INET);
EXPECT_EQ((std::array<u8, 4>{p.local[0], p.local[1], p.local[2], p.local[3]}),
(std::array<u8, 4>{127, 0, 0, 1}));
EXPECT_EQ((std::array<u8, 4>{p.remote[0], p.remote[1], p.remote[2], p.remote[3]}),
(std::array<u8, 4>{127, 0, 0, 1}));
}
} // namespace
+515
View File
@@ -0,0 +1,515 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <chrono>
#include <cstring>
#include <random>
#include <thread>
#include <gtest/gtest.h>
#include "core/net/guest_net.h"
#include "core/net/p2p_codec.h"
#include "core/net/p2p_transport.h"
using namespace Core::Net;
using namespace Core::Net::P2P;
using namespace std::chrono_literals;
using Host::Error;
namespace {
class TestCodec final : public Codec {
public:
Decoded Decode(std::span<const u8> packet, const Endpoint&) override {
Decoded d;
if (packet.size() < 5) {
return d;
}
d.kind = packet[0] == 1 ? Kind::Stream
: packet[0] == 2 ? Kind::Datagram
: packet[0] == 3 ? Kind::Signaling
: Kind::Invalid;
d.src_vport = static_cast<u16>((packet[1] << 8) | packet[2]);
d.dst_vport = static_cast<u16>((packet[3] << 8) | packet[4]);
d.payload.assign(packet.begin() + 5, packet.end());
return d;
}
std::vector<u8> EncodeStream(std::span<const u8> seg, const Endpoint&, Protection) override {
return Frame(1, 0, 0, seg);
}
std::vector<u8> EncodeDatagram(u16 src, u16 dst, std::span<const u8> payload, const Endpoint&,
Protection) override {
return Frame(2, src, dst, payload);
}
std::vector<u8> EncodeSignaling(std::span<const u8> data, const Endpoint&) override {
return Frame(3, 0, 0, data);
}
PseudoHeader StreamPseudoHeader(const Endpoint&) override {
PseudoHeader p;
p.local = p.remote = {127, 0, 0, 1};
return p;
}
private:
static std::vector<u8> Frame(u8 kind, u16 src, u16 dst, std::span<const u8> payload) {
std::vector<u8> v{kind, static_cast<u8>(src >> 8), static_cast<u8>(src),
static_cast<u8>(dst >> 8), static_cast<u8>(dst)};
v.insert(v.end(), payload.begin(), payload.end());
return v;
}
};
std::shared_ptr<Transport> MakeTransport(std::unique_ptr<Codec> codec) {
Error e;
auto t = Transport::Create(AF_INET, Endpoint::IPv4("127.0.0.1", 0), std::move(codec), {}, &e);
EXPECT_TRUE(t) << "transport creation failed: " << static_cast<int>(e);
return t;
}
std::vector<u8> RandomBytes(size_t n, u32 seed) {
std::mt19937 rng(seed);
std::vector<u8> v(n);
for (auto& b : v) {
b = static_cast<u8>(rng());
}
return v;
}
double Millis(Clock::time_point since) {
return std::chrono::duration<double, std::milli>(Clock::now() - since).count();
}
/// Drives a remote stream socket: send everything, read until `expect_in` bytes arrived.
bool RemotePump(StreamSocket& s, const std::vector<u8>& out, std::vector<u8>& in, size_t expect_in,
std::chrono::seconds limit) {
size_t off = 0;
const auto end = Clock::now() + limit;
u8 buf[8192];
while (Clock::now() < end) {
if (off < out.size()) {
off += s.Send(std::span(out).subspan(off)).bytes;
}
for (;;) {
const auto r = s.Recv(buf, false);
if (r.bytes == 0) {
break;
}
in.insert(in.end(), buf, buf + r.bytes);
}
if (off == out.size() && in.size() >= expect_in) {
return true;
}
std::this_thread::sleep_for(1ms);
}
return false;
}
class NetP2PGuest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
ASSERT_TRUE(Host::Initialize());
local_ = MakeTransport(std::make_unique<TestCodec>());
remote_ = MakeTransport(std::make_unique<TestCodec>());
SetP2PTransport(AF_INET, local_);
}
static void TearDownTestSuite() {
SetP2PTransport(AF_INET, nullptr);
local_.reset();
remote_.reset();
}
void TearDown() override {
local_->SetOutgoingLossForTesting(0);
remote_->SetOutgoingLossForTesting(0);
}
static Endpoint LocalAddress() {
return Endpoint::IPv4("127.0.0.1", local_->BoundPort());
}
static Endpoint RemoteAddress() {
return Endpoint::IPv4("127.0.0.1", remote_->BoundPort());
}
static s32 NewP2PSocket(bool stream) {
const auto r = P2PSocketCreate(AF_INET, stream);
EXPECT_EQ(r.error, Error::Ok);
return static_cast<s32>(r.value);
}
static inline std::shared_ptr<Transport> local_;
static inline std::shared_ptr<Transport> remote_;
};
TEST_F(NetP2PGuest, EpollInfiniteWaitWakesOnDatagram) {
const s32 sock = NewP2PSocket(false);
ASSERT_EQ(P2PSocketBind(sock, 3001).error, Error::Ok);
const s32 ep = static_cast<s32>(EpollCreate().value);
ASSERT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0xfeed).error, Error::Ok);
auto remote = remote_->CreateDatagram();
remote->Bind(4001);
std::thread sender([&] {
std::this_thread::sleep_for(200ms);
const u8 msg[] = "ping";
const auto to = LocalAddress();
remote->SendTo(msg, &to, 3001);
});
const auto start = Clock::now();
GuestEpollEvent events[4];
const auto r = EpollWait(ep, events, -1); // infinite timeout
const double waited = Millis(start);
sender.join();
ASSERT_EQ(r.value, 1);
EXPECT_EQ(events[0].data, 0xfeedu);
EXPECT_TRUE(events[0].events & Host::EvIn);
EXPECT_LT(waited, 2000.0);
u8 buf[16];
Endpoint from;
u16 from_vport = 0;
EXPECT_EQ(P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, &from, &from_vport).value, 5);
EXPECT_EQ(from_vport, 4001);
EXPECT_EQ(from, RemoteAddress());
SocketClose(sock);
EpollDestroy(ep);
}
TEST_F(NetP2PGuest, MixedNativeAndP2PEpoll) {
const s32 native = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ASSERT_EQ(SocketBind(native, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)).error,
Error::Ok);
socklen_t len = sizeof(addr);
SocketGetName(native, reinterpret_cast<sockaddr*>(&addr), &len);
const s32 p2p = NewP2PSocket(false);
P2PSocketBind(p2p, 3002);
const s32 ep = static_cast<s32>(EpollCreate().value);
EpollControl(ep, EpollOp::Add, native, Host::EvIn, 0, 1);
EpollControl(ep, EpollOp::Add, p2p, Host::EvIn, 0, 2);
GuestEpollEvent events[4];
auto remote = remote_->CreateDatagram();
remote->Bind(4002);
const u8 msg[] = "x";
const auto to = LocalAddress();
remote->SendTo(msg, &to, 3002);
auto r = EpollWait(ep, events, 2'000'000);
ASSERT_EQ(r.value, 1);
EXPECT_EQ(events[0].data, 2u);
EXPECT_EQ(EpollWait(ep, events, 0).value, 1); // level-triggered until read
u8 buf[8];
P2PSocketRecvFrom(p2p, buf, sizeof(buf), false, false, nullptr, nullptr);
EXPECT_EQ(EpollWait(ep, events, 50'000).value, 0);
Error e;
const auto helper = Host::CreateSocket(AF_INET, SOCK_DGRAM, 0, &e);
Host::SendTo(helper, "y", 1, 0, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
r = EpollWait(ep, events, 2'000'000);
ASSERT_EQ(r.value, 1);
EXPECT_EQ(events[0].data, 1u);
Host::CloseSocket(helper);
SocketClose(native);
SocketClose(p2p);
EpollDestroy(ep);
}
TEST_F(NetP2PGuest, SelectWorksOnP2PSockets) {
// select() needs no host socket: a P2P socket is waited on like a native one.
const s32 p2p = NewP2PSocket(false);
ASSERT_EQ(P2PSocketBind(p2p, 3009).error, Error::Ok);
const s32 native = static_cast<s32>(SocketCreate(AF_INET, SOCK_DGRAM, 0).value);
SelectEntry entries[] = {{p2p, Host::EvIn, 0}, {native, Host::EvIn, 0}};
EXPECT_EQ(SocketSelect(entries, 20'000).value, 0);
auto remote = remote_->CreateDatagram();
remote->Bind(4009);
std::thread sender([&] {
std::this_thread::sleep_for(100ms);
const u8 msg[] = "hi";
const auto to = LocalAddress();
remote->SendTo(msg, &to, 3009);
});
const auto r = SocketSelect(entries, 2'000'000);
sender.join();
ASSERT_EQ(r.error, Error::Ok);
EXPECT_EQ(r.value, 1);
EXPECT_EQ(entries[0].ready, Host::EvIn);
EXPECT_EQ(entries[1].ready, 0u);
// Selecting leaves nothing behind: an epoll on the same socket still works afterwards.
const s32 ep = static_cast<s32>(EpollCreate().value);
ASSERT_EQ(EpollControl(ep, EpollOp::Add, p2p, Host::EvIn, 0, 7).error, Error::Ok);
GuestEpollEvent events[2];
EXPECT_EQ(EpollWait(ep, events, 0).value, 1);
EpollDestroy(ep);
SocketClose(p2p);
SocketClose(native);
}
TEST_F(NetP2PGuest, EpollWritableForDatagram) {
const s32 sock = NewP2PSocket(false);
P2PSocketBind(sock, 3003);
const s32 ep = static_cast<s32>(EpollCreate().value);
EpollControl(ep, EpollOp::Add, sock, Host::EvIn | Host::EvOut, 0, 7);
GuestEpollEvent events[4];
ASSERT_EQ(EpollWait(ep, events, 0).value, 1); // reported once, not per handle
EXPECT_EQ(events[0].events, static_cast<u32>(Host::EvOut));
EpollControl(ep, EpollOp::Modify, sock, Host::EvIn, 0, 7);
EXPECT_EQ(EpollWait(ep, events, 0).value, 0);
EXPECT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, 0, 0).error, Error::Exist);
// One-shot/edge-triggered are not emulated for P2P sockets yet (DISCUSS in guest_net).
EpollControl(ep, EpollOp::Delete, sock, 0, 0, 0);
EXPECT_EQ(EpollControl(ep, EpollOp::Add, sock, Host::EvIn, Host::OneShot, 0).error,
Error::Inval);
SocketClose(sock);
EpollDestroy(ep);
}
TEST_F(NetP2PGuest, RecvTimeoutAndAbort) {
const s32 sock = NewP2PSocket(false);
P2PSocketBind(sock, 3004);
u8 buf[8];
SocketSetRecvTimeout(sock, 200ms);
auto start = Clock::now();
EXPECT_EQ(P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, nullptr, nullptr).error,
Error::WouldBlock);
EXPECT_GE(Millis(start), 190.0);
EXPECT_LT(Millis(start), 2000.0);
// Abort wakes a recv blocked without timeout (the old P2P abort only flushed queues).
SocketSetRecvTimeout(sock, 0us);
std::thread aborter([&] {
std::this_thread::sleep_for(150ms);
SocketAbort(sock, kSocketAbortPreserveRecv);
});
start = Clock::now();
EXPECT_EQ(P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, nullptr, nullptr).error,
Error::Intr);
EXPECT_LT(Millis(start), 2000.0);
aborter.join();
SocketClose(sock);
}
TEST_F(NetP2PGuest, EpollAbortWakesInfiniteWait) {
const s32 ep = static_cast<s32>(EpollCreate().value);
std::thread aborter([&] {
std::this_thread::sleep_for(100ms);
EpollAbort(ep, kEpollAbortPreserve);
});
GuestEpollEvent events[1];
EXPECT_EQ(EpollWait(ep, events, -1).error, Error::Intr);
aborter.join();
EpollDestroy(ep);
}
TEST_F(NetP2PGuest, StreamBlockingConnectAndRefused) {
auto listener = remote_->CreateStream();
ASSERT_EQ(listener->Bind(5000), Error::Ok);
ASSERT_EQ(listener->Listen(4), Error::Ok);
const s32 sock = NewP2PSocket(true);
ASSERT_EQ(P2PSocketConnect(sock, RemoteAddress(), 5000).error, Error::Ok); // blocks
Error e = Error::WouldBlock;
std::shared_ptr<StreamSocket> accepted;
for (int i = 0; i < 200 && !accepted; ++i) {
accepted = listener->Accept(nullptr, nullptr, &e);
std::this_thread::sleep_for(5ms);
}
ASSERT_TRUE(accepted);
const char msg[] = "hello from the guest";
EXPECT_EQ(P2PSocketSendTo(sock, msg, sizeof(msg), false, nullptr, 0).value,
static_cast<s64>(sizeof(msg)));
u8 buf[64]{};
size_t got = 0;
for (int i = 0; i < 200 && got < sizeof(msg); ++i) {
got += accepted->Recv(std::span(buf).subspan(got), false).bytes;
std::this_thread::sleep_for(5ms);
}
EXPECT_EQ(std::memcmp(buf, msg, sizeof(msg)), 0);
// Nothing listens on vport 5999: the remote transport answers with RST.
const s32 refused = NewP2PSocket(true);
EXPECT_EQ(P2PSocketConnect(refused, RemoteAddress(), 5999).error, Error::ConnRefused);
// Non-blocking connect reports progress, then completion via writability.
const s32 async = NewP2PSocket(true);
SocketSetNonBlocking(async, true);
EXPECT_EQ(P2PSocketConnect(async, RemoteAddress(), 5000).error, Error::InProgress);
const s32 ep = static_cast<s32>(EpollCreate().value);
EpollControl(ep, EpollOp::Add, async, Host::EvOut, 0, 0);
GuestEpollEvent events[1];
EXPECT_EQ(EpollWait(ep, events, 2'000'000).value, 1);
EXPECT_EQ(P2PSocketConnect(async, RemoteAddress(), 5000).error, Error::IsConn);
EpollDestroy(ep);
SocketClose(async);
SocketClose(sock);
SocketClose(refused);
listener->Close();
}
TEST_F(NetP2PGuest, CloseWakesBlockedAccept) {
const s32 listener = NewP2PSocket(true);
P2PSocketBind(listener, 3700);
SocketListen(listener, 1);
NetResult r{};
std::thread t([&] { r = P2PSocketAccept(listener, nullptr, nullptr); });
std::this_thread::sleep_for(100ms);
SocketClose(listener);
t.join();
EXPECT_EQ(r.error, Error::Intr);
}
TEST_F(NetP2PGuest, DatagramQueueBoundedAndUnboundDropped) {
const s32 sock = NewP2PSocket(false);
P2PSocketBind(sock, 3800);
EXPECT_EQ(P2PSocketBind(NewP2PSocket(false), 3800).error, Error::AddrInUse);
auto remote = remote_->CreateDatagram();
remote->Bind(4800);
const auto to = LocalAddress();
const std::vector<u8> big(1200, 0xab);
for (int i = 0; i < 400; ++i) { // 480 KB into a 256 KB budget
remote->SendTo(big, &to, 3800);
remote->SendTo(big, &to, 3801); // nobody bound here
if (i % 50 == 0) {
std::this_thread::sleep_for(1ms); // don't overflow the host socket buffer
}
}
std::this_thread::sleep_for(300ms);
size_t received = 0;
u8 buf[2048];
SocketSetNonBlocking(sock, true);
while (P2PSocketRecvFrom(sock, buf, sizeof(buf), false, false, nullptr, nullptr).value > 0) {
received += 1200;
}
EXPECT_GT(received, 0u);
EXPECT_LE(received, TransportConfig{}.datagram_queue_bytes);
SocketClose(sock);
}
class NetP2PGuestStream : public NetP2PGuest, public ::testing::WithParamInterface<double> {};
TEST_P(NetP2PGuestStream, BlockingAcceptSendRecv) {
const double loss = GetParam();
const size_t bytes = loss > 0 ? (1u << 20) : (4u << 20);
local_->SetOutgoingLossForTesting(loss);
remote_->SetOutgoingLossForTesting(loss);
const s32 listener = NewP2PSocket(true);
ASSERT_EQ(P2PSocketBind(listener, 3658).error, Error::Ok);
ASSERT_EQ(SocketListen(listener, 4).error, Error::Ok);
const auto guest_data = RandomBytes(bytes, 11);
const auto remote_data = RandomBytes(bytes, 12);
std::vector<u8> guest_got;
std::atomic<bool> guest_done{false};
// Guest side: blocking accept, blocking send of everything, blocking recv to EOF.
std::thread guest([&] {
const auto a = P2PSocketAccept(listener, nullptr, nullptr);
if (a.error != Error::Ok) {
return;
}
const s32 conn = static_cast<s32>(a.value);
std::thread writer([&] {
EXPECT_EQ(P2PSocketSendTo(conn, guest_data.data(), guest_data.size(), false, nullptr, 0)
.value,
static_cast<s64>(guest_data.size()));
SocketShutdown(conn, 1);
});
u8 buf[16384];
for (;;) {
const auto r =
P2PSocketRecvFrom(conn, buf, sizeof(buf), false, false, nullptr, nullptr);
if (r.error != Error::Ok || r.value == 0) {
break;
}
guest_got.insert(guest_got.end(), buf, buf + r.value);
}
writer.join();
SocketClose(conn);
guest_done = true;
});
auto remote = remote_->CreateStream();
EXPECT_EQ(remote->Connect(LocalAddress(), 3658), Error::InProgress);
std::vector<u8> remote_got;
const bool pumped = RemotePump(*remote, remote_data, remote_got, bytes, 120s);
remote->Shutdown(1);
if (!pumped) {
SocketAbort(listener, kSocketAbortPreserveRecv); // unblock the guest thread
}
guest.join();
remote->Close();
SocketClose(listener);
EXPECT_TRUE(pumped);
EXPECT_TRUE(guest_done);
EXPECT_TRUE(guest_got == remote_data) << "guest received " << guest_got.size();
EXPECT_TRUE(remote_got == guest_data) << "remote received " << remote_got.size();
}
INSTANTIATE_TEST_SUITE_P(Loss, NetP2PGuestStream, ::testing::Values(0.0, 0.05),
[](const auto& info) {
return info.param > 0 ? std::string("Loss5") : std::string("Clean");
});
// ---------------------------------------------------------------------------------------------
// The real wire format end to end
// ---------------------------------------------------------------------------------------------
TEST(NetP2PFraming, ProtectedStreamWithCommunicationId) {
ASSERT_TRUE(Host::Initialize());
auto keys_a = std::make_shared<Keyring>();
auto keys_b = std::make_shared<Keyring>();
const auto a = MakeTransport(std::make_unique<FramingCodec>(keys_a));
const auto b = MakeTransport(std::make_unique<FramingCodec>(keys_b));
const auto a_addr = Endpoint::IPv4("127.0.0.1", a->BoundPort());
const auto b_addr = Endpoint::IPv4("127.0.0.1", b->BoundPort());
P2PKey key;
key.value.fill(0x5a);
for (auto* keys : {keys_a.get(), keys_b.get()}) {
keys->SetCommunicationId(CommunicationId{'N', 'P', 'W', 'R'});
}
keys_a->SetPeerKey(b_addr, key);
keys_b->SetPeerKey(a_addr, key);
auto listener = b->CreateStream();
listener->SetProtection({true, true});
ASSERT_EQ(listener->Bind(3658), Error::Ok);
ASSERT_EQ(listener->Listen(1), Error::Ok);
auto client = a->CreateStream();
client->SetProtection({true, true});
ASSERT_EQ(client->Connect(b_addr, 3658), Error::InProgress);
std::shared_ptr<StreamSocket> server;
Error e;
for (int i = 0; i < 400 && !server; ++i) {
server = listener->Accept(nullptr, nullptr, &e);
std::this_thread::sleep_for(5ms);
}
ASSERT_TRUE(server);
const auto data = RandomBytes(256 * 1024, 5);
std::vector<u8> got;
std::vector<u8> nothing;
std::thread reader([&] { RemotePump(*server, {}, got, data.size(), 30s); });
EXPECT_TRUE(RemotePump(*client, data, nothing, 0, 30s));
reader.join();
EXPECT_TRUE(got == data) << "received " << got.size();
// A transport of another title (different communication ID) cannot even connect.
auto keys_c = std::make_shared<Keyring>();
keys_c->SetCommunicationId(CommunicationId{'O', 'T', 'H', 'R'});
const auto c = MakeTransport(std::make_unique<FramingCodec>(keys_c));
auto stranger = c->CreateStream();
stranger->Connect(b_addr, 3658);
std::this_thread::sleep_for(300ms);
EXPECT_EQ(stranger->ConnectResult(), Error::InProgress); // SYNs silently dropped
stranger->Abort();
}
} // namespace
+395
View File
@@ -0,0 +1,395 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// The STREAM_P2P TCP state machine: wire format, and two connections over a simulated link with
// loss, duplication, reordering and jitter on a virtual clock (so the tests are fast and
// deterministic).
#include <cstdlib>
#include <deque>
#include <random>
#include <string>
#include <queue>
#include <gtest/gtest.h>
#include "core/net/p2p_tcp.h"
using namespace Core::Net;
using namespace Core::Net::P2P;
using namespace std::chrono_literals;
namespace {
PseudoHeader Pseudo(u8 local, u8 remote) {
PseudoHeader p;
p.family = AF_INET;
p.local = {10, 0, 0, local};
p.remote = {10, 0, 0, remote};
return p;
}
std::vector<u8> RandomBytes(size_t n, u64 seed) {
std::mt19937_64 rng(seed);
std::vector<u8> v(n);
for (auto& b : v) {
b = static_cast<u8>(rng());
}
return v;
}
// ---------------------------------------------------------------------------------------------
// Wire format
// ---------------------------------------------------------------------------------------------
TEST(NetP2PTcpWire, SequenceArithmeticWraps) {
EXPECT_TRUE(SeqLt(0xFFFFFFF0u, 0x10u));
EXPECT_TRUE(SeqGt(0x10u, 0xFFFFFFF0u));
EXPECT_TRUE(SeqLe(5u, 5u));
EXPECT_FALSE(SeqLt(5u, 5u));
}
TEST(NetP2PTcpWire, BuildParseRoundTripWithChecksum) {
const u8 payload[] = {1, 2, 3, 4, 5};
const auto seg = BuildTcpSegment(Pseudo(1, 2), 1234, 80, 1000, 2000,
TcpFlag::Ack | TcpFlag::Psh, 4096, u16{1200}, payload);
const auto receiver = Pseudo(2, 1);
const auto parsed = ParseTcpSegment(seg, &receiver);
ASSERT_TRUE(parsed);
EXPECT_EQ(parsed->src_port, 1234);
EXPECT_EQ(parsed->dst_port, 80);
EXPECT_EQ(parsed->seq, 1000u);
EXPECT_EQ(parsed->ack, 2000u);
EXPECT_EQ(parsed->flags, TcpFlag::Ack | TcpFlag::Psh);
EXPECT_EQ(parsed->window, 4096);
EXPECT_EQ(parsed->mss, 1200);
EXPECT_EQ(std::vector<u8>(parsed->payload.begin(), parsed->payload.end()),
std::vector<u8>(std::begin(payload), std::end(payload)));
// A flipped bit, or the wrong pseudo-header, fails verification.
auto corrupt = seg;
corrupt.back() ^= 1;
EXPECT_FALSE(ParseTcpSegment(corrupt, &receiver));
const auto wrong = Pseudo(2, 9);
EXPECT_FALSE(ParseTcpSegment(seg, &wrong));
EXPECT_TRUE(ParseTcpSegment(corrupt)); // no pseudo-header: no verification
}
TEST(NetP2PTcpWire, KnownChecksum) {
// SYN 10.0.0.1:1234 -> 10.0.0.2:80, seq 1, window 1024, no options; checksum computed with
// an independent implementation (RFC 1071 one's complement sum, Python).
const auto seg =
BuildTcpSegment(Pseudo(1, 2), 1234, 80, 1, 0, TcpFlag::Syn, 1024, std::nullopt, {});
EXPECT_EQ((seg[16] << 8) | seg[17], 0x92BD);
}
TEST(NetP2PTcpWire, SkipsUnknownOptions) {
// SYN carrying MSS, SACK-permitted, timestamps, NOP and window scale, as Linux sends.
std::vector<u8> seg(40, 0);
seg[12] = 10 << 4;
seg[13] = TcpFlag::Syn;
const u8 options[] = {2, 4, 0x05, 0xB4, 4, 2, 8, 10, 0, 0, 0, 1, 0, 0, 0, 0, 1, 3, 3, 7};
std::copy(std::begin(options), std::end(options), seg.begin() + 20);
const auto parsed = ParseTcpSegment(seg);
ASSERT_TRUE(parsed);
EXPECT_EQ(parsed->mss, 1460);
EXPECT_TRUE(parsed->payload.empty());
}
TEST(NetP2PTcpWire, RejectsMalformed) {
std::vector<u8> seg(20, 0);
seg[12] = 4 << 4; // data offset below 20 bytes
EXPECT_FALSE(ParseTcpSegment(seg));
seg[12] = 15 << 4; // data offset beyond the segment
EXPECT_FALSE(ParseTcpSegment(seg));
EXPECT_FALSE(ParseTcpSegment(std::vector<u8>(19, 0)));
}
TEST(NetP2PTcpWire, ResetForUnknownPort) {
const auto p = Pseudo(1, 2);
const u8 data[10]{};
// With ACK: RST takes its sequence number from the ACK.
TcpSegment with_ack{5000, 80, 100, 777, TcpFlag::Ack, 100, std::nullopt, data};
auto rst = ParseTcpSegment(*BuildResetFor(p, with_ack));
EXPECT_EQ(rst->flags, TcpFlag::Rst);
EXPECT_EQ(rst->seq, 777u);
EXPECT_EQ(rst->src_port, 80);
EXPECT_EQ(rst->dst_port, 5000);
// Without ACK (a SYN): RST|ACK acknowledging what the segment occupied.
TcpSegment syn{5000, 80, 100, 0, TcpFlag::Syn, 100, std::nullopt, {}};
rst = ParseTcpSegment(*BuildResetFor(p, syn));
EXPECT_EQ(rst->flags, TcpFlag::Rst | TcpFlag::Ack);
EXPECT_EQ(rst->ack, 101u);
// Never answer a RST with a RST.
TcpSegment reset{5000, 80, 100, 0, TcpFlag::Rst, 0, std::nullopt, {}};
EXPECT_FALSE(BuildResetFor(p, reset));
}
// ---------------------------------------------------------------------------------------------
// Simulated link
// ---------------------------------------------------------------------------------------------
struct LinkParams {
double loss = 0.0;
double duplicate = 0.0;
std::chrono::milliseconds delay{20};
std::chrono::milliseconds jitter{0}; // jitter larger than packet spacing reorders
};
class Sim {
public:
Sim(u64 seed, LinkParams link) : rng_(seed), link_(link) {}
Clock::time_point now{};
void Transmit(int to, std::span<const u8> bytes) {
if (std::getenv("SHADPS4_NET_SIMLOG")) { // keep a trace, printed on failure
const auto seg = ParseTcpSegment(bytes);
char line[160];
std::snprintf(line, sizeof(line), "%8.3f %s flags=%02x seq=%u ack=%u len=%zu win=%u",
std::chrono::duration<double>(now.time_since_epoch()).count(),
to ? "A->B" : "B->A", seg->flags, seg->seq, seg->ack, seg->payload.size(),
seg->window);
log_.push_back(line);
if (log_.size() > 60) {
log_.pop_front();
}
}
std::uniform_real_distribution<double> u(0, 1);
const int copies = u(rng_) < link_.loss ? 0 : (u(rng_) < link_.duplicate ? 2 : 1);
for (int i = 0; i < copies; ++i) {
const auto jitter = link_.jitter.count()
? std::chrono::milliseconds(rng_() % link_.jitter.count())
: 0ms;
wire_.push({now + link_.delay + jitter, to, {bytes.begin(), bytes.end()}, order_++});
}
}
std::optional<Clock::time_point> NextDelivery() const {
return wire_.empty() ? std::nullopt : std::optional{wire_.top().deliver_at};
}
/// Pops the next packet due by `now`.
std::optional<std::pair<int, std::vector<u8>>> Pop() {
if (wire_.empty() || wire_.top().deliver_at > now) {
return std::nullopt;
}
auto p = wire_.top();
wire_.pop();
return std::pair{p.to, std::move(p.bytes)};
}
std::string Log() const {
std::string out;
for (const auto& line : log_) {
out += " " + line + "\n";
}
return out;
}
private:
struct Packet {
Clock::time_point deliver_at;
int to; // 0 = A, 1 = B
std::vector<u8> bytes;
u64 order;
bool operator>(const Packet& o) const {
return deliver_at != o.deliver_at ? deliver_at > o.deliver_at : order > o.order;
}
};
std::mt19937_64 rng_;
LinkParams link_;
std::priority_queue<Packet, std::vector<Packet>, std::greater<>> wire_;
u64 order_ = 0;
std::deque<std::string> log_;
};
struct TransferResult {
std::vector<u8> a_sent, b_sent, a_got, b_got;
TcpState a_state, b_state;
Error a_error, b_error;
bool b_created;
};
TransferResult RunTransfer(u64 seed, LinkParams link, size_t bytes, Clock::duration reader_stall,
std::string* log) {
Sim sim(seed, link);
TcpConfig config;
const auto a_pseudo = Pseudo(1, 2);
const auto b_pseudo = Pseudo(2, 1);
std::optional<TcpConnection> b;
// A's initial sequence number sits just below 2^32 so the transfer crosses the wrap.
TcpConnection a(config, a_pseudo, 5000, 6000, 0xfffff000u,
[&](std::span<const u8> s) { sim.Transmit(1, s); });
a.Connect(sim.now);
TransferResult r;
r.a_sent = RandomBytes(bytes, seed * 2 + 1);
r.b_sent = RandomBytes(bytes, seed * 2 + 2);
size_t a_off = 0, b_off = 0;
bool a_shut = false, b_shut = false;
const auto start = sim.now;
auto pump = [&](TcpConnection& c, const std::vector<u8>& out, size_t& off, std::vector<u8>& in,
bool& shut, bool may_read) {
while (off < out.size()) {
const auto w = c.Send(std::span(out).subspan(off), sim.now);
if (w.bytes == 0) {
break;
}
off += w.bytes;
}
if (off == out.size() && !shut &&
(c.State() == TcpState::Established || c.State() == TcpState::CloseWait)) {
c.Shutdown(sim.now);
shut = true;
}
if (may_read) {
u8 buf[4096];
for (;;) {
const auto got = c.Recv(buf, false, sim.now);
if (got.bytes == 0) {
break;
}
in.insert(in.end(), buf, buf + got.bytes);
}
}
};
const auto done = [](const TcpConnection& c) {
return c.State() == TcpState::Closed || c.State() == TcpState::TimeWait;
};
while (sim.now < start + 600s) {
pump(a, r.a_sent, a_off, r.a_got, a_shut, true);
if (b) {
pump(*b, r.b_sent, b_off, r.b_got, b_shut, sim.now - start >= reader_stall);
}
if (done(a) && b && done(*b) && r.a_got.size() == bytes && r.b_got.size() == bytes) {
break;
}
// Advance the virtual clock to the next packet or timer.
std::optional<Clock::time_point> next;
const auto consider = [&](std::optional<Clock::time_point> t) {
if (t && (!next || *t < *next)) {
next = t;
}
};
consider(sim.NextDelivery());
consider(a.NextDeadline());
if (b) {
consider(b->NextDeadline());
if (sim.now - start < reader_stall) {
consider(start + reader_stall);
}
}
if (!next) {
break;
}
sim.now = std::max(sim.now, *next);
while (auto packet = sim.Pop()) {
const auto [to, bytes_in] = std::move(*packet);
const auto& pseudo = to == 0 ? a_pseudo : b_pseudo;
const auto seg = ParseTcpSegment(bytes_in, &pseudo);
if (!seg) {
ADD_FAILURE() << "segment failed checksum verification";
continue;
}
TcpConnection* target = to == 0 ? &a : (b ? &*b : nullptr);
if (target && target->State() == TcpState::Closed) {
if (auto rst = BuildResetFor(pseudo, *seg)) {
sim.Transmit(to ^ 1, *rst);
}
continue;
}
if (target) {
target->OnSegment(*seg, sim.now);
} else if ((seg->flags & (TcpFlag::Syn | TcpFlag::Ack)) == TcpFlag::Syn) {
b.emplace(config, b_pseudo, 6000, 5000, 0x12345678u,
[&](std::span<const u8> s) { sim.Transmit(0, s); });
b->AcceptSyn(*seg, sim.now);
}
}
a.OnTimer(sim.now);
if (b) {
b->OnTimer(sim.now);
}
}
r.a_state = a.State();
r.a_error = a.PendingError();
r.b_created = b.has_value();
r.b_state = b ? b->State() : TcpState::Closed;
r.b_error = b ? b->PendingError() : Error::Ok;
if (log) {
*log = sim.Log();
}
return r;
}
struct Scenario {
const char* name;
LinkParams link;
Clock::duration reader_stall;
};
class NetP2PTcpSim : public ::testing::TestWithParam<Scenario> {};
TEST_P(NetP2PTcpSim, BidirectionalTransferAndClose) {
constexpr size_t kBytes = 300 * 1024;
for (u64 seed = 1; seed <= 20; ++seed) {
std::string log;
const auto r = RunTransfer(seed, GetParam().link, kBytes, GetParam().reader_stall, &log);
ASSERT_TRUE(r.b_created) << "seed " << seed;
ASSERT_TRUE(r.a_got == r.b_sent && r.b_got == r.a_sent)
<< "seed " << seed << ": a got " << r.a_got.size() << ", b got " << r.b_got.size()
<< " of " << kBytes << "; a " << ToString(r.a_state) << " b " << ToString(r.b_state)
<< "\n"
<< log << "(set SHADPS4_NET_SIMLOG=1 for a packet trace)";
EXPECT_EQ(r.a_error, Error::Ok) << "seed " << seed;
EXPECT_EQ(r.b_error, Error::Ok) << "seed " << seed;
}
}
INSTANTIATE_TEST_SUITE_P(
Links, NetP2PTcpSim,
::testing::Values(
Scenario{"Clean", {}, 0s}, Scenario{"Loss5", {.loss = 0.05}, 0s},
Scenario{"Loss20", {.loss = 0.20}, 0s},
Scenario{"ReorderDuplicate", {.duplicate = 0.05, .jitter = 30ms}, 0s},
Scenario{"Loss10ReorderDuplicate", {.loss = 0.10, .duplicate = 0.05, .jitter = 30ms}, 0s},
Scenario{"ZeroWindow", {.loss = 0.02}, 3s}),
[](const auto& info) { return std::string(info.param.name); });
TEST(NetP2PTcp, ConnectTimesOutOnDeadLink) {
Clock::time_point now{};
TcpConfig config;
int syns = 0;
TcpConnection a(config, Pseudo(1, 2), 5000, 6000, 1, [&](std::span<const u8>) { ++syns; });
a.Connect(now);
while (const auto next = a.NextDeadline()) {
now = *next;
a.OnTimer(now);
}
EXPECT_EQ(a.State(), TcpState::Closed);
EXPECT_EQ(a.PendingError(), Error::TimedOut);
EXPECT_EQ(syns, 1 + config.syn_retries);
EXPECT_GE(std::chrono::duration<double>(now.time_since_epoch()).count(), 60.0);
}
TEST(NetP2PTcp, AppIoBeforeAndAfterConnection) {
Clock::time_point now{};
TcpConnection a(TcpConfig{}, Pseudo(1, 2), 5000, 6000, 1, [](std::span<const u8>) {});
a.Connect(now);
const u8 byte = 1;
u8 buf[4];
EXPECT_EQ(a.Send({&byte, 1}, now).error, Error::WouldBlock); // still connecting
EXPECT_EQ(a.Recv(buf, false, now).error, Error::WouldBlock);
a.Abort();
EXPECT_EQ(a.State(), TcpState::Closed);
EXPECT_EQ(a.Send({&byte, 1}, now).error, Error::NotConn);
}
} // namespace
+475
View File
@@ -0,0 +1,475 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Linux only needs root or CAP_NET_ADMIN, otherwise every test is skipped.
#include <gtest/gtest.h>
#ifdef __linux__
#include <arpa/inet.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <atomic>
#include <cstring>
#include <functional>
#include <map>
#include <memory>
#include <random>
#include <thread>
#include "core/net/p2p_tcp.h"
using namespace Core::Net;
using namespace Core::Net::P2P;
using namespace std::chrono_literals;
namespace {
constexpr const char* KernelIp = "10.77.0.1"; // the TUN interface address (kernel TCP)
constexpr const char* OurIp = "10.77.0.2"; // the address our TCP answers for
std::vector<u8> RandomBytes(size_t n, u32 seed) {
std::mt19937 rng(seed);
std::vector<u8> v(n);
for (auto& b : v) {
b = static_cast<u8>(rng());
}
return v;
}
u16 IpChecksum(const u8* header, size_t size) {
u32 sum = 0;
for (size_t i = 0; i < size; i += 2) {
sum += (header[i] << 8) | header[i + 1];
}
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
return static_cast<u16>(~sum);
}
/// Kernel socket helpers with a generous timeout so a broken test fails instead of hanging.
int KernelSocket() {
const int s = socket(AF_INET, SOCK_STREAM, 0);
timeval tv{120, 0};
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
return s;
}
sockaddr_in Address(const char* ip, u16 port) {
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &addr.sin_addr);
return addr;
}
/// Our side of the network: owns TcpConnections keyed by (local port, remote port), accepts
/// SYNs on listening ports and resets everything else, like the P2P transport does.
class TunHost {
public:
explicit TunHost(int tun) : tun_(tun) {
inet_pton(AF_INET, OurIp, pseudo_.local.data());
inet_pton(AF_INET, KernelIp, pseudo_.remote.data());
}
double loss = 0.0;
std::map<std::pair<u16, u16>, std::unique_ptr<TcpConnection>> connections;
std::map<u16, bool> listeners;
TcpConnection& Connect(u16 local_port, u16 remote_port) {
auto& c = connections[{local_port, remote_port}];
c = std::make_unique<TcpConnection>(config_, pseudo_, local_port, remote_port, NextIss(),
[this](std::span<const u8> s) { EmitToKernel(s); });
c->Connect(Clock::now());
return *c;
}
TcpConnection* First() {
return connections.empty() ? nullptr : connections.begin()->second.get();
}
/// Runs the bridge until `step()` returns true or the time limit passes.
bool Run(const std::function<bool()>& step, std::chrono::seconds limit) {
const auto end = Clock::now() + limit;
u8 buf[65536];
while (Clock::now() < end) {
if (step()) {
return true;
}
auto next = Clock::now() + 5ms;
for (auto& [_, c] : connections) {
if (const auto d = c->NextDeadline(); d && *d < next) {
next = *d;
}
}
const auto wait = std::chrono::ceil<std::chrono::milliseconds>(
std::max<Clock::duration>(next - Clock::now(), 0ms));
pollfd pfd{tun_, POLLIN, 0};
poll(&pfd, 1, static_cast<int>(wait.count()));
for (;;) {
const auto n = read(tun_, buf, sizeof(buf));
if (n <= 0) {
break;
}
OnPacket({buf, static_cast<size_t>(n)});
}
const auto now = Clock::now();
for (auto& [_, c] : connections) {
if (const auto d = c->NextDeadline(); d && *d <= now) {
c->OnTimer(now);
}
}
}
return false;
}
int checksum_failures = 0;
private:
bool Drop() {
return loss > 0 && std::uniform_real_distribution<double>(0, 1)(rng_) < loss;
}
u32 NextIss() {
const u32 iss = next_iss_;
next_iss_ += 0x01000193;
return iss;
}
/// Wraps one of our TCP segments in IPv4 and hands it to the kernel.
void EmitToKernel(std::span<const u8> segment) {
if (Drop()) {
return;
}
std::vector<u8> packet(20 + segment.size());
packet[0] = 0x45;
packet[2] = static_cast<u8>(packet.size() >> 8);
packet[3] = static_cast<u8>(packet.size());
packet[6] = 0x40; // don't fragment
packet[8] = 64;
packet[9] = IPPROTO_TCP;
inet_pton(AF_INET, OurIp, packet.data() + 12);
inet_pton(AF_INET, KernelIp, packet.data() + 16);
const u16 checksum = IpChecksum(packet.data(), 20);
packet[10] = static_cast<u8>(checksum >> 8);
packet[11] = static_cast<u8>(checksum);
std::copy(segment.begin(), segment.end(), packet.begin() + 20);
[[maybe_unused]] const auto r = write(tun_, packet.data(), packet.size());
}
void OnPacket(std::span<const u8> packet) {
if (packet.size() < 20 || (packet[0] >> 4) != 4 || packet[9] != IPPROTO_TCP) {
return;
}
const size_t ihl = (packet[0] & 0x0f) * 4;
const size_t total = (packet[2] << 8) | packet[3];
if (total > packet.size() || ihl > total || Drop()) {
return;
}
// Verifying the kernel's checksum against our pseudo-header also proves ours agree.
const auto seg = ParseTcpSegment(packet.subspan(ihl, total - ihl), &pseudo_);
if (!seg) {
++checksum_failures;
return;
}
const auto key = std::pair{seg->dst_port, seg->src_port};
if (const auto it = connections.find(key);
it != connections.end() && it->second->State() != TcpState::Closed) {
it->second->OnSegment(*seg, Clock::now());
return;
}
if ((seg->flags & (TcpFlag::Syn | TcpFlag::Ack | TcpFlag::Rst)) == TcpFlag::Syn &&
listeners.contains(seg->dst_port)) {
auto& c = connections[key];
c = std::make_unique<TcpConnection>(config_, pseudo_, seg->dst_port, seg->src_port,
NextIss(),
[this](std::span<const u8> s) { EmitToKernel(s); });
c->AcceptSyn(*seg, Clock::now());
return;
}
if (const auto rst = BuildResetFor(pseudo_, *seg)) {
EmitToKernel(*rst);
}
}
int tun_;
TcpConfig config_;
PseudoHeader pseudo_;
std::mt19937 rng_{42};
u32 next_iss_ = 0xfffffc00u; // exercise sequence wraparound
};
class NetP2PTcpKernelInterop : public ::testing::Test {
protected:
void SetUp() override {
tun_ = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
if (tun_ < 0) {
GTEST_SKIP() << "no /dev/net/tun access (needs root or CAP_NET_ADMIN)";
}
ifreq ifr{};
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
std::strncpy(ifr.ifr_name, "shadps4tun0", IFNAMSIZ - 1);
if (ioctl(tun_, TUNSETIFF, &ifr) < 0) {
GTEST_SKIP() << "cannot create TUN device: " << std::strerror(errno);
}
const int s = socket(AF_INET, SOCK_DGRAM, 0);
auto* addr = reinterpret_cast<sockaddr_in*>(&ifr.ifr_addr);
addr->sin_family = AF_INET;
inet_pton(AF_INET, KernelIp, &addr->sin_addr);
const bool ok = ioctl(s, SIOCSIFADDR, &ifr) == 0 &&
(inet_pton(AF_INET, "255.255.255.0", &addr->sin_addr),
ioctl(s, SIOCSIFNETMASK, &ifr) == 0) &&
ioctl(s, SIOCGIFFLAGS, &ifr) == 0 &&
(ifr.ifr_flags |= IFF_UP | IFF_RUNNING, ioctl(s, SIOCSIFFLAGS, &ifr) == 0);
close(s);
if (!ok) {
GTEST_SKIP() << "cannot configure TUN device: " << std::strerror(errno);
}
std::this_thread::sleep_for(100ms);
host_ = std::make_unique<TunHost>(tun_);
}
void TearDown() override {
host_.reset();
if (tun_ >= 0) {
close(tun_); // the non-persistent interface disappears with its fd
}
}
static bool Finished(const TcpConnection* c) {
return c && (c->State() == TcpState::Closed || c->State() == TcpState::TimeWait);
}
/// The kernel connects to us; we echo; the kernel verifies and half-closes.
void KernelClientToOurEcho(double loss, size_t bytes) {
host_->loss = loss;
host_->listeners[7000] = true;
const auto data = RandomBytes(bytes, 1);
std::atomic<bool> kernel_done{false};
std::vector<u8> kernel_got;
std::string kernel_error;
std::thread kernel([&] {
const int s = KernelSocket();
const auto addr = Address(OurIp, 7000);
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) != 0) {
kernel_error = std::string("connect: ") + std::strerror(errno);
kernel_done = true;
return;
}
std::thread writer([&] {
for (size_t off = 0; off < data.size();) {
const auto n = send(s, data.data() + off, data.size() - off, 0);
if (n <= 0) {
break;
}
off += static_cast<size_t>(n);
}
shutdown(s, SHUT_WR);
});
u8 buf[16384];
for (;;) {
const auto n = recv(s, buf, sizeof(buf), 0);
if (n < 0) {
kernel_error = std::string("recv: ") + std::strerror(errno);
}
if (n <= 0) {
break;
}
kernel_got.insert(kernel_got.end(), buf, buf + n);
}
writer.join();
close(s);
kernel_done = true;
});
// Our echo: send back whatever arrives, close after the kernel's FIN.
std::vector<u8> pending;
bool shut = false;
host_->Run(
[&] {
if (auto* c = host_->First()) {
u8 buf[8192];
for (;;) {
const auto r = c->Recv(buf, false, Clock::now());
if (r.bytes == 0) {
break;
}
pending.insert(pending.end(), buf, buf + r.bytes);
}
while (!pending.empty()) {
const auto w = c->Send(pending, Clock::now());
if (w.bytes == 0) {
break;
}
pending.erase(pending.begin(), pending.begin() + w.bytes);
}
if (c->ReceivedFin() && pending.empty() && !shut) {
c->Shutdown(Clock::now());
shut = true;
}
}
return kernel_done.load();
},
300s);
kernel.join();
host_->Run([&] { return Finished(host_->First()); }, 20s); // final ACK exchange
const auto* c = host_->First();
EXPECT_TRUE(kernel_error.empty()) << kernel_error;
EXPECT_TRUE(kernel_got == data)
<< "kernel received " << kernel_got.size() << " of " << data.size();
ASSERT_TRUE(c);
EXPECT_EQ(c->PendingError(), Error::Ok);
EXPECT_TRUE(Finished(c)) << ToString(c->State());
EXPECT_EQ(host_->checksum_failures, 0);
}
/// We connect to a kernel echo server, send, verify the echo and close first.
void OurClientToKernelEcho(double loss, size_t bytes, int kernel_rcvbuf,
std::chrono::milliseconds kernel_stall) {
host_->loss = loss;
const int ls = socket(AF_INET, SOCK_STREAM, 0);
int one = 1;
setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
if (kernel_rcvbuf) {
setsockopt(ls, SOL_SOCKET, SO_RCVBUF, &kernel_rcvbuf, sizeof(kernel_rcvbuf));
}
const auto addr = Address(KernelIp, 7001);
ASSERT_EQ(bind(ls, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0)
<< std::strerror(errno);
listen(ls, 4);
std::atomic<bool> kernel_done{false};
std::thread kernel([&] {
const int s = accept(ls, nullptr, nullptr);
timeval tv{120, 0};
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
std::this_thread::sleep_for(kernel_stall); // let our sender hit a zero window
u8 buf[16384];
for (;;) {
const auto n = recv(s, buf, sizeof(buf), 0);
if (n <= 0) {
break;
}
for (ssize_t off = 0; off < n;) {
const auto w = send(s, buf + off, n - off, 0);
if (w <= 0) {
break;
}
off += w;
}
}
close(s);
kernel_done = true;
});
auto& c = host_->Connect(40001, 7001);
const auto data = RandomBytes(bytes, 2);
std::vector<u8> got;
size_t off = 0;
bool shut = false;
host_->Run(
[&] {
while (off < data.size()) {
const auto w = c.Send(std::span(data).subspan(off), Clock::now());
if (w.bytes == 0) {
break;
}
off += w.bytes;
}
if (off == data.size() && !shut && c.State() == TcpState::Established) {
c.Shutdown(Clock::now());
shut = true;
}
u8 buf[8192];
for (;;) {
const auto r = c.Recv(buf, false, Clock::now());
if (r.bytes == 0) {
break;
}
got.insert(got.end(), buf, buf + r.bytes);
}
return (c.ReceivedFin() && kernel_done && Finished(&c)) ||
c.PendingError() != Error::Ok;
},
300s);
if (!kernel_done) {
shutdown(ls, SHUT_RDWR);
c.Abort();
}
kernel.join();
close(ls);
EXPECT_TRUE(got == data) << "received " << got.size() << " of " << data.size();
EXPECT_EQ(c.PendingError(), Error::Ok);
EXPECT_EQ(host_->checksum_failures, 0);
}
int tun_ = -1;
std::unique_ptr<TunHost> host_;
};
TEST_F(NetP2PTcpKernelInterop, KernelClientToOurServer) {
KernelClientToOurEcho(0.0, 1 << 20);
}
TEST_F(NetP2PTcpKernelInterop, KernelClientToOurServerWithLoss) {
KernelClientToOurEcho(0.05, 256 << 10);
}
TEST_F(NetP2PTcpKernelInterop, OurClientToKernelServer) {
OurClientToKernelEcho(0.0, 1 << 20, 0, 0ms);
}
TEST_F(NetP2PTcpKernelInterop, OurClientToKernelServerWithLoss) {
OurClientToKernelEcho(0.05, 256 << 10, 0, 0ms);
}
TEST_F(NetP2PTcpKernelInterop, ZeroWindowProbing) {
// The kernel reads nothing for 3 s with a tiny receive buffer: our persist timer has to
// probe the closed window until it reopens.
OurClientToKernelEcho(0.0, 256 << 10, 4096, 3000ms);
}
TEST_F(NetP2PTcpKernelInterop, OurConnectToClosedPortIsRefused) {
auto& c = host_->Connect(40002, 7009); // nothing listens on 7009
host_->Run([&] { return c.State() == TcpState::Closed; }, 20s);
EXPECT_EQ(c.PendingError(), Error::ConnRefused);
}
TEST_F(NetP2PTcpKernelInterop, KernelConnectToOurClosedPortIsRefused) {
std::atomic<int> result{0};
std::atomic<bool> done{false};
std::thread kernel([&] {
const int s = KernelSocket();
const auto addr = Address(OurIp, 7010);
result =
connect(s, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == 0 ? 0 : errno;
close(s);
done = true;
});
host_->Run([&] { return done.load(); }, 20s);
kernel.join();
EXPECT_EQ(result, ECONNREFUSED); // we answered the SYN with a RST
}
} // namespace
#else
TEST(NetP2PTcpKernelInterop, Unsupported) {
GTEST_SKIP() << "the TUN interop tests run on Linux only";
}
#endif
+714
View File
@@ -0,0 +1,714 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <gtest/gtest.h>
#include "core/libraries/error_codes.h"
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_error.h"
using namespace Libraries::Net;
using namespace std::chrono_literals;
namespace {
// The homebrew's ports.
constexpr u16 TestPort = 8080;
constexpr u16 LogPort = 8181;
constexpr s32 MaxClients = 10;
OrbisNetSockaddr* Sa(OrbisNetSockaddrIn* in) {
return reinterpret_cast<OrbisNetSockaddr*>(in);
}
void Usleep(u32 micros) {
std::this_thread::sleep_for(std::chrono::microseconds{micros});
}
std::string Format(const char* fmt, u64 value) {
char buf[256];
std::snprintf(buf, sizeof(buf), fmt, static_cast<unsigned>(value));
return buf;
}
class Logger {
public:
virtual ~Logger() = default;
virtual void LogMessage(const char* fmt, u64 value) = 0;
std::vector<std::string> Sent() {
std::scoped_lock lock{mutex_};
return sent_;
}
std::vector<std::string> Received() {
std::scoped_lock lock{mutex_};
return received_;
}
std::vector<std::string> Errors() {
std::scoped_lock lock{mutex_};
return errors_;
}
/// Waits (up to 10 s) until every sent line has been received.
bool WaitForDelivery() {
for (int i = 0; i < 1000; ++i) {
{
std::scoped_lock lock{mutex_};
if (received_.size() >= sent_.size()) {
return true;
}
}
Usleep(10000);
}
return false;
}
protected:
void NoteSent(std::string line) {
std::scoped_lock lock{mutex_};
sent_.push_back(std::move(line));
}
void NoteReceived(std::string text) {
std::scoped_lock lock{mutex_};
received_.push_back(std::move(text));
}
void Error(const char* fmt, s32 value) {
std::scoped_lock lock{mutex_};
errors_.push_back(Format(fmt, static_cast<u32>(value)));
}
private:
std::mutex mutex_;
std::vector<std::string> sent_;
std::vector<std::string> received_;
std::vector<std::string> errors_;
};
/// Blocking stream sockets: a server thread accepting one connection per message.
class BIOStreamLogger final : public Logger {
public:
BIOStreamLogger() {
server_ = std::thread([this] { ServerThread(); });
while (!ready_) {
Usleep(10000);
}
}
~BIOStreamLogger() override {
{
std::scoped_lock lock{log_mutex_};
ready_ = false;
}
// Frees the server from its blocking accept; not expected to arrive.
Send("Closing BIOLoggerServer%lx\n", 0);
server_.join();
}
void LogMessage(const char* fmt, u64 value) override {
std::scoped_lock lock{log_mutex_};
NoteSent(Format(fmt, value));
std::thread client([&] { Send(fmt, value); });
client.join();
}
private:
void ServerThread() {
const s32 stream_sock = sceNetSocket("BIOStreamLoggerServerSocket", ORBIS_NET_AF_INET,
ORBIS_NET_SOCK_STREAM, 0);
if (stream_sock <= 0) {
return Error("socket creation failed with 0x%08x", stream_sock);
}
s32 opt = 1;
EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt,
sizeof(opt)),
ORBIS_OK);
EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt,
sizeof(opt)),
ORBIS_OK);
OrbisNetSockaddrIn in_addr{};
in_addr.sin_family = ORBIS_NET_AF_INET;
in_addr.sin_addr = ORBIS_NET_INADDR_ANY;
in_addr.sin_port = sceNetHtons(LogPort);
s32 result = sceNetBind(stream_sock, Sa(&in_addr), sizeof(in_addr));
if (result < 0) {
sceNetSocketClose(stream_sock);
return Error("bind failed with 0x%08x", result);
}
result = sceNetListen(stream_sock, 1);
if (result < 0) {
sceNetSocketClose(stream_sock);
return Error("listen failed with 0x%08x", result);
}
ready_ = true;
while (ready_) {
u32 addr_len = sizeof(in_addr);
const s32 log_sock = sceNetAccept(stream_sock, Sa(&in_addr), &addr_len);
if (!ready_) {
if (log_sock > 0) {
sceNetSocketClose(log_sock);
}
break;
}
if (log_sock <= 0) {
Error("Unexpected error 0x%08x while waiting for client", log_sock);
continue;
}
char log_buf[0x1000]{};
result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0);
if (result <= 0) {
Error("Unexpected error 0x%08x while reading from socket", result);
} else {
NoteReceived(log_buf);
}
if (sceNetSocketClose(log_sock) < 0) {
Error("Failed to close connection, error 0x%08x", result);
}
}
EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK);
}
void Send(const char* fmt, u64 value) {
const s32 stream_sock = sceNetSocket("BIOStreamLoggerClientSocket", ORBIS_NET_AF_INET,
ORBIS_NET_SOCK_STREAM, 0);
if (stream_sock <= 0) {
return Error("Failed to create client socket, error 0x%08x", stream_sock);
}
OrbisNetSockaddrIn in_addr{};
in_addr.sin_family = ORBIS_NET_AF_INET;
in_addr.sin_port = sceNetHtons(LogPort);
EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr), 1);
const s32 result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr));
if (!ready_) {
sceNetSocketClose(stream_sock);
return;
}
if (result < 0) {
sceNetSocketClose(stream_sock);
return Error("Unexpected error while trying to connect to server, error 0x%08x",
result);
}
const std::string text = Format(fmt, value);
if (sceNetSend(stream_sock, text.data(), text.size(), 0) <= 0) {
Error("Unexpected error while sending data to logger server, error 0x%08x",
*sceNetErrnoLoc());
}
EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK);
}
std::mutex log_mutex_;
std::atomic<bool> ready_{false};
std::thread server_;
};
/// Non-blocking stream sockets and epolls; with `async`, up to MaxClients messages in flight.
class NBIOStreamLogger final : public Logger {
public:
explicit NBIOStreamLogger(bool async) : async_(async) {
server_ = std::thread([this] { ServerThread(); });
while (!ready_) {
Usleep(10000);
}
}
~NBIOStreamLogger() override {
std::scoped_lock lock{log_mutex_};
for (auto& t : clients_) {
t.join();
}
clients_.clear();
ready_ = false;
server_.join();
}
void LogMessage(const char* fmt, u64 value) override {
std::scoped_lock lock{log_mutex_};
NoteSent(Format(fmt, value));
if (async_ && clients_.size() == static_cast<size_t>(MaxClients)) {
for (auto& t : clients_) {
t.join();
}
clients_.clear();
}
// The thread copies what it needs; nothing here outlives this call.
std::thread client([this, text = Format(fmt, value)] { Send(text); });
if (async_) {
clients_.push_back(std::move(client));
} else {
client.join();
}
}
private:
void ServerThread() {
const s32 stream_sock = sceNetSocket("NBIOStreamLoggerServerSocket", ORBIS_NET_AF_INET,
ORBIS_NET_SOCK_STREAM, 0);
if (stream_sock <= 0) {
return Error("socket creation failed with 0x%08x", stream_sock);
}
s32 opt = 1;
EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt,
sizeof(opt)),
ORBIS_OK);
EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt,
sizeof(opt)),
ORBIS_OK);
OrbisNetSockaddrIn in_addr{};
in_addr.sin_family = ORBIS_NET_AF_INET;
in_addr.sin_addr = ORBIS_NET_INADDR_ANY;
in_addr.sin_port = sceNetHtons(LogPort);
s32 result = sceNetBind(stream_sock, Sa(&in_addr), sizeof(in_addr));
if (result < 0) {
sceNetSocketClose(stream_sock);
return Error("bind failed with 0x%08x", result);
}
EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt,
sizeof(opt)),
ORBIS_OK);
result = sceNetListen(stream_sock, MaxClients);
if (result < 0) {
sceNetSocketClose(stream_sock);
return Error("listen failed with 0x%08x", result);
}
ready_ = true;
while (ready_) {
u32 addr_len = sizeof(in_addr);
const s32 log_sock = sceNetAccept(stream_sock, Sa(&in_addr), &addr_len);
if (!ready_) {
if (log_sock > 0) {
sceNetSocketClose(log_sock);
}
break;
}
if (log_sock == ORBIS_NET_ERROR_EAGAIN) {
Usleep(10000); // nobody waiting yet
continue;
}
if (log_sock <= 0) {
Error("Unexpected error 0x%08x while waiting for client", log_sock);
continue;
}
const s32 log_epoll = sceNetEpollCreate("NBIOStreamLoggerServerEpoll", 0);
if (log_epoll <= 0) {
Error("Unexpected error 0x%08x while creating epoll", log_epoll);
sceNetSocketClose(log_sock);
continue;
}
OrbisNetEpollEvent epoll_event{};
epoll_event.events = ORBIS_NET_EPOLLIN;
epoll_event.data.fd = log_sock;
result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, log_sock, &epoll_event);
if (result < 0) {
Error("Unexpected error 0x%08x while preparing epoll", result);
sceNetEpollDestroy(log_epoll);
sceNetSocketClose(log_sock);
continue;
}
OrbisNetEpollEvent epoll_ev_out{};
result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1);
if (result <= 0) {
Error("Unexpected result 0x%08x while waiting for socket", result);
sceNetEpollDestroy(log_epoll);
sceNetSocketClose(log_sock);
continue;
}
EXPECT_EQ(epoll_ev_out.data.fd, log_sock);
std::string text;
char log_buf[0x1000];
for (;;) {
std::memset(log_buf, 0, sizeof(log_buf));
result = sceNetRecv(log_sock, log_buf, sizeof(log_buf), 0);
if (result == ORBIS_NET_ERROR_EWOULDBLOCK || result == 0) {
break;
}
if (result < 0) {
Error("Unexpected error 0x%08x while reading from socket", result);
break;
}
text.append(log_buf, static_cast<size_t>(result));
}
if (!text.empty()) {
NoteReceived(std::move(text));
}
if (sceNetEpollDestroy(log_epoll) < 0) {
Error("Failed to destroy epoll, error 0x%08x", *sceNetErrnoLoc());
}
if (sceNetSocketClose(log_sock) < 0) {
Error("Failed to close connection, error 0x%08x", *sceNetErrnoLoc());
}
}
EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK);
}
void Send(const std::string& text) {
const s32 stream_sock = sceNetSocket("NBIOStreamLoggerClientSocket", ORBIS_NET_AF_INET,
ORBIS_NET_SOCK_STREAM, 0);
if (stream_sock <= 0) {
return Error("Failed to create client socket, error 0x%08x", stream_sock);
}
OrbisNetSockaddrIn in_addr{};
in_addr.sin_family = ORBIS_NET_AF_INET;
in_addr.sin_port = sceNetHtons(LogPort);
EXPECT_EQ(sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr), 1);
s32 opt = 1;
EXPECT_EQ(sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_NBIO, &opt,
sizeof(opt)),
ORBIS_OK);
// A non-blocking connect: EINPROGRESS, then retried until EISCONN. The homebrew treats
// anything else as an error (no EALREADY on the PS4 over loopback).
s32 result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr));
while (result != 0) {
if (!ready_) {
sceNetSocketClose(stream_sock);
return;
}
if (result == ORBIS_NET_ERROR_EINPROGRESS) {
Usleep(100000);
result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr));
continue;
}
if (result == ORBIS_NET_ERROR_EISCONN) {
break;
}
Error("Unexpected error while trying to connect to server, error 0x%08x", result);
sceNetSocketClose(stream_sock);
return;
}
const s32 log_epoll = sceNetEpollCreate("NBIOStreamLoggerClientEpoll", 0);
if (log_epoll <= 0) {
Error("Unexpected error while creating epoll, error 0x%08x", log_epoll);
sceNetSocketClose(stream_sock);
return;
}
OrbisNetEpollEvent epoll_event{};
epoll_event.events = ORBIS_NET_EPOLLOUT;
epoll_event.data.fd = stream_sock;
result = sceNetEpollControl(log_epoll, ORBIS_NET_EPOLL_CTL_ADD, stream_sock, &epoll_event);
if (result < 0) {
Error("Unexpected error while preparing epoll, error 0x%08x", result);
sceNetEpollDestroy(log_epoll);
sceNetSocketClose(stream_sock);
return;
}
OrbisNetEpollEvent epoll_ev_out{};
result = sceNetEpollWait(log_epoll, &epoll_ev_out, 1, -1);
if (result <= 0) {
Error("Unexpected result while waiting for socket, 0x%08x", result);
sceNetEpollDestroy(log_epoll);
sceNetSocketClose(stream_sock);
return;
}
EXPECT_TRUE(epoll_ev_out.events & ORBIS_NET_EPOLLOUT);
size_t sent = 0;
while (sent < text.size()) {
result = sceNetSend(stream_sock, text.data() + sent, text.size() - sent, 0);
if (result < 0) {
Error("Unexpected error while sending data to logger server, error 0x%08x", result);
break;
}
sent += static_cast<size_t>(result);
}
EXPECT_EQ(sceNetEpollDestroy(log_epoll), ORBIS_OK);
EXPECT_EQ(sceNetSocketClose(stream_sock), ORBIS_OK);
}
const bool async_;
std::mutex log_mutex_;
std::atomic<bool> ready_{false};
std::thread server_;
std::vector<std::thread> clients_;
};
std::atomic<bool> g_server_ready{false};
std::atomic<bool> g_cond{false};
Logger* g_active_logger = nullptr;
void LogMessage(const char* msg, u64 value) {
if (g_active_logger != nullptr) {
g_active_logger->LogMessage(msg, value);
}
}
constexpr const char* ServerMessage = "This is a test message coming from the server";
constexpr const char* ClientMessage = "This is a test message coming from the client";
constexpr u32 MessageLength = 0x2d; // both messages, as the PS4 log shows
void ServerThread() {
const s32 stream_sock =
sceNetSocket("TestStreamServer", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
LogMessage("Server: socket(AF_INET, SOCK_STREAM, 0) returns 0x%08x\n", stream_sock);
ASSERT_GT(stream_sock, 0);
s32 opt = 1;
s32 result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEADDR, &opt,
sizeof(opt));
LogMessage("Server: setsockopt(SOL_SOCKET, SO_REUSEADDR) returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
result = sceNetSetsockopt(stream_sock, ORBIS_NET_SOL_SOCKET, ORBIS_NET_SO_REUSEPORT, &opt,
sizeof(opt));
LogMessage("Server: setsockopt(SOL_SOCKET, SO_REUSEPORT) returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
OrbisNetSockaddrIn in_addr{};
in_addr.sin_family = ORBIS_NET_AF_INET;
in_addr.sin_addr = ORBIS_NET_INADDR_ANY;
in_addr.sin_port = sceNetHtons(TestPort);
result = sceNetBind(stream_sock, Sa(&in_addr), sizeof(in_addr));
LogMessage("Server: bind returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
result = sceNetListen(stream_sock, 3);
LogMessage("Server: listen returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
g_server_ready = true;
u32 addr_len = sizeof(in_addr);
const s32 connected_sock = sceNetAccept(stream_sock, Sa(&in_addr), &addr_len);
LogMessage("Server: accept returns 0x%08x\n", connected_sock);
ASSERT_GT(connected_sock, 0);
result = sceNetSend(connected_sock, ServerMessage, std::strlen(ServerMessage), 0);
LogMessage("Server: send returns 0x%08x\n", result);
EXPECT_EQ(static_cast<u32>(result), MessageLength);
char buffer[1024]{};
result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0);
LogMessage("Server: recv returns 0x%08x\n", result);
EXPECT_EQ(static_cast<u32>(result), MessageLength);
EXPECT_STREQ(buffer, ClientMessage);
const s32 epoll = sceNetEpollCreate("ServerEpoll", 0);
LogMessage("Server: epoll_create returns 0x%08x\n", epoll);
EXPECT_GT(epoll, 0);
OrbisNetEpollEvent epoll_event{};
epoll_event.events = ORBIS_NET_EPOLLIN;
epoll_event.data.fd = connected_sock;
result = sceNetEpollControl(epoll, ORBIS_NET_EPOLL_CTL_ADD, connected_sock, &epoll_event);
LogMessage("Server: epoll_ctl returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
// Nothing sent yet: a zero timeout finds nothing.
OrbisNetEpollEvent epoll_ev_out{};
result = sceNetEpollWait(epoll, &epoll_ev_out, 1, 0);
LogMessage("Server: epoll_wait returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
g_cond = true;
// No timeout: returns once the client's second message is there.
result = sceNetEpollWait(epoll, &epoll_ev_out, 1, -1);
LogMessage("Server: epoll_wait returns 0x%08x\n", result);
EXPECT_EQ(result, 1);
EXPECT_EQ(epoll_ev_out.data.fd, connected_sock);
EXPECT_EQ(epoll_ev_out.ident, static_cast<u64>(connected_sock));
EXPECT_TRUE(epoll_ev_out.events & ORBIS_NET_EPOLLIN);
std::memset(buffer, 0, sizeof(buffer));
result = sceNetRecv(connected_sock, buffer, sizeof(buffer), 0);
LogMessage("Server: recv returns 0x%08x\n", result);
EXPECT_EQ(static_cast<u32>(result), MessageLength);
EXPECT_STREQ(buffer, ClientMessage);
result = sceNetEpollDestroy(epoll);
LogMessage("Server: epoll_destroy returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
result = sceNetSocketClose(connected_sock);
LogMessage("Server: socket_close returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
result = sceNetSocketClose(stream_sock);
LogMessage("Server: socket_close returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
g_server_ready = false;
g_cond = false;
}
void ClientThread() {
while (!g_server_ready) {
Usleep(10000);
}
const s32 stream_sock =
sceNetSocket("TestStreamSocket", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
LogMessage("Client: socket(AF_INET, SOCK_STREAM, 0) returns 0x%08x\n", stream_sock);
ASSERT_GT(stream_sock, 0);
OrbisNetSockaddrIn in_addr{};
in_addr.sin_family = ORBIS_NET_AF_INET;
in_addr.sin_port = sceNetHtons(TestPort);
s32 result = sceNetInetPton(ORBIS_NET_AF_INET, "127.0.0.1", &in_addr.sin_addr);
LogMessage("Client: inet_pton(ORBIS_NET_AF_INET) returns 0x%08x\n", result);
EXPECT_EQ(result, 1);
result = sceNetConnect(stream_sock, Sa(&in_addr), sizeof(in_addr));
LogMessage("Client: connect returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
char buffer[1024]{};
result = sceNetRecv(stream_sock, buffer, sizeof(buffer), 0);
LogMessage("Client: recv returns 0x%08x\n", result);
EXPECT_EQ(static_cast<u32>(result), MessageLength);
EXPECT_STREQ(buffer, ServerMessage);
result = sceNetSend(stream_sock, ClientMessage, std::strlen(ClientMessage), 0);
LogMessage("Client: send returns 0x%08x\n", result);
EXPECT_EQ(static_cast<u32>(result), MessageLength);
while (!g_cond) {
Usleep(10000);
}
g_cond = false;
result = sceNetSend(stream_sock, ClientMessage, std::strlen(ClientMessage), 0);
LogMessage("Client: send returns 0x%08x\n", result);
EXPECT_EQ(static_cast<u32>(result), MessageLength);
result = sceNetSocketClose(stream_sock);
LogMessage("Client: socket_close returns 0x%08x\n", result);
EXPECT_EQ(result, 0);
}
void RunRound() {
std::thread server(ServerThread);
std::thread client(ClientThread);
server.join();
client.join();
}
constexpr const char* Ps4Round[] = {
"Server: socket(AF_INET, SOCK_STREAM, 0) returns <id>\n",
"Server: setsockopt(SOL_SOCKET, SO_REUSEADDR) returns 0x00000000\n",
"Server: setsockopt(SOL_SOCKET, SO_REUSEPORT) returns 0x00000000\n",
"Server: bind returns 0x00000000\n",
"Server: listen returns 0x00000000\n",
"Client: socket(AF_INET, SOCK_STREAM, 0) returns <id>\n",
"Client: inet_pton(ORBIS_NET_AF_INET) returns 0x00000001\n",
"Client: connect returns 0x00000000\n",
"Server: accept returns <id>\n",
"Server: send returns 0x0000002d\n",
"Client: recv returns 0x0000002d\n",
"Client: send returns 0x0000002d\n",
"Server: recv returns 0x0000002d\n",
"Server: epoll_create returns <id>\n",
"Server: epoll_ctl returns 0x00000000\n",
"Server: epoll_wait returns 0x00000000\n",
"Client: send returns 0x0000002d\n",
"Client: socket_close returns 0x00000000\n",
"Server: epoll_wait returns 0x00000001\n",
"Server: recv returns 0x0000002d\n",
"Server: epoll_destroy returns 0x00000000\n",
"Server: socket_close returns 0x00000000\n",
"Server: socket_close returns 0x00000000\n",
};
std::string MaskId(const std::string& line) {
static constexpr const char* IdLines[] = {"socket(AF_INET", "accept returns",
"epoll_create returns"};
const bool is_id = std::any_of(std::begin(IdLines), std::end(IdLines), [&](const char* k) {
return line.find(k) != std::string::npos;
});
const size_t at = line.rfind("0x");
if (!is_id || at == std::string::npos) {
return line;
}
const auto value = static_cast<s32>(std::stoul(line.substr(at + 2), nullptr, 16));
EXPECT_GT(value, 0) << line;
return line.substr(0, at) + "<id>\n";
}
/// The lines of one thread ("Server:" or "Client:"), in order.
std::vector<std::string> Thread(const std::vector<std::string>& lines, const char* prefix) {
std::vector<std::string> out;
for (const auto& line : lines) {
if (line.starts_with(prefix)) {
out.push_back(line);
}
}
return out;
}
void CheckLogger(Logger& logger, const char* name, bool ordered) {
SCOPED_TRACE(name);
ASSERT_TRUE(logger.WaitForDelivery()) << "not every message reached the logger server";
EXPECT_TRUE(logger.Errors().empty()) << "logger error: " << logger.Errors().front();
std::vector<std::string> sent, received;
for (const auto& line : logger.Sent()) {
sent.push_back(MaskId(line));
}
for (const auto& text : logger.Received()) {
received.push_back(MaskId(text));
}
const std::vector<std::string> ps4(std::begin(Ps4Round), std::end(Ps4Round));
for (const char* prefix : {"Server:", "Client:"}) {
EXPECT_EQ(Thread(sent, prefix), Thread(ps4, prefix)) << prefix;
if (ordered) {
EXPECT_EQ(Thread(received, prefix), Thread(ps4, prefix)) << prefix;
}
}
std::sort(received.begin(), received.end());
auto expected = ps4;
std::sort(expected.begin(), expected.end());
EXPECT_EQ(received, expected);
}
class NetSampleSockets : public ::testing::Test {
protected:
static void SetUpTestSuite() {
ASSERT_EQ(sceNetInit(), ORBIS_OK);
}
static void TearDownTestSuite() {
EXPECT_EQ(sceNetTerm(), ORBIS_OK);
}
};
} // namespace
TEST_F(NetSampleSockets, IntegrationTestsPr14) {
// First round: no logging; basic blocking stream socket behaviour on its own.
RunRound();
// Second round: a logger built on blocking stream sockets.
{
BIOStreamLogger logger;
g_active_logger = &logger;
RunRound();
g_active_logger = nullptr;
CheckLogger(logger, "BIOStreamLogger", true);
}
// Third round: non-blocking stream sockets, one message at a time.
{
NBIOStreamLogger logger(false);
g_active_logger = &logger;
RunRound();
g_active_logger = nullptr;
CheckLogger(logger, "NBIOStreamLogger (sync)", true);
}
// Fourth round: non-blocking stream sockets, several messages in flight.
{
NBIOStreamLogger logger(true);
g_active_logger = &logger;
RunRound();
g_active_logger = nullptr;
CheckLogger(logger, "NBIOStreamLogger (async)", false);
}
}
+61
View File
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// What the net kernel glue (src/core/libraries/net/net_kernel.cpp) needs from the kernel,
// without the kernel: its errno and its file table. The table follows fs.cpp (lowest free
// slot first, entries deleted on release); the rest of fs.cpp needs the whole file system.
#include <mutex>
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/kernel.h"
namespace Libraries::Kernel {
s32* PS4_SYSV_ABI __Error() {
thread_local s32 error = 0;
return &error;
}
} // namespace Libraries::Kernel
namespace Core::FileSys {
int HandleTable::CreateHandle() {
std::scoped_lock lock{m_mutex};
auto* file = new File{};
for (size_t index = 0; index < m_files.size(); ++index) {
if (m_files[index] == nullptr) {
m_files[index] = file;
return static_cast<int>(index);
}
}
m_files.push_back(file);
return static_cast<int>(m_files.size() - 1);
}
void HandleTable::DeleteHandle(int d) {
std::scoped_lock lock{m_mutex};
delete m_files.at(d);
m_files[d] = nullptr;
}
File* HandleTable::GetFile(int d) {
std::scoped_lock lock{m_mutex};
if (d < 0 || static_cast<size_t>(d) >= m_files.size()) {
return nullptr;
}
return m_files[d];
}
void HandleTable::CreateStdHandles() {
// Descriptors 0-2, so net objects start at 3 as in the emulator.
for (int i = 0; i < 3; ++i) {
const int fd = CreateHandle();
auto* file = GetFile(fd);
file->type = FileType::Device;
file->is_opened = true;
}
}
} // namespace Core::FileSys
+50
View File
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// The kernel calls libSceNet forwards to (threads, sleep, process time), on host threads.
#include <chrono>
#include <cstdlib>
#include <thread>
#include "core/libraries/kernel/threads.h"
#include "core/libraries/kernel/threads/pthread.h"
#include "core/libraries/kernel/time.h"
namespace Libraries::Kernel {
u64 PS4_SYSV_ABI sceKernelGetProcessTime() {
static const auto start = std::chrono::steady_clock::now();
return static_cast<u64>(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count());
}
s32 PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) {
std::this_thread::sleep_for(std::chrono::microseconds{microseconds});
return 0;
}
int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAttrT*,
PthreadEntryFunc start_routine, void* arg,
const char*) {
auto* t = new std::thread([start_routine, arg] { start_routine(arg); });
*thread = reinterpret_cast<PthreadT>(t);
return 0;
}
int PS4_SYSV_ABI posix_pthread_join(PthreadT pthread, void** thread_return) {
auto* t = reinterpret_cast<std::thread*>(pthread);
t->join();
delete t;
if (thread_return != nullptr) {
*thread_return = nullptr;
}
return 0;
}
void PS4_SYSV_ABI posix_pthread_exit(void*) {
std::abort(); // no test exits a thread this way
}
} // namespace Libraries::Kernel