Move rpcs3/Emu/Cell/lv2 to kernel/cellos

This commit is contained in:
DH 2025-10-04 16:46:36 +03:00
parent fce4127c2e
commit dbfa5002e5
282 changed files with 40062 additions and 41342 deletions

View file

@ -0,0 +1,157 @@
#include "stdafx.h"
#include "sys_net/lv2_socket.h"
#include "sys_net/network_context.h"
LOG_CHANNEL(sys_net);
lv2_socket::lv2_socket(lv2_socket_family family, lv2_socket_type type,
lv2_ip_protocol protocol) {
this->family = family;
this->type = type;
this->protocol = protocol;
}
std::unique_lock<shared_mutex> lv2_socket::lock() {
return std::unique_lock(mutex);
}
lv2_socket_family lv2_socket::get_family() const { return family; }
lv2_socket_type lv2_socket::get_type() const { return type; }
lv2_ip_protocol lv2_socket::get_protocol() const { return protocol; }
std::size_t lv2_socket::get_queue_size() const { return queue.size(); }
socket_type lv2_socket::get_socket() const { return native_socket; }
#ifdef _WIN32
bool lv2_socket::is_connecting() const { return connecting; }
void lv2_socket::set_connecting(bool connecting) {
this->connecting = connecting;
}
#endif
void lv2_socket::set_lv2_id(u32 id) { lv2_id = id; }
bs_t<lv2_socket::poll_t> lv2_socket::get_events() const {
return events.load();
}
void lv2_socket::set_poll_event(bs_t<lv2_socket::poll_t> event) {
events += event;
}
void lv2_socket::poll_queue(
shared_ptr<ppu_thread> ppu, bs_t<lv2_socket::poll_t> event,
std::function<bool(bs_t<lv2_socket::poll_t>)> poll_cb) {
set_poll_event(event);
queue.emplace_back(std::move(ppu), poll_cb);
// Makes sure network_context thread is awaken
if (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM) {
auto &nc = g_fxo->get<network_context>();
const u32 prev_value = nc.num_polls.fetch_add(1);
if (!prev_value) {
nc.num_polls.notify_one();
}
}
}
u32 lv2_socket::clear_queue(ppu_thread *ppu) {
std::lock_guard lock(mutex);
u32 cleared = 0;
for (auto it = queue.begin(); it != queue.end();) {
if (it->first.get() == ppu) {
it = queue.erase(it);
cleared++;
continue;
}
it++;
}
if (queue.empty()) {
events.store({});
}
if (cleared && (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) {
// Makes sure network_context thread can go back to sleep if there is no
// active polling
const u32 prev_value =
g_fxo->get<network_context>().num_polls.fetch_sub(cleared);
ensure(prev_value >= cleared);
}
return cleared;
}
void lv2_socket::handle_events(const pollfd &native_pfd,
[[maybe_unused]] bool unset_connecting) {
bs_t<lv2_socket::poll_t> events_happening{};
if (native_pfd.revents & (POLLIN | POLLHUP) &&
events.test_and_reset(lv2_socket::poll_t::read))
events_happening += lv2_socket::poll_t::read;
if (native_pfd.revents & POLLOUT &&
events.test_and_reset(lv2_socket::poll_t::write))
events_happening += lv2_socket::poll_t::write;
if (native_pfd.revents & POLLERR &&
events.test_and_reset(lv2_socket::poll_t::error))
events_happening += lv2_socket::poll_t::error;
if (events_happening || (!queue.empty() && (so_rcvtimeo || so_sendtimeo))) {
std::lock_guard lock(mutex);
#ifdef _WIN32
if (unset_connecting)
set_connecting(false);
#endif
u32 handled = 0;
for (auto it = queue.begin(); it != queue.end();) {
if (it->second(events_happening)) {
it = queue.erase(it);
handled++;
continue;
}
it++;
}
if (handled &&
(type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) {
const u32 prev_value =
g_fxo->get<network_context>().num_polls.fetch_sub(handled);
ensure(prev_value >= handled);
}
if (queue.empty()) {
events.store({});
}
}
}
void lv2_socket::queue_wake(ppu_thread *ppu) {
switch (type) {
case SYS_NET_SOCK_STREAM:
case SYS_NET_SOCK_DGRAM:
g_fxo->get<network_context>().add_ppu_to_awake(ppu);
break;
case SYS_NET_SOCK_DGRAM_P2P:
case SYS_NET_SOCK_STREAM_P2P:
g_fxo->get<p2p_context>().add_ppu_to_awake(ppu);
break;
default:
break;
}
}
lv2_socket &lv2_socket::operator=(thread_state s) noexcept {
if (s == thread_state::destroying_context) {
close();
}
return *this;
}
lv2_socket::~lv2_socket() noexcept {}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,400 @@
#include "stdafx.h"
#include "Emu/NP/np_helpers.h"
#include "sys_net/lv2_socket_p2p.h"
#include "sys_net/network_context.h"
#include "sys_net/sys_net_helpers.h"
LOG_CHANNEL(sys_net);
lv2_socket_p2p::lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type,
lv2_ip_protocol protocol)
: lv2_socket(family, type, protocol) {
sockopt_cache cache_type;
cache_type.data._int = SYS_NET_SOCK_DGRAM_P2P;
cache_type.len = 4;
sockopts[(static_cast<u64>(SYS_NET_SOL_SOCKET) << 32ull) | SYS_NET_SO_TYPE] =
cache_type;
}
lv2_socket_p2p::lv2_socket_p2p(utils::serial &ar, lv2_socket_type type)
: lv2_socket(make_exact(ar), type) {
ar(port, vport, bound_addr);
auto data_dequeue =
ar.pop<std::deque<std::pair<sys_net_sockaddr_in_p2p, std::vector<u8>>>>();
for (; !data_dequeue.empty(); data_dequeue.pop_front()) {
data.push(std::move(data_dequeue.front()));
}
}
void lv2_socket_p2p::save(utils::serial &ar) {
lv2_socket::save(ar, true);
ar(port, vport, bound_addr);
std::deque<std::pair<sys_net_sockaddr_in_p2p, std::vector<u8>>> data_dequeue;
for (auto save_data = ::as_rvalue(data); !save_data.empty();
save_data.pop()) {
data_dequeue.push_back(std::move(save_data.front()));
}
ar(data_dequeue);
}
void lv2_socket_p2p::handle_new_data(sys_net_sockaddr_in_p2p p2p_addr,
std::vector<u8> p2p_data) {
std::lock_guard lock(mutex);
sys_net.trace("Received a P2P packet for vport %d and saved it",
p2p_addr.sin_vport);
data.push(std::make_pair(std::move(p2p_addr), std::move(p2p_data)));
// Check if poll is happening
if (events.test_and_reset(lv2_socket::poll_t::read)) {
bs_t<lv2_socket::poll_t> read_event = lv2_socket::poll_t::read;
for (auto it = queue.begin(); it != queue.end();) {
if (it->second(read_event)) {
it = queue.erase(it);
continue;
}
it++;
}
if (queue.empty()) {
events.store({});
}
}
}
std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr>
lv2_socket_p2p::accept([[maybe_unused]] bool is_lock) {
sys_net.fatal("[P2P] accept() called on a P2P socket");
return {};
}
std::optional<s32>
lv2_socket_p2p::connect([[maybe_unused]] const sys_net_sockaddr &addr) {
sys_net.fatal("[P2P] connect() called on a P2P socket");
return {};
}
s32 lv2_socket_p2p::connect_followup() {
sys_net.fatal("[P2P] connect_followup() called on a P2P socket");
return {};
}
std::pair<s32, sys_net_sockaddr> lv2_socket_p2p::getpeername() {
sys_net.fatal("[P2P] getpeername() called on a P2P socket");
return {};
}
s32 lv2_socket_p2p::listen([[maybe_unused]] s32 backlog) {
sys_net.fatal("[P2P] listen() called on a P2P socket");
return {};
}
s32 lv2_socket_p2p::bind(const sys_net_sockaddr &addr) {
const auto *psa_in_p2p =
reinterpret_cast<const sys_net_sockaddr_in_p2p *>(&addr);
u16 p2p_port = psa_in_p2p->sin_port;
u16 p2p_vport = psa_in_p2p->sin_vport;
sys_net.notice("[P2P] Trying to bind %s:%d:%d",
np::ip_to_string(std::bit_cast<u32>(psa_in_p2p->sin_addr)),
p2p_port, p2p_vport);
if (p2p_port != SCE_NP_PORT) {
if (p2p_port == 0) {
return -SYS_NET_EINVAL;
}
sys_net.warning("[P2P] Attempting to bind a socket to a port != %d",
+SCE_NP_PORT);
}
socket_type real_socket{};
auto &nc = g_fxo->get<p2p_context>();
{
std::lock_guard list_lock(nc.list_p2p_ports_mutex);
nc.create_p2p_port(p2p_port);
auto &pport = ::at32(nc.list_p2p_ports, p2p_port);
real_socket = pport.p2p_socket;
{
std::lock_guard lock(pport.bound_p2p_vports_mutex);
if (p2p_vport == 0) {
// Find a free vport starting at 30000
p2p_vport = 30000;
while (pport.bound_p2p_vports.contains(p2p_vport)) {
p2p_vport++;
}
}
if (pport.bound_p2p_vports.contains(p2p_vport)) {
// Check that all other sockets are SO_REUSEADDR or SO_REUSEPORT
auto &bound_sockets = ::at32(pport.bound_p2p_vports, p2p_vport);
if (!sys_net_helpers::all_reusable(bound_sockets)) {
return -SYS_NET_EADDRINUSE;
}
bound_sockets.insert(lv2_id);
} else {
std::set<s32> bound_ports{lv2_id};
pport.bound_p2p_vports.insert(
std::make_pair(p2p_vport, std::move(bound_ports)));
}
}
}
{
std::lock_guard lock(mutex);
port = p2p_port;
vport = p2p_vport;
native_socket = real_socket;
bound_addr = psa_in_p2p->sin_addr;
}
return CELL_OK;
}
std::pair<s32, sys_net_sockaddr> lv2_socket_p2p::getsockname() {
std::lock_guard lock(mutex);
// Unbound socket
if (!native_socket) {
return {CELL_OK, {}};
}
sys_net_sockaddr sn_addr{};
sys_net_sockaddr_in_p2p *paddr =
reinterpret_cast<sys_net_sockaddr_in_p2p *>(&sn_addr);
paddr->sin_len = sizeof(sys_net_sockaddr_in);
paddr->sin_family = SYS_NET_AF_INET;
paddr->sin_port = port;
paddr->sin_vport = vport;
paddr->sin_addr = bound_addr;
return {CELL_OK, sn_addr};
}
std::tuple<s32, lv2_socket::sockopt_data, u32>
lv2_socket_p2p::getsockopt(s32 level, s32 optname, u32 len) {
std::lock_guard lock(mutex);
const u64 key = (static_cast<u64>(level) << 32) | static_cast<u64>(optname);
if (!sockopts.contains(key)) {
sys_net.error("Unhandled getsockopt(level=%d, optname=%d, len=%d)", level,
optname, len);
return {};
}
const auto &cache = ::at32(sockopts, key);
return {CELL_OK, cache.data, cache.len};
}
s32 lv2_socket_p2p::setsockopt(s32 level, s32 optname,
const std::vector<u8> &optval) {
std::lock_guard lock(mutex);
int native_int = *reinterpret_cast<const be_t<s32> *>(optval.data());
if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO) {
so_nbio = native_int;
}
const u64 key = (static_cast<u64>(level) << 32) | static_cast<u64>(optname);
sockopt_cache cache{};
memcpy(&cache.data._int, optval.data(), optval.size());
cache.len = ::size32(optval);
sockopts[key] = std::move(cache);
return CELL_OK;
}
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>>
lv2_socket_p2p::recvfrom(s32 flags, u32 len, bool is_lock) {
std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);
if (is_lock) {
lock.lock();
}
if (data.empty()) {
if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT))
return {{-SYS_NET_EWOULDBLOCK, {}, {}}};
return std::nullopt;
}
sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport,
data.size());
std::vector<u8> res_buf(len);
const auto &p2p_data = data.front();
s32 native_result = std::min(len, static_cast<u32>(p2p_data.second.size()));
memcpy(res_buf.data(), p2p_data.second.data(), native_result);
sys_net_sockaddr sn_addr;
memcpy(&sn_addr, &p2p_data.first, sizeof(sn_addr));
data.pop();
return {{native_result, res_buf, sn_addr}};
}
std::optional<s32>
lv2_socket_p2p::sendto(s32 flags, const std::vector<u8> &buf,
std::optional<sys_net_sockaddr> opt_sn_addr,
bool is_lock) {
std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);
if (is_lock) {
lock.lock();
}
ensure(opt_sn_addr);
ensure(socket); // ensures it has been bound
ensure(
buf.size() <=
static_cast<usz>(
65535 -
VPORT_P2P_HEADER_SIZE)); // catch games using full payload for future
// fragmentation implementation if necessary
const u16 p2p_port =
reinterpret_cast<const sys_net_sockaddr_in *>(&*opt_sn_addr)->sin_port;
const u16 p2p_vport =
reinterpret_cast<const sys_net_sockaddr_in_p2p *>(&*opt_sn_addr)
->sin_vport;
auto native_addr = sys_net_addr_to_native_addr(*opt_sn_addr);
char ip_str[16];
inet_ntop(AF_INET, &native_addr.sin_addr, ip_str, sizeof(ip_str));
sys_net.trace("[P2P] Sending a packet to %s:%d:%d", ip_str, p2p_port,
p2p_vport);
std::vector<u8> p2p_data(buf.size() + VPORT_P2P_HEADER_SIZE);
const le_t<u16> p2p_vport_le = p2p_vport;
const le_t<u16> src_vport_le = vport;
const le_t<u16> p2p_flags_le = P2P_FLAG_P2P;
memcpy(p2p_data.data(), &p2p_vport_le, sizeof(u16));
memcpy(p2p_data.data() + sizeof(u16), &src_vport_le, sizeof(u16));
memcpy(p2p_data.data() + sizeof(u16) + sizeof(u16), &p2p_flags_le,
sizeof(u16));
memcpy(p2p_data.data() + VPORT_P2P_HEADER_SIZE, buf.data(), buf.size());
int native_flags = 0;
if (flags & SYS_NET_MSG_WAITALL) {
native_flags |= MSG_WAITALL;
}
auto native_result = np::sendto_possibly_ipv6(
native_socket, reinterpret_cast<const char *>(p2p_data.data()),
::size32(p2p_data), &native_addr, native_flags);
if (native_result >= 0) {
return {std::max<s32>(native_result - VPORT_P2P_HEADER_SIZE, 0l)};
}
s32 result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
if (result) {
return {-result};
}
// Note that this can only happen if the send buffer is full
return std::nullopt;
}
std::optional<s32>
lv2_socket_p2p::sendmsg([[maybe_unused]] s32 flags,
[[maybe_unused]] const sys_net_msghdr &msg,
[[maybe_unused]] bool is_lock) {
sys_net.todo("lv2_socket_p2p::sendmsg");
return {};
}
void lv2_socket_p2p::close() {
if (!port || !vport) {
return;
}
if (g_fxo->is_init<p2p_context>()) {
auto &nc = g_fxo->get<p2p_context>();
std::lock_guard lock(nc.list_p2p_ports_mutex);
if (!nc.list_p2p_ports.contains(port))
return;
auto &p2p_port = ::at32(nc.list_p2p_ports, port);
{
std::lock_guard lock(p2p_port.bound_p2p_vports_mutex);
if (!p2p_port.bound_p2p_vports.contains(vport)) {
return;
}
auto &bound_sockets = ::at32(p2p_port.bound_p2p_vports, vport);
bound_sockets.erase(lv2_id);
if (bound_sockets.empty()) {
p2p_port.bound_p2p_vports.erase(vport);
}
}
}
}
s32 lv2_socket_p2p::shutdown([[maybe_unused]] s32 how) {
sys_net.todo("[P2P] shutdown");
return CELL_OK;
}
s32 lv2_socket_p2p::poll(sys_net_pollfd &sn_pfd,
[[maybe_unused]] pollfd &native_pfd) {
std::lock_guard lock(mutex);
ensure(vport);
// Check if it's a bound P2P socket
if ((sn_pfd.events & SYS_NET_POLLIN) && !data.empty()) {
sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport,
data.size());
sn_pfd.revents |= SYS_NET_POLLIN;
}
// Data can always be written on a dgram socket
if (sn_pfd.events & SYS_NET_POLLOUT) {
sn_pfd.revents |= SYS_NET_POLLOUT;
}
return sn_pfd.revents ? 1 : 0;
}
std::tuple<bool, bool, bool>
lv2_socket_p2p::select(bs_t<lv2_socket::poll_t> selected,
[[maybe_unused]] pollfd &native_pfd) {
std::lock_guard lock(mutex);
bool read_set = false;
bool write_set = false;
// Check if it's a bound P2P socket
if ((selected & lv2_socket::poll_t::read) && vport && !data.empty()) {
sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport,
data.size());
read_set = true;
}
if (selected & lv2_socket::poll_t::write) {
write_set = true;
}
return {read_set, write_set, false};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
#include "stdafx.h"
#include "Emu/NP/vport0.h"
#include "sys_net/lv2_socket_raw.h"
LOG_CHANNEL(sys_net);
template <typename T> struct socket_raw_logging {
socket_raw_logging() = default;
socket_raw_logging(const socket_raw_logging &) = delete;
socket_raw_logging &operator=(const socket_raw_logging &) = delete;
atomic_t<bool> logged = false;
};
#define LOG_ONCE(raw_var, message) \
if (!g_fxo->get<socket_raw_logging<class raw_var>>().logged.exchange( \
true)) { \
sys_net.todo(message); \
}
lv2_socket_raw::lv2_socket_raw(lv2_socket_family family, lv2_socket_type type,
lv2_ip_protocol protocol)
: lv2_socket(family, type, protocol) {}
lv2_socket_raw::lv2_socket_raw(utils::serial &ar, lv2_socket_type type)
: lv2_socket(make_exact(ar), type) {}
void lv2_socket_raw::save(utils::serial &ar) { lv2_socket::save(ar, true); }
std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr>
lv2_socket_raw::accept([[maybe_unused]] bool is_lock) {
sys_net.fatal("[RAW] accept() called on a RAW socket");
return {};
}
std::optional<s32>
lv2_socket_raw::connect([[maybe_unused]] const sys_net_sockaddr &addr) {
sys_net.fatal("[RAW] connect() called on a RAW socket");
return CELL_OK;
}
s32 lv2_socket_raw::connect_followup() {
sys_net.fatal("[RAW] connect_followup() called on a RAW socket");
return CELL_OK;
}
std::pair<s32, sys_net_sockaddr> lv2_socket_raw::getpeername() {
LOG_ONCE(raw_getpeername, "[RAW] getpeername() called on a RAW socket");
return {};
}
s32 lv2_socket_raw::listen([[maybe_unused]] s32 backlog) {
LOG_ONCE(raw_listen, "[RAW] listen() called on a RAW socket");
return {};
}
s32 lv2_socket_raw::bind([[maybe_unused]] const sys_net_sockaddr &addr) {
LOG_ONCE(raw_bind, "lv2_socket_raw::bind");
return {};
}
std::pair<s32, sys_net_sockaddr> lv2_socket_raw::getsockname() {
LOG_ONCE(raw_getsockname, "lv2_socket_raw::getsockname");
return {};
}
std::tuple<s32, lv2_socket::sockopt_data, u32>
lv2_socket_raw::getsockopt([[maybe_unused]] s32 level,
[[maybe_unused]] s32 optname,
[[maybe_unused]] u32 len) {
LOG_ONCE(raw_getsockopt, "lv2_socket_raw::getsockopt");
return {};
}
s32 lv2_socket_raw::setsockopt(s32 level, s32 optname,
const std::vector<u8> &optval) {
LOG_ONCE(raw_setsockopt, "lv2_socket_raw::setsockopt");
// TODO
int native_int = *reinterpret_cast<const be_t<s32> *>(optval.data());
if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO) {
so_nbio = native_int;
}
return {};
}
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>>
lv2_socket_raw::recvfrom(s32 flags, [[maybe_unused]] u32 len,
[[maybe_unused]] bool is_lock) {
LOG_ONCE(raw_recvfrom, "lv2_socket_raw::recvfrom");
if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) {
return {{-SYS_NET_EWOULDBLOCK, {}, {}}};
}
return {};
}
std::optional<s32> lv2_socket_raw::sendto(
[[maybe_unused]] s32 flags, [[maybe_unused]] const std::vector<u8> &buf,
[[maybe_unused]] std::optional<sys_net_sockaddr> opt_sn_addr,
[[maybe_unused]] bool is_lock) {
LOG_ONCE(raw_sendto, "lv2_socket_raw::sendto");
return ::size32(buf);
}
std::optional<s32>
lv2_socket_raw::sendmsg([[maybe_unused]] s32 flags,
[[maybe_unused]] const sys_net_msghdr &msg,
[[maybe_unused]] bool is_lock) {
LOG_ONCE(raw_sendmsg, "lv2_socket_raw::sendmsg");
return {};
}
void lv2_socket_raw::close() { LOG_ONCE(raw_close, "lv2_socket_raw::close"); }
s32 lv2_socket_raw::shutdown([[maybe_unused]] s32 how) {
LOG_ONCE(raw_shutdown, "lv2_socket_raw::shutdown");
return {};
}
s32 lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd &sn_pfd,
[[maybe_unused]] pollfd &native_pfd) {
LOG_ONCE(raw_poll, "lv2_socket_raw::poll");
return {};
}
std::tuple<bool, bool, bool>
lv2_socket_raw::select([[maybe_unused]] bs_t<lv2_socket::poll_t> selected,
[[maybe_unused]] pollfd &native_pfd) {
LOG_ONCE(raw_select, "lv2_socket_raw::select");
return {};
}

