mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-02 13:01:27 +12:00
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.
1271 lines
30 KiB
C++
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);
|
|
}
|