rpcs3/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp
Elad 575a245f8d
IDM: Implement lock-free smart pointers (#16403)
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`.

Notes to programmers:

* This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking).
* Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type.
* `idm::check/get_unlocked` can now be used anywhere.

Misc fixes:
* Fixes some segfaults with RPCN with interaction with IDM.
* Fix deadlocks in access violation handler due locking recursion.
* Fixes race condition in process exit-spawn on memory containers read.
* Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes.
* Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation)
* Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
2024-12-22 20:59:48 +02:00

1271 lines
30 KiB
C++

#include "stdafx.h"
#include "Emu/Cell/lv2/sys_net.h"
#include "Emu/NP/np_dnshook.h"
#include "Emu/NP/np_handler.h"
#include "lv2_socket_native.h"
#include "sys_net_helpers.h"
#ifdef _WIN32
constexpr SOCKET invalid_socket = INVALID_SOCKET;
#else
constexpr int invalid_socket = -1;
#endif
LOG_CHANNEL(sys_net);
lv2_socket_native::lv2_socket_native(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol)
: lv2_socket(family, type, protocol)
{
}
lv2_socket_native::lv2_socket_native(utils::serial& ar, lv2_socket_type type)
: lv2_socket(stx::make_exact(ar), type)
{
[[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net);
#ifdef _WIN32
ar(so_reuseaddr, so_reuseport);
#else
std::array<char, 8> dummy{};
ar(dummy);
if (dummy != std::array<char, 8>{})
{
sys_net.error("[Native] Savestate tried to load Win32 specific data, compatibility may be affected");
}
#endif
if (version >= 2)
{
// Flag to signal failure of TCP connection on socket start
ar(feign_tcp_conn_failure);
}
}
void lv2_socket_native::save(utils::serial& ar)
{
USING_SERIALIZATION_VERSION(lv2_net);
lv2_socket::save(ar, true);
#ifdef _WIN32
ar(so_reuseaddr, so_reuseport);
#else
ar(std::array<char, 8>{});
#endif
ar(is_socket_connected());
}
lv2_socket_native::~lv2_socket_native()
{
std::lock_guard lock(mutex);
if (socket)
{
#ifdef _WIN32
::closesocket(socket);
#else
::close(socket);
#endif
}
}
s32 lv2_socket_native::create_socket()
{
ensure(family == SYS_NET_AF_INET);
ensure(type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM);
ensure(protocol == SYS_NET_IPPROTO_IP || protocol == SYS_NET_IPPROTO_TCP || protocol == SYS_NET_IPPROTO_UDP);
const int native_domain = AF_INET;
const int native_type = type == SYS_NET_SOCK_STREAM ? SOCK_STREAM : SOCK_DGRAM;
int native_proto = protocol == SYS_NET_IPPROTO_TCP ? IPPROTO_TCP :
protocol == SYS_NET_IPPROTO_UDP ? IPPROTO_UDP :
IPPROTO_IP;
auto socket_res = ::socket(native_domain, native_type, native_proto);
if (socket_res == invalid_socket)
{
return -get_last_error(false);
}
set_socket(socket_res, family, type, protocol);
return CELL_OK;
}
void lv2_socket_native::set_socket(socket_type socket, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol)
{
this->socket = socket;
this->family = family;
this->type = type;
this->protocol = protocol;
set_default_buffers();
set_non_blocking();
}
std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> lv2_socket_native::accept(bool is_lock)
{
std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);
if (is_lock)
{
lock.lock();
}
::sockaddr_storage native_addr;
::socklen_t native_addrlen = sizeof(native_addr);
if (feign_tcp_conn_failure)
{
sys_net.error("Calling socket::accept() from a previously connected socket!");
}
socket_type native_socket = ::accept(socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);
if (native_socket != invalid_socket)
{
auto newsock = make_single<lv2_socket_native>(family, type, protocol);
newsock->set_socket(native_socket, family, type, protocol);
// Sockets inherit non blocking behaviour from their parent
newsock->so_nbio = so_nbio;
sys_net_sockaddr ps3_addr = native_addr_to_sys_net_addr(native_addr);
return {true, 0, std::move(newsock), ps3_addr};
}
if (auto result = get_last_error(!so_nbio); result)
{
return {true, -result, {}, {}};
}
return {false, {}, {}, {}};
}
s32 lv2_socket_native::bind(const sys_net_sockaddr& addr)
{
std::lock_guard lock(mutex);
const auto* psa_in = reinterpret_cast<const sys_net_sockaddr_in*>(&addr);
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
u32 saddr = nph.get_bind_ip();
if (saddr == 0)
{
// If zero use the supplied address
saddr = std::bit_cast<u32>(psa_in->sin_addr);
}
if (feign_tcp_conn_failure)
{
sys_net.error("Calling socket::bind() from a previously connected socket!");
}
::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 = saddr;
::socklen_t native_addr_len = sizeof(native_addr);
// Note that this is a hack(TODO)
// ATM we don't support binding 3658 udp because we use it for the p2ps main socket
// Only Fat Princess is known to do this to my knowledge
if (psa_in->sin_port == 3658 && type == SYS_NET_SOCK_DGRAM)
{
native_addr.sin_port = std::bit_cast<u16, be_t<u16>>(3659);
}
sys_net.warning("[Native] Trying to bind %s:%d", native_addr.sin_addr, std::bit_cast<be_t<u16>, u16>(native_addr.sin_port));
if (::bind(socket, reinterpret_cast<struct sockaddr*>(&native_addr), native_addr_len) == 0)
{
// Only UPNP port forward binds to 0.0.0.0
if (saddr == 0)
{
if (native_addr.sin_port == 0)
{
sockaddr_in client_addr;
socklen_t client_addr_size = sizeof(client_addr);
ensure(::getsockname(socket, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_size) == 0);
bound_port = std::bit_cast<u16, be_t<u16>>(client_addr.sin_port);
}
else
{
bound_port = std::bit_cast<u16, be_t<u16>>(native_addr.sin_port);
}
nph.upnp_add_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP");
}
last_bound_addr = addr;
return CELL_OK;
}
auto error = get_last_error(false);
#ifdef __linux__
if (error == SYS_NET_EACCES && std::bit_cast<be_t<u16>, u16>(native_addr.sin_port) < 1024)
{
sys_net.error("The game tried to bind a port < 1024 which is privileged on Linux\n"
"Consider setting rpcs3 privileges for it with: setcap 'cap_net_bind_service=+ep' /path/to/rpcs3");
}
#endif
return -error;
}
std::optional<s32> lv2_socket_native::connect(const sys_net_sockaddr& addr)
{
std::lock_guard lock(mutex);
const auto* psa_in = reinterpret_cast<const sys_net_sockaddr_in*>(&addr);
::sockaddr_in native_addr = sys_net_addr_to_native_addr(addr);
::socklen_t native_addr_len = sizeof(native_addr);
sys_net.notice("[Native] Attempting to connect on %s:%d", native_addr.sin_addr, std::bit_cast<be_t<u16>, u16>(native_addr.sin_port));
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
if (!nph.get_net_status() && is_ip_public_address(native_addr))
{
return -SYS_NET_EADDRNOTAVAIL;
}
if (psa_in->sin_port == 53)
{
// Add socket to the dns hook list
sys_net.notice("[Native] sys_net_bnet_connect: using DNS...");
auto& dnshook = g_fxo->get<np::dnshook>();
dnshook.add_dns_spy(lv2_id);
}
#ifdef _WIN32
bool was_connecting = connecting;
#endif
if (feign_tcp_conn_failure)
{
// As if still connected
return -SYS_NET_EALREADY;
}
if (::connect(socket, reinterpret_cast<struct sockaddr*>(&native_addr), native_addr_len) == 0)
{
return CELL_OK;
}
sys_net_error result = get_last_error(!so_nbio);
#ifdef _WIN32
// See https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
if (was_connecting && (result == SYS_NET_EINVAL || result == SYS_NET_EWOULDBLOCK))
return -SYS_NET_EALREADY;
#endif
if (result)
{
if (result == SYS_NET_EWOULDBLOCK || result == SYS_NET_EINPROGRESS)
{
result = SYS_NET_EINPROGRESS;
#ifdef _WIN32
connecting = true;
#endif
this->poll_queue(null_ptr, lv2_socket::poll_t::write, [this](bs_t<lv2_socket::poll_t> events) -> bool
{
if (events & lv2_socket::poll_t::write)
{
int native_error;
::socklen_t size = sizeof(native_error);
if (::getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&native_error), &size) != 0 || size != sizeof(int))
{
so_error = 1;
}
else
{
// TODO: check error formats (both native and translated)
so_error = native_error ? convert_error(false, native_error) : 0;
}
return true;
}
events += lv2_socket::poll_t::write;
return false;
});
}
return -result;
}
#ifdef _WIN32
connecting = true;
#endif
return std::nullopt;
}
s32 lv2_socket_native::connect_followup()
{
int native_error;
::socklen_t size = sizeof(native_error);
if (::getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&native_error), &size) != 0 || size != sizeof(int))
{
return -1;
}
// TODO: check error formats (both native and translated)
return native_error ? -convert_error(false, native_error) : 0;
}
std::pair<s32, sys_net_sockaddr> lv2_socket_native::getpeername()
{
std::lock_guard lock(mutex);
::sockaddr_storage native_addr;
::socklen_t native_addrlen = sizeof(native_addr);
if (::getpeername(socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen) == 0)
{
ensure(native_addr.ss_family == AF_INET);
sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr);
return {CELL_OK, sn_addr};
}
return {-get_last_error(false), {}};
}
std::pair<s32, sys_net_sockaddr> lv2_socket_native::getsockname()
{
std::lock_guard lock(mutex);
::sockaddr_storage native_addr;
::socklen_t native_addrlen = sizeof(native_addr);
if (::getsockname(socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen) == 0)
{
ensure(native_addr.ss_family == AF_INET);
sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr);
return {CELL_OK, sn_addr};
}
#ifdef _WIN32
else
{
// windows doesn't support getsockname for sockets that are not bound
if (get_native_error() == WSAEINVAL)
{
return {CELL_OK, {}};
}
}
#endif
return {-get_last_error(false), {}};
}
std::tuple<s32, lv2_socket::sockopt_data, u32> lv2_socket_native::getsockopt(s32 level, s32 optname, u32 len)
{
std::lock_guard lock(mutex);
sockopt_data out_val;
u32 out_len = sizeof(out_val);
int native_level = -1;
int native_opt = -1;
union
{
char ch[128];
int _int = 0;
::timeval timeo;
::linger linger;
} native_val;
::socklen_t native_len = sizeof(native_val);
if (level == SYS_NET_SOL_SOCKET)
{
native_level = SOL_SOCKET;
switch (optname)
{
case SYS_NET_SO_NBIO:
{
// Special
out_val._int = so_nbio;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_SO_ERROR:
{
// Special
out_val._int = std::exchange(so_error, 0);
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_SO_KEEPALIVE:
{
native_opt = SO_KEEPALIVE;
break;
}
case SYS_NET_SO_SNDBUF:
{
native_opt = SO_SNDBUF;
break;
}
case SYS_NET_SO_RCVBUF:
{
native_opt = SO_RCVBUF;
break;
}
case SYS_NET_SO_SNDLOWAT:
{
native_opt = SO_SNDLOWAT;
break;
}
case SYS_NET_SO_RCVLOWAT:
{
native_opt = SO_RCVLOWAT;
break;
}
case SYS_NET_SO_BROADCAST:
{
native_opt = SO_BROADCAST;
break;
}
#ifdef _WIN32
case SYS_NET_SO_REUSEADDR:
{
out_val._int = so_reuseaddr;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_SO_REUSEPORT:
{
out_val._int = so_reuseport;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
#else
case SYS_NET_SO_REUSEADDR:
{
native_opt = SO_REUSEADDR;
break;
}
case SYS_NET_SO_REUSEPORT:
{
native_opt = SO_REUSEPORT;
break;
}
#endif
case SYS_NET_SO_SNDTIMEO:
case SYS_NET_SO_RCVTIMEO:
{
if (len < sizeof(sys_net_timeval))
return {-SYS_NET_EINVAL, {}, {}};
native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO;
break;
}
case SYS_NET_SO_LINGER:
{
if (len < sizeof(sys_net_linger))
return {-SYS_NET_EINVAL, {}, {}};
native_opt = SO_LINGER;
break;
}
default:
{
sys_net.error("sys_net_bnet_getsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", lv2_id, optname);
return {-SYS_NET_EINVAL, {}, {}};
}
}
}
else if (level == SYS_NET_IPPROTO_TCP)
{
native_level = IPPROTO_TCP;
switch (optname)
{
case SYS_NET_TCP_MAXSEG:
{
// Special (no effect)
out_val._int = so_tcp_maxseg;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_TCP_NODELAY:
{
native_opt = TCP_NODELAY;
break;
}
default:
{
sys_net.error("sys_net_bnet_getsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", lv2_id, optname);
return {-SYS_NET_EINVAL, {}, {}};
}
}
}
else if (level == SYS_NET_IPPROTO_IP)
{
native_level = IPPROTO_IP;
switch (optname)
{
case SYS_NET_IP_HDRINCL:
{
native_opt = IP_HDRINCL;
break;
}
case SYS_NET_IP_TOS:
{
native_opt = IP_TOS;
break;
}
case SYS_NET_IP_TTL:
{
native_opt = IP_TTL;
break;
}
case SYS_NET_IP_MULTICAST_IF:
{
native_opt = IP_MULTICAST_IF;
break;
}
case SYS_NET_IP_MULTICAST_TTL:
{
native_opt = IP_MULTICAST_TTL;
break;
}
case SYS_NET_IP_MULTICAST_LOOP:
{
native_opt = IP_MULTICAST_LOOP;
break;
}
case SYS_NET_IP_ADD_MEMBERSHIP:
{
native_opt = IP_ADD_MEMBERSHIP;
break;
}
case SYS_NET_IP_DROP_MEMBERSHIP:
{
native_opt = IP_DROP_MEMBERSHIP;
break;
}
case SYS_NET_IP_TTLCHK:
{
sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_TTLCHK): stubbed option");
return {CELL_OK, out_val, out_len};
}
case SYS_NET_IP_MAXTTL:
{
sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_MAXTTL): stubbed option");
return {CELL_OK, out_val, out_len};
}
case SYS_NET_IP_DONTFRAG:
{
#ifdef _WIN32
native_opt = IP_DONTFRAGMENT;
#else
native_opt = IP_DF;
#endif
break;
}
default:
{
sys_net.error("sys_net_bnet_getsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", lv2_id, optname);
return {-SYS_NET_EINVAL, {}, {}};
}
}
}
else
{
sys_net.error("sys_net_bnet_getsockopt(s=%d): unknown level (0x%x)", lv2_id, level);
return {-SYS_NET_EINVAL, {}, {}};
}
if (::getsockopt(socket, native_level, native_opt, native_val.ch, &native_len) != 0)
{
return {-get_last_error(false), {}, {}};
}
if (level == SYS_NET_SOL_SOCKET)
{
switch (optname)
{
case SYS_NET_SO_SNDTIMEO:
case SYS_NET_SO_RCVTIMEO:
{
// TODO
out_val.timeo = {::narrow<s64>(native_val.timeo.tv_sec), ::narrow<s64>(native_val.timeo.tv_usec)};
out_len = sizeof(sys_net_timeval);
return {CELL_OK, out_val, out_len};
}
case SYS_NET_SO_LINGER:
{
// TODO
out_val.linger = {::narrow<s32>(native_val.linger.l_onoff), ::narrow<s32>(native_val.linger.l_linger)};
out_len = sizeof(sys_net_linger);
return {CELL_OK, out_val, out_len};
}
default: break;
}
}
// Fallback to int
out_val._int = native_val._int;
out_len = sizeof(s32);
return {CELL_OK, out_val, out_len};
}
s32 lv2_socket_native::setsockopt(s32 level, s32 optname, const std::vector<u8>& optval)
{
std::lock_guard lock(mutex);
int native_int = 0;
int native_level = -1;
int native_opt = -1;
const void* native_val = &native_int;
::socklen_t native_len = sizeof(int);
::linger native_linger;
::ip_mreq native_mreq;
#ifdef _WIN32
u32 native_timeo;
#else
::timeval native_timeo;
#endif
native_int = *reinterpret_cast<const be_t<s32>*>(optval.data());
if (level == SYS_NET_SOL_SOCKET)
{
native_level = SOL_SOCKET;
switch (optname)
{
case SYS_NET_SO_NBIO:
{
// Special
so_nbio = native_int;
return {};
}
case SYS_NET_SO_KEEPALIVE:
{
native_opt = SO_KEEPALIVE;
break;
}
case SYS_NET_SO_SNDBUF:
{
native_opt = SO_SNDBUF;
break;
}
case SYS_NET_SO_RCVBUF:
{
native_opt = SO_RCVBUF;
break;
}
case SYS_NET_SO_SNDLOWAT:
{
native_opt = SO_SNDLOWAT;
break;
}
case SYS_NET_SO_RCVLOWAT:
{
native_opt = SO_RCVLOWAT;
break;
}
case SYS_NET_SO_BROADCAST:
{
native_opt = SO_BROADCAST;
break;
}
#ifdef _WIN32
case SYS_NET_SO_REUSEADDR:
{
native_opt = SO_REUSEADDR;
so_reuseaddr = native_int;
native_int = so_reuseaddr || so_reuseport ? 1 : 0;
break;
}
case SYS_NET_SO_REUSEPORT:
{
native_opt = SO_REUSEADDR;
so_reuseport = native_int;
native_int = so_reuseaddr || so_reuseport ? 1 : 0;
break;
}
#else
case SYS_NET_SO_REUSEADDR:
{
native_opt = SO_REUSEADDR;
break;
}
case SYS_NET_SO_REUSEPORT:
{
native_opt = SO_REUSEPORT;
break;
}
#endif
case SYS_NET_SO_SNDTIMEO:
case SYS_NET_SO_RCVTIMEO:
{
if (optval.size() < sizeof(sys_net_timeval))
return -SYS_NET_EINVAL;
native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO;
native_val = &native_timeo;
native_len = sizeof(native_timeo);
const int tv_sec = ::narrow<int>(reinterpret_cast<const sys_net_timeval*>(optval.data())->tv_sec);
const int tv_usec = ::narrow<int>(reinterpret_cast<const sys_net_timeval*>(optval.data())->tv_usec);
#ifdef _WIN32
native_timeo = tv_sec * 1000;
native_timeo += tv_usec / 1000;
#else
native_timeo.tv_sec = tv_sec;
native_timeo.tv_usec = tv_usec;
#endif
// TODO: Overflow detection?
(optname == SYS_NET_SO_SNDTIMEO ? so_sendtimeo : so_rcvtimeo) = tv_usec + tv_sec * 1000000;
break;
}
case SYS_NET_SO_LINGER:
{
if (optval.size() < sizeof(sys_net_linger))
return -SYS_NET_EINVAL;
// TODO
native_opt = SO_LINGER;
native_val = &native_linger;
native_len = sizeof(native_linger);
native_linger.l_onoff = reinterpret_cast<const sys_net_linger*>(optval.data())->l_onoff;
native_linger.l_linger = reinterpret_cast<const sys_net_linger*>(optval.data())->l_linger;
break;
}
case SYS_NET_SO_USECRYPTO:
{
// TODO
sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option (0x%x) (SYS_NET_SO_USECRYPTO)", lv2_id, optname);
return {};
}
case SYS_NET_SO_USESIGNATURE:
{
// TODO
sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option (0x%x) (SYS_NET_SO_USESIGNATURE)", lv2_id, optname);
return {};
}
default:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", lv2_id, optname);
return -SYS_NET_EINVAL;
}
}
}
else if (level == SYS_NET_IPPROTO_TCP)
{
native_level = IPPROTO_TCP;
switch (optname)
{
case SYS_NET_TCP_MAXSEG:
{
// Special (no effect)
so_tcp_maxseg = native_int;
return {};
}
case SYS_NET_TCP_NODELAY:
{
native_opt = TCP_NODELAY;
break;
}
default:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", lv2_id, optname);
return -SYS_NET_EINVAL;
}
}
}
else if (level == SYS_NET_IPPROTO_IP)
{
native_level = IPPROTO_IP;
switch (optname)
{
case SYS_NET_IP_HDRINCL:
{
native_opt = IP_HDRINCL;
break;
}
case SYS_NET_IP_TOS:
{
native_opt = IP_TOS;
break;
}
case SYS_NET_IP_TTL:
{
native_opt = IP_TTL;
break;
}
case SYS_NET_IP_MULTICAST_IF:
{
native_opt = IP_MULTICAST_IF;
break;
}
case SYS_NET_IP_MULTICAST_TTL:
{
native_opt = IP_MULTICAST_TTL;
break;
}
case SYS_NET_IP_MULTICAST_LOOP:
{
native_opt = IP_MULTICAST_LOOP;
break;
}
case SYS_NET_IP_ADD_MEMBERSHIP:
case SYS_NET_IP_DROP_MEMBERSHIP:
{
if (optval.size() < sizeof(sys_net_ip_mreq))
return -SYS_NET_EINVAL;
native_opt = optname == SYS_NET_IP_ADD_MEMBERSHIP ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
native_val = &native_mreq;
native_len = sizeof(::ip_mreq);
native_mreq.imr_interface.s_addr = std::bit_cast<u32>(reinterpret_cast<const sys_net_ip_mreq*>(optval.data())->imr_interface);
native_mreq.imr_multiaddr.s_addr = std::bit_cast<u32>(reinterpret_cast<const sys_net_ip_mreq*>(optval.data())->imr_multiaddr);
break;
}
case SYS_NET_IP_TTLCHK:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_TTLCHK)", lv2_id, optname);
break;
}
case SYS_NET_IP_MAXTTL:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_MAXTTL)", lv2_id, optname);
break;
}
case SYS_NET_IP_DONTFRAG:
{
#ifdef _WIN32
native_opt = IP_DONTFRAGMENT;
#else
native_opt = IP_DF;
#endif
break;
}
default:
{
sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", lv2_id, optname);
return -SYS_NET_EINVAL;
}
}
}
else
{
sys_net.error("sys_net_bnet_setsockopt(s=%d): unknown level (0x%x)", lv2_id, level);
return -SYS_NET_EINVAL;
}
if (::setsockopt(socket, native_level, native_opt, static_cast<const char*>(native_val), native_len) == 0)
{
return {};
}
return -get_last_error(false);
}
s32 lv2_socket_native::listen(s32 backlog)
{
std::lock_guard lock(mutex);
if (::listen(socket, backlog) == 0)
{
return CELL_OK;
}
return -get_last_error(false);
}
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> lv2_socket_native::recvfrom(s32 flags, u32 len, bool is_lock)
{
std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);
if (is_lock)
{
lock.lock();
}
if (feign_tcp_conn_failure)
{
// As if just lost the connection
feign_tcp_conn_failure = false;
return {{-SYS_NET_ECONNRESET, {},{}}};
}
int native_flags = 0;
::sockaddr_storage native_addr{};
::socklen_t native_addrlen = sizeof(native_addr);
std::vector<u8> res_buf(len);
auto& dnshook = g_fxo->get<np::dnshook>();
if (dnshook.is_dns(lv2_id) && dnshook.is_dns_queue(lv2_id))
{
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
const auto packet = dnshook.get_dns_packet(lv2_id);
ensure(packet.size() < len);
memcpy(res_buf.data(), packet.data(), packet.size());
native_addr.ss_family = AF_INET;
(reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_port = std::bit_cast<u16, be_t<u16>>(53); // htons(53)
(reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_addr.s_addr = nph.get_dns_ip();
const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
return {{::narrow<s32>(packet.size()), res_buf, sn_addr}};
}
if (flags & SYS_NET_MSG_PEEK)
{
native_flags |= MSG_PEEK;
}
if (flags & SYS_NET_MSG_WAITALL)
{
native_flags |= MSG_WAITALL;
}
auto native_result = ::recvfrom(socket, reinterpret_cast<char*>(res_buf.data()), len, native_flags, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);
if (native_result >= 0)
{
const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
return {{::narrow<s32>(native_result), res_buf, sn_addr}};
}
#ifdef _WIN32
else
{
// Windows returns an error when trying to peek at a message and buffer not long enough to contain the whole message, should be ignored
if ((native_flags & MSG_PEEK) && get_native_error() == WSAEMSGSIZE)
{
const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
return {{len, res_buf, sn_addr}};
}
// Windows will return WSASHUTDOWN when the connection is shutdown, POSIX just returns EOF (0) in this situation.
if (get_native_error() == WSAESHUTDOWN)
{
const auto sn_addr = native_addr_to_sys_net_addr(native_addr);
return {{0, {}, sn_addr}};
}
}
const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting);
#else
const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
#endif
if (result)
{
return {{-result, {}, {}}};
}
return std::nullopt;
}
std::optional<s32> lv2_socket_native::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();
}
int native_flags = 0;
int native_result = -1;
std::optional<sockaddr_in> native_addr = std::nullopt;
if (opt_sn_addr)
{
native_addr = sys_net_addr_to_native_addr(*opt_sn_addr);
sys_net.trace("[Native] Attempting to send to %s:%d", (*native_addr).sin_addr, std::bit_cast<be_t<u16>, u16>((*native_addr).sin_port));
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
if (!nph.get_net_status() && is_ip_public_address(*native_addr))
{
return -SYS_NET_EADDRNOTAVAIL;
}
}
else if (feign_tcp_conn_failure)
{
// As if just lost the connection
feign_tcp_conn_failure = false;
return -SYS_NET_ECONNRESET;
}
sys_net_error result{};
if (flags & SYS_NET_MSG_WAITALL)
{
native_flags |= MSG_WAITALL;
}
auto& dnshook = g_fxo->get<np::dnshook>();
if (opt_sn_addr && type == SYS_NET_SOCK_DGRAM && reinterpret_cast<const sys_net_sockaddr_in*>(&*opt_sn_addr)->sin_port == 53)
{
dnshook.add_dns_spy(lv2_id);
}
if (dnshook.is_dns(lv2_id))
{
const s32 ret_analyzer = dnshook.analyze_dns_packet(lv2_id, reinterpret_cast<const u8*>(buf.data()), ::size32(buf));
// Check if the packet is intercepted
if (ret_analyzer >= 0)
{
return {ret_analyzer};
}
}
native_result = ::sendto(socket, reinterpret_cast<const char*>(buf.data()), ::narrow<int>(buf.size()), native_flags, native_addr ? reinterpret_cast<struct sockaddr*>(&native_addr.value()) : nullptr, native_addr ? sizeof(sockaddr_in) : 0);
if (native_result >= 0)
{
return {native_result};
}
#ifdef _WIN32
result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting);
#else
result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
#endif
if (result)
{
return {-result};
}
// Note that this can only happen if the send buffer is full
return std::nullopt;
}
std::optional<s32> lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock)
{
std::unique_lock<shared_mutex> lock(mutex, std::defer_lock);
if (is_lock)
{
lock.lock();
}
int native_flags = 0;
int native_result = -1;
sys_net_error result{};
if (flags & SYS_NET_MSG_WAITALL)
{
native_flags |= MSG_WAITALL;
}
if (feign_tcp_conn_failure)
{
// As if just lost the connection
feign_tcp_conn_failure = false;
return {-SYS_NET_ECONNRESET};
}
for (int i = 0; i < msg.msg_iovlen; i++)
{
auto iov_base = msg.msg_iov[i].iov_base;
const u32 len = msg.msg_iov[i].iov_len;
const std::vector<u8> buf_copy(vm::_ptr<const char>(iov_base.addr()), vm::_ptr<const char>(iov_base.addr()) + len);
native_result = ::send(socket, reinterpret_cast<const char*>(buf_copy.data()), ::narrow<int>(buf_copy.size()), native_flags);
if (native_result >= 0)
{
return {native_result};
}
}
result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
if (result)
{
return {-result};
}
return std::nullopt;
}
void lv2_socket_native::close()
{
std::lock_guard lock(mutex);
if (socket)
{
#ifdef _WIN32
::closesocket(socket);
#else
::close(socket);
#endif
socket = {};
}
auto& dnshook = g_fxo->get<np::dnshook>();
dnshook.remove_dns_spy(lv2_id);
if (bound_port)
{
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
nph.upnp_remove_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP");
bound_port = 0;
}
}
s32 lv2_socket_native::shutdown(s32 how)
{
std::lock_guard lock(mutex);
if (feign_tcp_conn_failure)
{
// As if still connected
return CELL_OK;
}
#ifdef _WIN32
const int native_how =
how == SYS_NET_SHUT_RD ? SD_RECEIVE :
how == SYS_NET_SHUT_WR ? SD_SEND :
SD_BOTH;
#else
const int native_how =
how == SYS_NET_SHUT_RD ? SHUT_RD :
how == SYS_NET_SHUT_WR ? SHUT_WR :
SHUT_RDWR;
#endif
if (::shutdown(socket, native_how) == 0)
{
return CELL_OK;
}
return -get_last_error(false);
}
s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd)
{
// Check for fake packet for dns interceptions
auto& dnshook = g_fxo->get<np::dnshook>();
if (sn_pfd.events & SYS_NET_POLLIN && dnshook.is_dns(sn_pfd.fd) && dnshook.is_dns_queue(sn_pfd.fd))
{
sn_pfd.revents |= SYS_NET_POLLIN;
return 1;
}
if (sn_pfd.events & ~(SYS_NET_POLLIN | SYS_NET_POLLOUT | SYS_NET_POLLERR))
{
sys_net.warning("sys_net_bnet_poll(fd=%d): events=0x%x", sn_pfd.fd, sn_pfd.events);
}
native_pfd.fd = socket;
if (sn_pfd.events & SYS_NET_POLLIN)
{
native_pfd.events |= POLLIN;
}
if (sn_pfd.events & SYS_NET_POLLOUT)
{
native_pfd.events |= POLLOUT;
}
return 0;
}
std::tuple<bool, bool, bool> lv2_socket_native::select(bs_t<lv2_socket::poll_t> selected, pollfd& native_pfd)
{
native_pfd.fd = socket;
if (selected & lv2_socket::poll_t::read)
{
native_pfd.events |= POLLIN;
}
if (selected & lv2_socket::poll_t::write)
{
native_pfd.events |= POLLOUT;
}
return {};
}
void lv2_socket_native::set_default_buffers()
{
// Those are the default PS3 values
u32 default_RCVBUF = (type == SYS_NET_SOCK_STREAM) ? 65535 : 9216;
if (::setsockopt(socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&default_RCVBUF), sizeof(default_RCVBUF)) != 0)
{
sys_net.error("Error setting default SO_RCVBUF on sys_net_bnet_socket socket");
}
u32 default_SNDBUF = 131072;
if (::setsockopt(socket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&default_SNDBUF), sizeof(default_SNDBUF)) != 0)
{
sys_net.error("Error setting default SO_SNDBUF on sys_net_bnet_socket socket");
}
}
void lv2_socket_native::set_non_blocking()
{
// Set non-blocking
// This is done to avoid having threads stuck on blocking socket functions
// Blocking functions just put the thread to sleep and delegate the waking up to network_thread which polls the sockets
#ifdef _WIN32
u_long _true = 1;
::ioctlsocket(socket, FIONBIO, &_true);
#else
::fcntl(socket, F_SETFL, ::fcntl(socket, F_GETFL, 0) | O_NONBLOCK);
#endif
}
bool lv2_socket_native::is_socket_connected()
{
if (type != SYS_NET_SOCK_STREAM)
{
return false;
}
std::lock_guard lock(mutex);
int listening = 0;
socklen_t len = sizeof(listening);
if (::getsockopt(socket, SOL_SOCKET, SO_ACCEPTCONN, reinterpret_cast<char*>(&listening), &len) == -1)
{
return false;
}
if (listening)
{
// Would be handled in other ways
return false;
}
fd_set readfds, writefds;
struct timeval timeout{0, 0}; // Zero timeout
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(socket, &readfds);
FD_SET(socket, &writefds);
// Use select to check for readability and writability
const int result = ::select(1, &readfds, &writefds, NULL, &timeout);
if (result < 0)
{
// Error occurred
return false;
}
// Socket is connected if it's readable or writable
return FD_ISSET(socket, &readfds) || FD_ISSET(socket, &writefds);
}