View file

@ -0,0 +1,299 @@
#include "stdafx.h"
#include "Emu/NP/ip_address.h"
#include "cellos/sys_sync.h"
#include "rpcsx/fw/ps3/sceNp.h" // for SCE_NP_PORT
#include "sys_net/network_context.h"
#include "sys_net/sys_net_helpers.h"
LOG_CHANNEL(sys_net);
// Used by RPCN to send signaling packets to RPCN server(for UDP hole punching)
bool send_packet_from_p2p_port_ipv4(const std::vector<u8> &data,
const sockaddr_in &addr) {
auto &nc = g_fxo->get<p2p_context>();
{
std::lock_guard list_lock(nc.list_p2p_ports_mutex);
if (nc.list_p2p_ports.contains(SCE_NP_PORT)) {
auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
if (def_port.is_ipv6) {
const auto addr6 = np::sockaddr_to_sockaddr6(addr);
if (::sendto(def_port.p2p_socket,
reinterpret_cast<const char *>(data.data()),
::size32(data), 0,
reinterpret_cast<const sockaddr *>(&addr6),
sizeof(sockaddr_in6)) == -1) {
sys_net.error(
"Failed to send IPv4 signaling packet on IPv6 socket: %s",
get_last_error(false, false));
return false;
}
} else if (::sendto(def_port.p2p_socket,
reinterpret_cast<const char *>(data.data()),
::size32(data), 0,
reinterpret_cast<const sockaddr *>(&addr),
sizeof(sockaddr_in)) == -1) {
sys_net.error("Failed to send signaling packet on IPv4 socket: %s",
get_last_error(false, false));
return false;
}
} else {
sys_net.error("send_packet_from_p2p_port_ipv4: port %d not present",
+SCE_NP_PORT);
return false;
}
}
return true;
}
bool send_packet_from_p2p_port_ipv6(const std::vector<u8> &data,
const sockaddr_in6 &addr) {
auto &nc = g_fxo->get<p2p_context>();
{
std::lock_guard list_lock(nc.list_p2p_ports_mutex);
if (nc.list_p2p_ports.contains(SCE_NP_PORT)) {
auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
ensure(def_port.is_ipv6);
if (::sendto(def_port.p2p_socket,
reinterpret_cast<const char *>(data.data()), ::size32(data),
0, reinterpret_cast<const sockaddr *>(&addr),
sizeof(sockaddr_in6)) == -1) {
sys_net.error("Failed to send signaling packet on IPv6 socket: %s",
get_last_error(false, false));
return false;
}
} else {
sys_net.error("send_packet_from_p2p_port_ipv6: port %d not present",
+SCE_NP_PORT);
return false;
}
}
return true;
}
std::vector<std::vector<u8>> get_rpcn_msgs() {
std::vector<std::vector<u8>> msgs;
auto &nc = g_fxo->get<p2p_context>();
{
std::lock_guard list_lock(nc.list_p2p_ports_mutex);
if (nc.list_p2p_ports.contains(SCE_NP_PORT)) {
auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
{
std::lock_guard lock(def_port.s_rpcn_mutex);
msgs = std::move(def_port.rpcn_msgs);
def_port.rpcn_msgs.clear();
}
} else {
sys_net.error("get_rpcn_msgs: port %d not present", +SCE_NP_PORT);
}
}
return msgs;
}
std::vector<signaling_message> get_sign_msgs() {
std::vector<signaling_message> msgs;
auto &nc = g_fxo->get<p2p_context>();
{
std::lock_guard list_lock(nc.list_p2p_ports_mutex);
if (nc.list_p2p_ports.contains(SCE_NP_PORT)) {
auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT);
{
std::lock_guard lock(def_port.s_sign_mutex);
msgs = std::move(def_port.sign_msgs);
def_port.sign_msgs.clear();
}
} else {
sys_net.error("get_sign_msgs: port %d not present", +SCE_NP_PORT);
}
}
return msgs;
}
namespace np {
void init_np_handler_dependencies();
}
void base_network_thread::add_ppu_to_awake(ppu_thread *ppu) {
std::lock_guard lock(mutex_ppu_to_awake);
ppu_to_awake.emplace_back(ppu);
}
void base_network_thread::del_ppu_to_awake(ppu_thread *ppu) {
std::lock_guard lock(mutex_ppu_to_awake);
for (auto it = ppu_to_awake.begin(); it != ppu_to_awake.end();) {
if (*it == ppu) {
it = ppu_to_awake.erase(it);
continue;
}
it++;
}
}
void base_network_thread::wake_threads() {
std::lock_guard lock(mutex_ppu_to_awake);
ppu_to_awake.erase(std::unique(ppu_to_awake.begin(), ppu_to_awake.end()),
ppu_to_awake.end());
for (ppu_thread *ppu : ppu_to_awake) {
network_clear_queue(*ppu);
lv2_obj::append(ppu);
}
if (!ppu_to_awake.empty()) {
ppu_to_awake.clear();
lv2_obj::awake_all();
}
}
p2p_thread::p2p_thread() { np::init_np_handler_dependencies(); }
void p2p_thread::bind_sce_np_port() {
std::lock_guard list_lock(list_p2p_ports_mutex);
create_p2p_port(SCE_NP_PORT);
}
void network_thread::operator()() {
std::vector<shared_ptr<lv2_socket>> socklist;
socklist.reserve(lv2_socket::id_count);
{
std::lock_guard lock(mutex_ppu_to_awake);
ppu_to_awake.clear();
}
std::vector<::pollfd> fds(lv2_socket::id_count);
#ifdef _WIN32
std::vector<bool> connecting(lv2_socket::id_count);
std::vector<bool> was_connecting(lv2_socket::id_count);
#endif
while (thread_ctrl::state() != thread_state::aborting) {
if (!num_polls) {
thread_ctrl::wait_on(num_polls, 0);
continue;
}
ensure(socklist.size() <= lv2_socket::id_count);
// Wait with 1ms timeout
#ifdef _WIN32
windows_poll(fds, ::size32(socklist), 1, connecting);
#else
::poll(fds.data(), socklist.size(), 1);
#endif
std::lock_guard lock(mutex_thread_loop);
for (usz i = 0; i < socklist.size(); i++) {
#ifdef _WIN32
socklist[i]->handle_events(fds[i], was_connecting[i] && !connecting[i]);
#else
socklist[i]->handle_events(fds[i]);
#endif
}
wake_threads();
socklist.clear();
// Obtain all native active sockets
idm::select<lv2_socket>([&](u32 id, lv2_socket &s) {
if (s.get_type() == SYS_NET_SOCK_DGRAM ||
s.get_type() == SYS_NET_SOCK_STREAM) {
socklist.emplace_back(idm::get_unlocked<lv2_socket>(id));
}
});
for (usz i = 0; i < socklist.size(); i++) {
auto events = socklist[i]->get_events();
fds[i].fd = events ? socklist[i]->get_socket() : -1;
fds[i].events = (events & lv2_socket::poll_t::read ? POLLIN : 0) |
(events & lv2_socket::poll_t::write ? POLLOUT : 0) | 0;
fds[i].revents = 0;
#ifdef _WIN32
const auto cur_connecting = socklist[i]->is_connecting();
was_connecting[i] = cur_connecting;
connecting[i] = cur_connecting;
#endif
}
}
}
// Must be used under list_p2p_ports_mutex lock!
void p2p_thread::create_p2p_port(u16 p2p_port) {
if (!list_p2p_ports.contains(p2p_port)) {
list_p2p_ports.emplace(std::piecewise_construct,
std::forward_as_tuple(p2p_port),
std::forward_as_tuple(p2p_port));
const u32 prev_value = num_p2p_ports.fetch_add(1);
if (!prev_value) {
num_p2p_ports.notify_one();
}
}
}
void p2p_thread::operator()() {
std::vector<::pollfd> p2p_fd(lv2_socket::id_count);
while (thread_ctrl::state() != thread_state::aborting) {
if (!num_p2p_ports) {
thread_ctrl::wait_on(num_p2p_ports, 0);
continue;
}
// Check P2P sockets for incoming packets
auto num_p2p_sockets = 0;
std::memset(p2p_fd.data(), 0, p2p_fd.size() * sizeof(::pollfd));
{
auto set_fd = [&](socket_type socket) {
p2p_fd[num_p2p_sockets].events = POLLIN;
p2p_fd[num_p2p_sockets].revents = 0;
p2p_fd[num_p2p_sockets].fd = socket;
num_p2p_sockets++;
};
std::lock_guard lock(list_p2p_ports_mutex);
for (const auto &[_, p2p_port] : list_p2p_ports) {
set_fd(p2p_port.p2p_socket);
}
}
#ifdef _WIN32
const auto ret_p2p = WSAPoll(p2p_fd.data(), num_p2p_sockets, 1);
#else
const auto ret_p2p = ::poll(p2p_fd.data(), num_p2p_sockets, 1);
#endif
if (ret_p2p > 0) {
std::lock_guard lock(list_p2p_ports_mutex);
auto fd_index = 0;
auto process_fd = [&](nt_p2p_port &p2p_port) {
if ((p2p_fd[fd_index].revents & POLLIN) == POLLIN ||
(p2p_fd[fd_index].revents & POLLRDNORM) == POLLRDNORM) {
while (p2p_port.recv_data())
;
}
fd_index++;
};
for (auto &[_, p2p_port] : list_p2p_ports) {
process_fd(p2p_port);
}
wake_threads();
} else if (ret_p2p < 0) {
sys_net.error("[P2P] Error poll on master P2P socket: %d",
get_last_error(false));
}
}
}

