mirror of
https://github.com/RPCSX/rpcsx.git
synced 2025-12-06 07:12:14 +01:00
Simplify signaling by making Matching2 a layer over normal signaling. Implements UPNP port forwarding Implement sceNpMatching2AbortRequest Fix reported bw in sceNpUtil Hack for Fat Princess binding udp on 3658 Reenable CB for sceNpBasicAddPlayersHistoryAsync Misc fixes
1150 lines
27 KiB
C++
1150 lines
27 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"
|
|
|
|
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(ar, type)
|
|
{
|
|
#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
|
|
}
|
|
|
|
void lv2_socket_native::save(utils::serial& ar)
|
|
{
|
|
static_cast<lv2_socket*>(this)->save(ar, true);
|
|
#ifdef _WIN32
|
|
ar(so_reuseaddr, so_reuseport);
|
|
#else
|
|
ar(std::array<char, 8>{});
|
|
#endif
|
|
}
|
|
|
|
lv2_socket_native::~lv2_socket_native()
|
|
{
|
|
std::lock_guard lock(mutex);
|
|
if (socket)
|
|
{
|
|
#ifdef _WIN32
|
|
::closesocket(socket);
|
|
#else
|
|
::close(socket);
|
|
#endif
|
|
}
|
|
|
|
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::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 == -1)
|
|
{
|
|
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, std::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);
|
|
|
|
socket_type native_socket = ::accept(socket, reinterpret_cast<struct sockaddr*>(&native_addr), &native_addrlen);
|
|
|
|
if (native_socket != -1)
|
|
{
|
|
auto newsock = std::make_shared<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);
|
|
}
|
|
|
|
::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;
|
|
}
|
|
return -get_last_error(false);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
if (result)
|
|
{
|
|
if (result == SYS_NET_EWOULDBLOCK || result == SYS_NET_EINPROGRESS)
|
|
{
|
|
result = SYS_NET_EINPROGRESS;
|
|
#ifdef _WIN32
|
|
connecting = true;
|
|
#endif
|
|
this->poll_queue(nullptr, 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 ? get_last_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 ? -get_last_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();
|
|
}
|
|
|
|
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}};
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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()), buf.size());
|
|
|
|
// Check if the packet is intercepted
|
|
if (ret_analyzer >= 0)
|
|
{
|
|
return {ret_analyzer};
|
|
}
|
|
}
|
|
|
|
native_result = ::sendto(socket, reinterpret_cast<const char*>(buf.data()), buf.size(), native_flags, native_addr ? reinterpret_cast<struct sockaddr*>(&(*native_addr)) : nullptr, native_addr ? sizeof(*native_addr) : 0);
|
|
|
|
if (native_result >= 0)
|
|
{
|
|
return {native_result};
|
|
}
|
|
|
|
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_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;
|
|
}
|
|
|
|
|
|
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()), 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);
|
|
|
|
#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
|
|
}
|