#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 dummy{}; ar(dummy); if (dummy != std::array{}) { 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(this)->save(ar, true); #ifdef _WIN32 ar(so_reuseaddr, so_reuseport); #else ar(std::array{}); #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>(); 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); #ifdef _WIN32 if (socket_res == INVALID_SOCKET) #else if (socket_res == -1) #endif { 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, sys_net_sockaddr> lv2_socket_native::accept(bool is_lock) { std::unique_lock 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(&native_addr), &native_addrlen); if (native_socket != -1) { auto newsock = std::make_shared(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(&addr); auto& nph = g_fxo->get>(); u32 saddr = nph.get_bind_ip(); if (saddr == 0) { // If zero use the supplied address saddr = std::bit_cast(psa_in->sin_addr); } ::sockaddr_in native_addr{}; native_addr.sin_family = AF_INET; native_addr.sin_port = std::bit_cast(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>(3659); } sys_net.warning("[Native] Trying to bind %s:%d", native_addr.sin_addr, std::bit_cast, u16>(native_addr.sin_port)); if (::bind(socket, reinterpret_cast(&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(&client_addr), &client_addr_size) == 0); bound_port = std::bit_cast>(client_addr.sin_port); } else { bound_port = std::bit_cast>(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 lv2_socket_native::connect(const sys_net_sockaddr& addr) { std::lock_guard lock(mutex); const auto* psa_in = reinterpret_cast(&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, u16>(native_addr.sin_port)); auto& nph = g_fxo->get>(); 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(); dnshook.add_dns_spy(lv2_id); } #ifdef _WIN32 bool was_connecting = connecting; #endif if (::connect(socket, reinterpret_cast(&native_addr), native_addr_len) == 0) { return CELL_OK; } #ifdef _WIN32 sys_net_error result = get_last_error(!so_nbio, was_connecting); #else sys_net_error result = get_last_error(!so_nbio); #endif 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 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(&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(&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 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(&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 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(&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 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(native_val.timeo.tv_sec), ::narrow(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(native_val.linger.l_onoff), ::narrow(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& 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*>(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(reinterpret_cast(optval.data())->tv_sec); const int tv_usec = ::narrow(reinterpret_cast(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(optval.data())->l_onoff; native_linger.l_linger = reinterpret_cast(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(reinterpret_cast(optval.data())->imr_interface); native_mreq.imr_multiaddr.s_addr = std::bit_cast(reinterpret_cast(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(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, sys_net_sockaddr>> lv2_socket_native::recvfrom(s32 flags, u32 len, bool is_lock) { std::unique_lock 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 res_buf(len); auto& dnshook = g_fxo->get(); if (dnshook.is_dns(lv2_id) && dnshook.is_dns_queue(lv2_id)) { auto& nph = g_fxo->get>(); 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>(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(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(res_buf.data()), len, native_flags, reinterpret_cast(&native_addr), &native_addrlen); if (native_result >= 0) { const auto sn_addr = native_addr_to_sys_net_addr(native_addr); return {{::narrow(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 lv2_socket_native::sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock) { std::unique_lock lock(mutex, std::defer_lock); if (is_lock) { lock.lock(); } int native_flags = 0; int native_result = -1; std::optional 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, u16>((*native_addr).sin_port)); auto& nph = g_fxo->get>(); 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(); if (opt_sn_addr && type == SYS_NET_SOCK_DGRAM && reinterpret_cast(&*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(buf.data()), buf.size()); // Check if the packet is intercepted if (ret_analyzer >= 0) { return {ret_analyzer}; } } native_result = ::sendto(socket, reinterpret_cast(buf.data()), buf.size(), native_flags, native_addr ? reinterpret_cast(&(*native_addr)) : nullptr, native_addr ? sizeof(*native_addr) : 0); if (native_result >= 0) { return {native_result}; } #ifdef _WIN32 get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting); #else 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 lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock) { std::unique_lock 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 buf_copy(vm::_ptr(iov_base.addr()), vm::_ptr(iov_base.addr()) + len); native_result = ::send(socket, reinterpret_cast(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(); dnshook.remove_dns_spy(lv2_id); if (bound_port) { auto& nph = g_fxo->get>(); 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(); 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 lv2_socket_native::select(bs_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(&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(&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 }