View file

@ -0,0 +1,377 @@
#include "sys_net/nt_p2p_port.h"
#include "Emu/NP/ip_address.h"
#include "Emu/NP/np_handler.h"
#include "Emu/NP/signaling_handler.h"
#include "Emu/NP/vport0.h"
#include "stdafx.h"
#include "sys_net/lv2_socket_p2ps.h"
#include "sys_net/sys_net_helpers.h"
LOG_CHANNEL(sys_net);
namespace sys_net_helpers {
bool all_reusable(const std::set<s32> &sock_ids) {
for (const s32 sock_id : sock_ids) {
const auto [_, reusable] =
idm::check<lv2_socket>(sock_id, [&](lv2_socket &sock) -> bool {
auto [res_reuseaddr, optval_reuseaddr, optlen_reuseaddr] =
sock.getsockopt(SYS_NET_SOL_SOCKET, SYS_NET_SO_REUSEADDR,
sizeof(s32));
auto [res_reuseport, optval_reuseport, optlen_reuseport] =
sock.getsockopt(SYS_NET_SOL_SOCKET, SYS_NET_SO_REUSEPORT,
sizeof(s32));
const bool reuse_addr =
optlen_reuseaddr == 4 && !!optval_reuseaddr._int;
const bool reuse_port =
optlen_reuseport == 4 && !!optval_reuseport._int;
return (reuse_addr || reuse_port);
});
if (!reusable) {
return false;
}
}
return true;
}
} // namespace sys_net_helpers
nt_p2p_port::nt_p2p_port(u16 port) : port(port) {
is_ipv6 = np::is_ipv6_supported();
// Creates and bind P2P Socket
p2p_socket = is_ipv6 ? ::socket(AF_INET6, SOCK_DGRAM, 0)
: ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
#ifdef _WIN32
if (p2p_socket == INVALID_SOCKET)
#else
if (p2p_socket == -1)
#endif
fmt::throw_exception("Failed to create DGRAM socket for P2P socket: %s!",
get_last_error(true));
np::set_socket_non_blocking(p2p_socket);
u32 optval = 131072; // value obtained from DECR for a SOCK_DGRAM_P2P
// socket(should maybe be bigger for actual socket?)
if (setsockopt(p2p_socket, SOL_SOCKET, SO_RCVBUF,
reinterpret_cast<const char *>(&optval), sizeof(optval)) != 0)
fmt::throw_exception("Error setsockopt SO_RCVBUF on P2P socket: %s",
get_last_error(true));
int ret_bind = 0;
const u16 be_port = std::bit_cast<u16, be_t<u16>>(port);
if (is_ipv6) {
// Some OS(Windows, maybe more) will only support IPv6 adressing by default
// and we need IPv4 over IPv6
optval = 0;
if (setsockopt(p2p_socket, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char *>(&optval),
sizeof(optval)) != 0)
fmt::throw_exception("Error setsockopt IPV6_V6ONLY on P2P socket: %s",
get_last_error(true));
::sockaddr_in6 p2p_ipv6_addr{.sin6_family = AF_INET6, .sin6_port = be_port};
ret_bind = ::bind(p2p_socket, reinterpret_cast<sockaddr *>(&p2p_ipv6_addr),
sizeof(p2p_ipv6_addr));
} else {
::sockaddr_in p2p_ipv4_addr{.sin_family = AF_INET, .sin_port = be_port};
ret_bind = ::bind(p2p_socket, reinterpret_cast<sockaddr *>(&p2p_ipv4_addr),
sizeof(p2p_ipv4_addr));
}
if (ret_bind == -1)
fmt::throw_exception("Failed to bind DGRAM socket to %d for P2P: %s!", port,
get_last_error(true));
auto &nph = g_fxo->get<named_thread<np::np_handler>>();
nph.upnp_add_port_mapping(port, "UDP");
sys_net.notice("P2P port %d was bound!", port);
}
nt_p2p_port::~nt_p2p_port() { np::close_socket(p2p_socket); }
void nt_p2p_port::dump_packet(p2ps_encapsulated_tcp *tcph) {
sys_net.trace("PACKET DUMP:\nsrc_port: %d\ndst_port: %d\nflags: %d\nseq: "
"%d\nack: %d\nlen: %d",
tcph->src_port, tcph->dst_port, tcph->flags, tcph->seq,
tcph->ack, tcph->length);
}
// Must be used under bound_p2p_vports_mutex lock
u16 nt_p2p_port::get_port() {
if (binding_port == 0) {
binding_port = 30000;
}
return binding_port++;
}
bool nt_p2p_port::handle_connected(s32 sock_id,
p2ps_encapsulated_tcp *tcp_header, u8 *data,
::sockaddr_storage *op_addr) {
const auto sock =
idm::check<lv2_socket>(sock_id, [&](lv2_socket &sock) -> bool {
ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P);
auto &sock_p2ps = reinterpret_cast<lv2_socket_p2ps &>(sock);
return sock_p2ps.handle_connected(tcp_header, data, op_addr, this);
});
if (!sock) {
sys_net.error("[P2PS] Couldn't find the socket!");
return false;
}
if (!sock.ret) {
sys_net.error("[P2PS] handle_connected() failed!");
return false;
}
return true;
}
bool nt_p2p_port::handle_listening(s32 sock_id,
p2ps_encapsulated_tcp *tcp_header, u8 *data,
::sockaddr_storage *op_addr) {
auto sock = idm::get_unlocked<lv2_socket>(sock_id);
if (!sock)
return false;
auto &sock_p2ps = reinterpret_cast<lv2_socket_p2ps &>(*sock);
return sock_p2ps.handle_listening(tcp_header, data, op_addr);
}
bool nt_p2p_port::recv_data() {
::sockaddr_storage native_addr{};
::socklen_t native_addrlen = sizeof(native_addr);
const auto recv_res = ::recvfrom(
p2p_socket, reinterpret_cast<char *>(p2p_recv_data.data()),
::size32(p2p_recv_data), 0,
reinterpret_cast<struct sockaddr *>(&native_addr), &native_addrlen);
if (recv_res == -1) {
auto lerr = get_last_error(false);
if (lerr != SYS_NET_EINPROGRESS && lerr != SYS_NET_EWOULDBLOCK)
sys_net.error("Error recvfrom on %s P2P socket: %d",
is_ipv6 ? "IPv6" : "IPv4", lerr);
return false;
}
if (recv_res < static_cast<s32>(sizeof(u16))) {
sys_net.error("Received badly formed packet on P2P port(no vport)!");
return true;
}
u16 dst_vport = reinterpret_cast<le_t<u16> &>(p2p_recv_data[0]);
if (is_ipv6) {
const auto *addr_ipv6 = reinterpret_cast<sockaddr_in6 *>(&native_addr);
const auto addr_ipv4 = np::sockaddr6_to_sockaddr(*addr_ipv6);
native_addr = {};
std::memcpy(&native_addr, &addr_ipv4, sizeof(addr_ipv4));
}
if (dst_vport == 0) {
if (recv_res < VPORT_0_HEADER_SIZE) {
sys_net.error("Bad vport 0 packet(no subset)!");
return true;
}
const u8 subset = p2p_recv_data[2];
const auto data_size = recv_res - VPORT_0_HEADER_SIZE;
std::vector<u8> vport_0_data(p2p_recv_data.data() + VPORT_0_HEADER_SIZE,
p2p_recv_data.data() + VPORT_0_HEADER_SIZE +
data_size);
switch (subset) {
case SUBSET_RPCN: {
std::lock_guard lock(s_rpcn_mutex);
rpcn_msgs.push_back(std::move(vport_0_data));
return true;
}
case SUBSET_SIGNALING: {
signaling_message msg;
msg.src_addr =
reinterpret_cast<struct sockaddr_in *>(&native_addr)->sin_addr.s_addr;
msg.src_port = std::bit_cast<u16, be_t<u16>>(
reinterpret_cast<struct sockaddr_in *>(&native_addr)->sin_port);
msg.data = std::move(vport_0_data);
{
std::lock_guard lock(s_sign_mutex);
sign_msgs.push_back(std::move(msg));
}
auto &sigh = g_fxo->get<named_thread<signaling_handler>>();
sigh.wake_up();
return true;
}
default: {
sys_net.error("Invalid vport 0 subset!");
return true;
}
}
}
if (recv_res < VPORT_P2P_HEADER_SIZE) {
return true;
}
const u16 src_vport =
*reinterpret_cast<le_t<u16> *>(p2p_recv_data.data() + sizeof(u16));
const u16 vport_flags = *reinterpret_cast<le_t<u16> *>(
p2p_recv_data.data() + sizeof(u16) + sizeof(u16));
std::vector<u8> p2p_data(recv_res - VPORT_P2P_HEADER_SIZE);
memcpy(p2p_data.data(), p2p_recv_data.data() + VPORT_P2P_HEADER_SIZE,
p2p_data.size());
if (vport_flags & P2P_FLAG_P2P) {
std::lock_guard lock(bound_p2p_vports_mutex);
if (bound_p2p_vports.contains(dst_vport)) {
sys_net_sockaddr_in_p2p p2p_addr{};
p2p_addr.sin_len = sizeof(sys_net_sockaddr_in);
p2p_addr.sin_family = SYS_NET_AF_INET;
p2p_addr.sin_addr = std::bit_cast<be_t<u32>, u32>(
reinterpret_cast<struct sockaddr_in *>(&native_addr)
->sin_addr.s_addr);
p2p_addr.sin_vport = src_vport;
p2p_addr.sin_port = std::bit_cast<be_t<u16>, u16>(
reinterpret_cast<struct sockaddr_in *>(&native_addr)->sin_port);
auto &bound_sockets = ::at32(bound_p2p_vports, dst_vport);
for (const auto sock_id : bound_sockets) {
const auto sock =
idm::check<lv2_socket>(sock_id, [&](lv2_socket &sock) {
ensure(sock.get_type() == SYS_NET_SOCK_DGRAM_P2P);
auto &sock_p2p = reinterpret_cast<lv2_socket_p2p &>(sock);
sock_p2p.handle_new_data(p2p_addr, p2p_data);
});
if (!sock) {
sys_net.error("Socket %d found in bound_p2p_vports didn't exist!",
sock_id);
bound_sockets.erase(sock_id);
if (bound_sockets.empty()) {
bound_p2p_vports.erase(dst_vport);
}
}
}
return true;
}
} else if (vport_flags & P2P_FLAG_P2PS) {
if (p2p_data.size() < sizeof(p2ps_encapsulated_tcp)) {
sys_net.notice("Received P2P packet targeted at unbound vport(likely) or "
"invalid(vport=%d)",
dst_vport);
return true;
}
auto *tcp_header =
reinterpret_cast<p2ps_encapsulated_tcp *>(p2p_data.data());
// Validate signature & length
if (tcp_header->signature != P2PS_U2S_SIG) {
sys_net.notice("Received P2P packet targeted at unbound vport(vport=%d)",
dst_vport);
return true;
}
if (tcp_header->length !=
(p2p_data.size() - sizeof(p2ps_encapsulated_tcp))) {
sys_net.error(
"Received STREAM-P2P packet tcp length didn't match packet length");
return true;
}
// Sanity check
if (tcp_header->dst_port != dst_vport) {
sys_net.error("Received STREAM-P2P packet with dst_port != vport");
return true;
}
// Validate checksum
u16 given_checksum = tcp_header->checksum;
tcp_header->checksum = 0;
if (given_checksum !=
u2s_tcp_checksum(reinterpret_cast<const le_t<u16> *>(p2p_data.data()),
p2p_data.size())) {
sys_net.error("Checksum is invalid, dropping packet!");
return true;
}
// The packet is valid
dump_packet(tcp_header);
// Check if it's bound
const u64 key_connected =
(reinterpret_cast<struct sockaddr_in *>(&native_addr)
->sin_addr.s_addr) |
(static_cast<u64>(tcp_header->src_port) << 48) |
(static_cast<u64>(tcp_header->dst_port) << 32);
{
std::lock_guard lock(bound_p2p_vports_mutex);
if (bound_p2p_streams.contains(key_connected)) {
const auto sock_id = ::at32(bound_p2p_streams, key_connected);
sys_net.trace("Received packet for connected STREAM-P2P socket(s=%d)",
sock_id);
handle_connected(sock_id, tcp_header,
p2p_data.data() + sizeof(p2ps_encapsulated_tcp),
&native_addr);
return true;
}
if (bound_p2ps_vports.contains(tcp_header->dst_port)) {
const auto &bound_sockets =
::at32(bound_p2ps_vports, tcp_header->dst_port);
for (const auto sock_id : bound_sockets) {
sys_net.trace("Received packet for listening STREAM-P2P socket(s=%d)",
sock_id);
handle_listening(sock_id, tcp_header,
p2p_data.data() + sizeof(p2ps_encapsulated_tcp),
&native_addr);
}
return true;
}
if (tcp_header->flags == p2ps_tcp_flags::RST) {
sys_net.trace("[P2PS] Received RST on unbound P2PS");
return true;
}
// The P2PS packet was sent to an unbound vport, send a RST packet
p2ps_encapsulated_tcp send_hdr;
send_hdr.src_port = tcp_header->dst_port;
send_hdr.dst_port = tcp_header->src_port;
send_hdr.flags = p2ps_tcp_flags::RST;
auto packet = generate_u2s_packet(send_hdr, nullptr, 0);
if (np::sendto_possibly_ipv6(
p2p_socket, reinterpret_cast<char *>(packet.data()),
::size32(packet),
reinterpret_cast<const sockaddr_in *>(&native_addr), 0) == -1) {
sys_net.error("[P2PS] Error sending RST to sender to unbound P2PS: %s",
get_last_error(false));
return true;
}
sys_net.trace("[P2PS] Sent RST to sender to unbound P2PS");
return true;
}
}
sys_net.notice("Received a P2P packet with no bound target(dst_vport = %d)",
dst_vport);
return true;
}

View file

@ -0,0 +1,243 @@
#include "stdafx.h"
#include "Emu/Cell/PPUThread.h"
#include "Emu/IdManager.h"
#include "sys_net/lv2_socket.h"
#include "sys_net/network_context.h"
#include "sys_net/sys_net_helpers.h"
LOG_CHANNEL(sys_net);
int get_native_error() {
int native_error;
#ifdef _WIN32
native_error = WSAGetLastError();
#else
native_error = errno;
#endif
return native_error;
}
sys_net_error convert_error(bool is_blocking, int native_error,
[[maybe_unused]] bool is_connecting) {
// Convert the error code for socket functions to a one for sys_net
sys_net_error result{};
const char *name{};
#ifdef _WIN32
#define ERROR_CASE(error) \
case WSA##error: \
result = SYS_NET_##error; \
name = #error; \
break;
#else
#define ERROR_CASE(error) \
case error: \
result = SYS_NET_##error; \
name = #error; \
break;
#endif
switch (native_error) {
#ifndef _WIN32
ERROR_CASE(ENOENT);
ERROR_CASE(ENOMEM);
ERROR_CASE(EBUSY);
ERROR_CASE(ENOSPC);
ERROR_CASE(EPIPE);
#endif
// TODO: We don't currently support EFAULT or EINTR
// ERROR_CASE(EFAULT);
// ERROR_CASE(EINTR);
ERROR_CASE(EBADF);
ERROR_CASE(EACCES);
ERROR_CASE(EINVAL);
ERROR_CASE(EMFILE);
ERROR_CASE(EWOULDBLOCK);
ERROR_CASE(EINPROGRESS);
ERROR_CASE(EALREADY);
ERROR_CASE(EDESTADDRREQ);
ERROR_CASE(EMSGSIZE);
ERROR_CASE(EPROTOTYPE);
ERROR_CASE(ENOPROTOOPT);
ERROR_CASE(EPROTONOSUPPORT);
ERROR_CASE(EOPNOTSUPP);
ERROR_CASE(EPFNOSUPPORT);
ERROR_CASE(EAFNOSUPPORT);
ERROR_CASE(EADDRINUSE);
ERROR_CASE(EADDRNOTAVAIL);
ERROR_CASE(ENETDOWN);
ERROR_CASE(ENETUNREACH);
ERROR_CASE(ECONNABORTED);
ERROR_CASE(ECONNRESET);
ERROR_CASE(ENOBUFS);
ERROR_CASE(EISCONN);
ERROR_CASE(ENOTCONN);
ERROR_CASE(ESHUTDOWN);
ERROR_CASE(ETOOMANYREFS);
ERROR_CASE(ETIMEDOUT);
ERROR_CASE(ECONNREFUSED);
ERROR_CASE(EHOSTDOWN);
ERROR_CASE(EHOSTUNREACH);
#ifdef _WIN32
// Windows likes to be special with unique errors
case WSAENETRESET:
result = SYS_NET_ECONNRESET;
name = "WSAENETRESET";
break;
#endif
default:
fmt::throw_exception("sys_net get_last_error(is_blocking=%d, "
"native_error=%d): Unknown/illegal socket error",
is_blocking, native_error);
}
#ifdef _WIN32
if (is_connecting) {
// Windows will return SYS_NET_ENOTCONN when recvfrom/sendto is called on a
// socket that is connecting but not yet connected
if (result == SYS_NET_ENOTCONN)
return SYS_NET_EAGAIN;
}
#endif
if (name && result != SYS_NET_EWOULDBLOCK && result != SYS_NET_EINPROGRESS) {
sys_net.error("Socket error %s", name);
}
if (is_blocking && result == SYS_NET_EWOULDBLOCK) {
return {};
}
if (is_blocking && result == SYS_NET_EINPROGRESS) {
return {};
}
return result;
#undef ERROR_CASE
}
sys_net_error get_last_error(bool is_blocking, bool is_connecting) {
return convert_error(is_blocking, get_native_error(), is_connecting);
}
sys_net_sockaddr
native_addr_to_sys_net_addr(const ::sockaddr_storage &native_addr) {
ensure(native_addr.ss_family == AF_INET ||
native_addr.ss_family == AF_UNSPEC);
sys_net_sockaddr sn_addr;
sys_net_sockaddr_in *paddr =
reinterpret_cast<sys_net_sockaddr_in *>(&sn_addr);
*paddr = {};
paddr->sin_len = sizeof(sys_net_sockaddr_in);
paddr->sin_family = SYS_NET_AF_INET;
paddr->sin_port = std::bit_cast<be_t<u16>, u16>(
reinterpret_cast<const sockaddr_in *>(&native_addr)->sin_port);
paddr->sin_addr = std::bit_cast<be_t<u32>, u32>(
reinterpret_cast<const sockaddr_in *>(&native_addr)->sin_addr.s_addr);
return sn_addr;
}
::sockaddr_in sys_net_addr_to_native_addr(const sys_net_sockaddr &sn_addr) {
ensure(sn_addr.sa_family == SYS_NET_AF_INET);
const sys_net_sockaddr_in *psa_in =
reinterpret_cast<const sys_net_sockaddr_in *>(&sn_addr);
::sockaddr_in native_addr{};
native_addr.sin_family = AF_INET;
native_addr.sin_port = std::bit_cast<u16>(psa_in->sin_port);
native_addr.sin_addr.s_addr = std::bit_cast<u32>(psa_in->sin_addr);
#ifdef _WIN32
// Windows doesn't support sending packets to 0.0.0.0 but it works on unixes,
// send to 127.0.0.1 instead
if (native_addr.sin_addr.s_addr == 0x00000000) {
sys_net.warning("[Native] Redirected 0.0.0.0 to 127.0.0.1");
native_addr.sin_addr.s_addr = std::bit_cast<u32, be_t<u32>>(0x7F000001);
}
#endif
return native_addr;
}
bool is_ip_public_address(const ::sockaddr_in &addr) {
const u8 *ip = reinterpret_cast<const u8 *>(&addr.sin_addr.s_addr);
if ((ip[0] == 10) || (ip[0] == 127) ||
(ip[0] == 172 && (ip[1] >= 16 && ip[1] <= 31)) ||
(ip[0] == 192 && ip[1] == 168)) {
return false;
}
return true;
}
u32 network_clear_queue(ppu_thread &ppu) {
u32 cleared = 0;
idm::select<lv2_socket>(
[&](u32, lv2_socket &sock) { cleared += sock.clear_queue(&ppu); });
return cleared;
}
void clear_ppu_to_awake(ppu_thread &ppu) {
g_fxo->get<network_context>().del_ppu_to_awake(&ppu);
g_fxo->get<p2p_context>().del_ppu_to_awake(&ppu);
}
#ifdef _WIN32
// Workaround function for WSAPoll not reporting failed connections
// Note that this was fixed in Windows 10 version 2004 (after more than 10 years
// lol)
void windows_poll(std::vector<pollfd> &fds, unsigned long nfds, int timeout,
std::vector<bool> &connecting) {
ensure(fds.size() >= nfds);
ensure(connecting.size() >= nfds);
// Don't call WSAPoll with zero nfds (errors 10022 or 10038)
if (std::none_of(fds.begin(), fds.begin() + nfds,
[](pollfd &pfd) { return pfd.fd != INVALID_SOCKET; })) {
if (timeout > 0) {
Sleep(timeout);
}
return;
}
int r = ::WSAPoll(fds.data(), nfds, timeout);
if (r == SOCKET_ERROR) {
sys_net.error(
"WSAPoll failed: %s",
fmt::win_error{static_cast<unsigned long>(WSAGetLastError()), nullptr});
return;
}
for (unsigned long i = 0; i < nfds; i++) {
if (connecting[i]) {
if (!fds[i].revents) {
int error = 0;
socklen_t intlen = sizeof(error);
if (getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char *>(&error), &intlen) == -1 ||
error != 0) {
// Connection silently failed
connecting[i] = false;
fds[i].revents =
POLLERR | POLLHUP | (fds[i].events & (POLLIN | POLLOUT));
}
} else {
connecting[i] = false;
}
}
}
}
#endif