#include "stdafx.h" #include "sys_net.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUThread.h" #include "Utilities/Thread.h" #include "sys_sync.h" #ifdef _WIN32 #include #include #else #ifdef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef __clang__ #pragma GCC diagnostic pop #endif #endif #include "Emu/NP/np_handler.h" #include "Emu/NP/np_helpers.h" #include "Emu/NP/np_dnshook.h" #include #include #include "sys_net/network_context.h" #include "sys_net/lv2_socket.h" #include "sys_net/lv2_socket_native.h" #include "sys_net/lv2_socket_raw.h" #include "sys_net/lv2_socket_p2p.h" #include "sys_net/lv2_socket_p2ps.h" #include "sys_net/sys_net_helpers.h" LOG_CHANNEL(sys_net); LOG_CHANNEL(sys_net_dump); template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto error) { switch (s32 _error = error) { #define SYS_NET_ERROR_CASE(x) \ case -x: return "-" #x; \ case x: \ return #x SYS_NET_ERROR_CASE(SYS_NET_ENOENT); SYS_NET_ERROR_CASE(SYS_NET_EINTR); SYS_NET_ERROR_CASE(SYS_NET_EBADF); SYS_NET_ERROR_CASE(SYS_NET_ENOMEM); SYS_NET_ERROR_CASE(SYS_NET_EACCES); SYS_NET_ERROR_CASE(SYS_NET_EFAULT); SYS_NET_ERROR_CASE(SYS_NET_EBUSY); SYS_NET_ERROR_CASE(SYS_NET_EINVAL); SYS_NET_ERROR_CASE(SYS_NET_EMFILE); SYS_NET_ERROR_CASE(SYS_NET_ENOSPC); SYS_NET_ERROR_CASE(SYS_NET_EPIPE); SYS_NET_ERROR_CASE(SYS_NET_EAGAIN); static_assert(SYS_NET_EWOULDBLOCK == SYS_NET_EAGAIN); SYS_NET_ERROR_CASE(SYS_NET_EINPROGRESS); SYS_NET_ERROR_CASE(SYS_NET_EALREADY); SYS_NET_ERROR_CASE(SYS_NET_EDESTADDRREQ); SYS_NET_ERROR_CASE(SYS_NET_EMSGSIZE); SYS_NET_ERROR_CASE(SYS_NET_EPROTOTYPE); SYS_NET_ERROR_CASE(SYS_NET_ENOPROTOOPT); SYS_NET_ERROR_CASE(SYS_NET_EPROTONOSUPPORT); SYS_NET_ERROR_CASE(SYS_NET_EOPNOTSUPP); SYS_NET_ERROR_CASE(SYS_NET_EPFNOSUPPORT); SYS_NET_ERROR_CASE(SYS_NET_EAFNOSUPPORT); SYS_NET_ERROR_CASE(SYS_NET_EADDRINUSE); SYS_NET_ERROR_CASE(SYS_NET_EADDRNOTAVAIL); SYS_NET_ERROR_CASE(SYS_NET_ENETDOWN); SYS_NET_ERROR_CASE(SYS_NET_ENETUNREACH); SYS_NET_ERROR_CASE(SYS_NET_ECONNABORTED); SYS_NET_ERROR_CASE(SYS_NET_ECONNRESET); SYS_NET_ERROR_CASE(SYS_NET_ENOBUFS); SYS_NET_ERROR_CASE(SYS_NET_EISCONN); SYS_NET_ERROR_CASE(SYS_NET_ENOTCONN); SYS_NET_ERROR_CASE(SYS_NET_ESHUTDOWN); SYS_NET_ERROR_CASE(SYS_NET_ETOOMANYREFS); SYS_NET_ERROR_CASE(SYS_NET_ETIMEDOUT); SYS_NET_ERROR_CASE(SYS_NET_ECONNREFUSED); SYS_NET_ERROR_CASE(SYS_NET_EHOSTDOWN); SYS_NET_ERROR_CASE(SYS_NET_EHOSTUNREACH); #undef SYS_NET_ERROR_CASE } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case SYS_NET_SOCK_STREAM: return "STREAM"; case SYS_NET_SOCK_DGRAM: return "DGRAM"; case SYS_NET_SOCK_RAW: return "RAW"; case SYS_NET_SOCK_DGRAM_P2P: return "DGRAM-P2P"; case SYS_NET_SOCK_STREAM_P2P: return "STREAM-P2P"; } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case SYS_NET_AF_UNSPEC: return "UNSPEC"; case SYS_NET_AF_LOCAL: return "LOCAL"; case SYS_NET_AF_INET: return "INET"; case SYS_NET_AF_INET6: return "INET6"; } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case SYS_NET_IPPROTO_IP: return "IPPROTO_IP"; case SYS_NET_IPPROTO_ICMP: return "IPPROTO_ICMP"; case SYS_NET_IPPROTO_IGMP: return "IPPROTO_IGMP"; case SYS_NET_IPPROTO_TCP: return "IPPROTO_TCP"; case SYS_NET_IPPROTO_UDP: return "IPPROTO_UDP"; case SYS_NET_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6"; } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case SYS_NET_TCP_NODELAY: return "TCP_NODELAY"; case SYS_NET_TCP_MAXSEG: return "TCP_MAXSEG"; case SYS_NET_TCP_MSS_TO_ADVERTISE: return "TCP_MSS_TO_ADVERTISE"; } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case SYS_NET_SO_SNDBUF: return "SO_SNDBUF"; case SYS_NET_SO_RCVBUF: return "SO_RCVBUF"; case SYS_NET_SO_SNDLOWAT: return "SO_SNDLOWAT"; case SYS_NET_SO_RCVLOWAT: return "SO_RCVLOWAT"; case SYS_NET_SO_SNDTIMEO: return "SO_SNDTIMEO"; case SYS_NET_SO_RCVTIMEO: return "SO_RCVTIMEO"; case SYS_NET_SO_ERROR: return "SO_ERROR"; case SYS_NET_SO_TYPE: return "SO_TYPE"; case SYS_NET_SO_NBIO: return "SO_NBIO"; case SYS_NET_SO_TPPOLICY: return "SO_TPPOLICY"; case SYS_NET_SO_REUSEADDR: return "SO_REUSEADDR"; case SYS_NET_SO_KEEPALIVE: return "SO_KEEPALIVE"; case SYS_NET_SO_BROADCAST: return "SO_BROADCAST"; case SYS_NET_SO_LINGER: return "SO_LINGER"; case SYS_NET_SO_OOBINLINE: return "SO_OOBINLINE"; case SYS_NET_SO_REUSEPORT: return "SO_REUSEPORT"; case SYS_NET_SO_ONESBCAST: return "SO_ONESBCAST"; case SYS_NET_SO_USECRYPTO: return "SO_USECRYPTO"; case SYS_NET_SO_USESIGNATURE: return "SO_USESIGNATURE"; case SYS_NET_SOL_SOCKET: return "SOL_SOCKET"; } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto value) { switch (value) { case SYS_NET_IP_HDRINCL: return "IP_HDRINCL"; case SYS_NET_IP_TOS: return "IP_TOS"; case SYS_NET_IP_TTL: return "IP_TTL"; case SYS_NET_IP_MULTICAST_IF: return "IP_MULTICAST_IF"; case SYS_NET_IP_MULTICAST_TTL: return "IP_MULTICAST_TTL"; case SYS_NET_IP_MULTICAST_LOOP: return "IP_MULTICAST_LOOP"; case SYS_NET_IP_ADD_MEMBERSHIP: return "IP_ADD_MEMBERSHIP"; case SYS_NET_IP_DROP_MEMBERSHIP: return "IP_DROP_MEMBERSHIP"; case SYS_NET_IP_TTLCHK: return "IP_TTLCHK"; case SYS_NET_IP_MAXTTL: return "IP_MAXTTL"; case SYS_NET_IP_DONTFRAG: return "IP_DONTFRAG"; } return unknown; }); } template <> void fmt_class_string::format(std::string& out, u64 arg) { const u8* data = reinterpret_cast(&get_object(arg)); fmt::append(out, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]); } lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type) : family(ar) , type(_type) , protocol(ar) , so_nbio(ar) , so_error(ar) , so_tcp_maxseg(ar) #ifdef _WIN32 , so_reuseaddr(ar) , so_reuseport(ar) { #else { // Try to match structure between different platforms ar.pos += 8; #endif [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net); ar(so_rcvtimeo, so_sendtimeo); lv2_id = idm::last_id(); ar(last_bound_addr); } std::shared_ptr lv2_socket::load(utils::serial& ar) { const lv2_socket_type type{ar}; std::shared_ptr sock_lv2; switch (type) { case SYS_NET_SOCK_STREAM: case SYS_NET_SOCK_DGRAM: { auto lv2_native = std::make_shared(ar, type); ensure(lv2_native->create_socket() >= 0); sock_lv2 = std::move(lv2_native); break; } case SYS_NET_SOCK_RAW: sock_lv2 = std::make_shared(ar, type); break; case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = std::make_shared(ar, type); break; case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = std::make_shared(ar, type); break; } if (std::memcmp(&sock_lv2->last_bound_addr, std::array{}.data(), 16)) { // NOTE: It is allowed fail sock_lv2->bind(sock_lv2->last_bound_addr); } return sock_lv2; } void lv2_socket::save(utils::serial& ar, bool save_only_this_class) { USING_SERIALIZATION_VERSION(lv2_net); if (save_only_this_class) { ar(family, protocol, so_nbio, so_error, so_tcp_maxseg); #ifdef _WIN32 ar(so_reuseaddr, so_reuseport); #else ar(std::array{}); #endif ar(so_rcvtimeo, so_sendtimeo); ar(last_bound_addr); return; } ar(type); switch (type) { case SYS_NET_SOCK_STREAM: case SYS_NET_SOCK_DGRAM: { static_cast(this)->save(ar); break; } case SYS_NET_SOCK_RAW: static_cast(this)->save(ar); break; case SYS_NET_SOCK_DGRAM_P2P: static_cast(this)->save(ar); break; case SYS_NET_SOCK_STREAM_P2P: static_cast(this)->save(ar); break; } } void sys_net_dump_data(std::string_view desc, const u8* data, s32 len, const void* addr) { const sys_net_sockaddr_in_p2p* p2p_addr = reinterpret_cast(addr); if (p2p_addr) sys_net_dump.trace("%s(%s:%d:%d): %s", desc, np::ip_to_string(std::bit_cast(p2p_addr->sin_addr)), p2p_addr->sin_port, p2p_addr->sin_vport, fmt::buf_to_hexstring(data, len)); else sys_net_dump.trace("%s: %s", desc, fmt::buf_to_hexstring(data, len)); } error_code sys_net_bnet_accept(ppu_thread& ppu, s32 s, vm::ptr addr, vm::ptr paddrlen) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_accept(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); if (addr.operator bool() != paddrlen.operator bool() || (paddrlen && *paddrlen < addr.size())) { return -SYS_NET_EINVAL; } s32 result = 0; sys_net_sockaddr sn_addr{}; std::shared_ptr new_socket{}; const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) { auto [success, res, res_socket, res_addr] = sock.accept(); if (success) { result = res; sn_addr = res_addr; new_socket = std::move(res_socket); return true; } auto lock = sock.lock(); sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::read, [&](bs_t events) -> bool { if (events & lv2_socket::poll_t::read) { auto [success, res, res_socket, res_addr] = sock.accept(false); if (success) { result = res; sn_addr = res_addr; new_socket = std::move(res_socket); lv2_obj::awake(&ppu); return success; } } sock.set_poll_event(lv2_socket::poll_t::read); return false; }); lv2_obj::prepare_for_sleep(ppu); lv2_obj::sleep(ppu); return false; }); if (!sock) { return -SYS_NET_EBADF; } if (!sock.ret) { while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { if (is_stopped(state)) { return {}; } if (state & cpu_flag::signal) { break; } ppu.state.wait(state); } if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { return -SYS_NET_EINTR; } if (result < 0) { return sys_net_error{result}; } } if (result < 0) { return sys_net_error{result}; } s32 id_ps3 = result; if (!id_ps3) { ensure(new_socket); id_ps3 = idm::import_existing(new_socket); if (id_ps3 == id_manager::id_traits::invalid) { return -SYS_NET_EMFILE; } } static_cast(ppu.test_stopped()); if (addr) { *paddrlen = sizeof(sys_net_sockaddr_in); *addr = sn_addr; } // Socket ID return not_an_error(id_ps3); } error_code sys_net_bnet_bind(ppu_thread& ppu, s32 s, vm::cptr addr, u32 addrlen) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_bind(s=%d, addr=*0x%x, addrlen=%u)", s, addr, addrlen); if (!addr || addrlen < addr.size()) { return -SYS_NET_EINVAL; } if (!idm::check(s)) { return -SYS_NET_EBADF; } const sys_net_sockaddr sn_addr = *addr; // 0 presumably defaults to AF_INET(to check?) if (sn_addr.sa_family != SYS_NET_AF_INET && sn_addr.sa_family != SYS_NET_AF_UNSPEC) { sys_net.error("sys_net_bnet_bind: unsupported sa_family (%d)", sn_addr.sa_family); return -SYS_NET_EAFNOSUPPORT; } const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { return sock.bind(sn_addr); }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret) { return sys_net_error{sock.ret}; } return CELL_OK; } error_code sys_net_bnet_connect(ppu_thread& ppu, s32 s, vm::ptr addr, u32 addrlen) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_connect(s=%d, addr=*0x%x, addrlen=%u)", s, addr, addrlen); if (!addr || addrlen < addr.size()) { return -SYS_NET_EINVAL; } if (addr->sa_family != SYS_NET_AF_INET) { sys_net.error("sys_net_bnet_connect(s=%d): unsupported sa_family (%d)", s, addr->sa_family); return -SYS_NET_EAFNOSUPPORT; } if (!idm::check(s)) { return -SYS_NET_EBADF; } s32 result = 0; sys_net_sockaddr sn_addr = *addr; const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) { const auto success = sock.connect(sn_addr); if (success) { result = *success; return true; } auto lock = sock.lock(); sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::write, [&](bs_t events) -> bool { if (events & lv2_socket::poll_t::write) { result = sock.connect_followup(); lv2_obj::awake(&ppu); return true; } sock.set_poll_event(lv2_socket::poll_t::write); return false; }); lv2_obj::prepare_for_sleep(ppu); lv2_obj::sleep(ppu); return false; }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret) { if (result < 0) { return sys_net_error{result}; } return not_an_error(result); } if (!sock.ret) { while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { if (is_stopped(state)) { return {}; } if (state & cpu_flag::signal) { break; } ppu.state.wait(state); } if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { return -SYS_NET_EINTR; } if (result) { if (result < 0) { return sys_net_error{result}; } return not_an_error(result); } } return CELL_OK; } error_code sys_net_bnet_getpeername(ppu_thread& ppu, s32 s, vm::ptr addr, vm::ptr paddrlen) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_getpeername(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); // Note: paddrlen is both an input and output argument if (!addr || !paddrlen || *paddrlen < addr.size()) { return -SYS_NET_EINVAL; } const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { auto [res, sn_addr] = sock.getpeername(); if (res == CELL_OK) { *paddrlen = sizeof(sys_net_sockaddr); *addr = sn_addr; } return res; }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret < 0) { return sys_net_error{sock.ret}; } return CELL_OK; } error_code sys_net_bnet_getsockname(ppu_thread& ppu, s32 s, vm::ptr addr, vm::ptr paddrlen) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_getsockname(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); // Note: paddrlen is both an input and output argument if (!addr || !paddrlen || *paddrlen < addr.size()) { return -SYS_NET_EINVAL; } const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { auto [res, sn_addr] = sock.getsockname(); if (res == CELL_OK) { *paddrlen = sizeof(sys_net_sockaddr); *addr = sn_addr; } return res; }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret < 0) { return sys_net_error{sock.ret}; } return CELL_OK; } error_code sys_net_bnet_getsockopt(ppu_thread& ppu, s32 s, s32 level, s32 optname, vm::ptr optval, vm::ptr optlen) { ppu.state += cpu_flag::wait; switch (level) { case SYS_NET_SOL_SOCKET: sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_SOL_SOCKET, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); break; case SYS_NET_IPPROTO_TCP: sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); break; case SYS_NET_IPPROTO_IP: sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_IP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); break; default: sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=0x%x, optname=0x%x, optval=*0x%x, optlen=%u)", s, level, optname, optval, optlen); break; } if (!optval || !optlen) { return -SYS_NET_EINVAL; } const u32 len = *optlen; if (!len) { return -SYS_NET_EINVAL; } const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { if (len < sizeof(s32)) { return -SYS_NET_EINVAL; } const auto& [res, out_val, out_len] = sock.getsockopt(level, optname, *optlen); if (res == CELL_OK) { std::memcpy(optval.get_ptr(), out_val.ch, out_len); *optlen = out_len; } return res; }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret < 0) { return sys_net_error{sock.ret}; } return CELL_OK; } error_code sys_net_bnet_listen(ppu_thread& ppu, s32 s, s32 backlog) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_listen(s=%d, backlog=%d)", s, backlog); if (backlog <= 0) { return -SYS_NET_EINVAL; } const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { return sock.listen(backlog); }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret < 0) { return sys_net_error{sock.ret}; } return CELL_OK; } error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr buf, u32 len, s32 flags, vm::ptr addr, vm::ptr paddrlen) { ppu.state += cpu_flag::wait; sys_net.trace("sys_net_bnet_recvfrom(s=%d, buf=*0x%x, len=%u, flags=0x%x, addr=*0x%x, paddrlen=*0x%x)", s, buf, len, flags, addr, paddrlen); // If addr is null, paddrlen must be null as well if (!buf || !len || addr.operator bool() != paddrlen.operator bool()) { return -SYS_NET_EINVAL; } if (flags & ~(SYS_NET_MSG_PEEK | SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) { fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", flags); } s32 result = 0; sys_net_sockaddr sn_addr{}; const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) { const auto success = sock.recvfrom(flags, len); if (success) { const auto& [res, vec, res_addr] = *success; if (res > 0) { sn_addr = res_addr; std::memcpy(buf.get_ptr(), vec.data(), res); sys_net_dump_data("recvfrom", vec.data(), res, &res_addr); } result = res; return true; } auto lock = sock.lock(); sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::read, [&](bs_t events) -> bool { if (events & lv2_socket::poll_t::read) { const auto success = sock.recvfrom(flags, len, false); if (success) { const auto& [res, vec, res_addr] = *success; if (res > 0) { sn_addr = res_addr; std::memcpy(buf.get_ptr(), vec.data(), res); sys_net_dump_data("recvfrom", vec.data(), res, &res_addr); } result = res; lv2_obj::awake(&ppu); return true; } } if (sock.so_rcvtimeo && get_guest_system_time() - ppu.start_time > sock.so_rcvtimeo) { result = -SYS_NET_EWOULDBLOCK; lv2_obj::awake(&ppu); return true; } sock.set_poll_event(lv2_socket::poll_t::read); return false; }); lv2_obj::prepare_for_sleep(ppu); lv2_obj::sleep(ppu); return false; }); if (!sock) { return -SYS_NET_EBADF; } if (!sock.ret) { while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { if (is_stopped(state)) { return {}; } if (state & cpu_flag::signal) { break; } ppu.state.wait(state); } if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { return -SYS_NET_EINTR; } } static_cast(ppu.test_stopped()); if (result == -SYS_NET_EWOULDBLOCK) { return not_an_error(result); } if (result >= 0) { if (addr) { *paddrlen = sizeof(sys_net_sockaddr_in); *addr = sn_addr; } return not_an_error(result); } return sys_net_error{result}; } error_code sys_net_bnet_recvmsg(ppu_thread& ppu, s32 s, vm::ptr msg, s32 flags) { ppu.state += cpu_flag::wait; sys_net.todo("sys_net_bnet_recvmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, flags); return CELL_OK; } error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr msg, s32 flags) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_sendmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, flags); if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) { fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", flags); } s32 result{}; const auto sock = idm::check(s, [&](lv2_socket& sock) { auto netmsg = msg.get_ptr(); const auto success = sock.sendmsg(flags, *netmsg); if (success) { result = *success; return true; } sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::write, [&](bs_t events) -> bool { if (events & lv2_socket::poll_t::write) { const auto success = sock.sendmsg(flags, *netmsg, false); if (success) { result = *success; lv2_obj::awake(&ppu); return true; } } sock.set_poll_event(lv2_socket::poll_t::write); return false; }); lv2_obj::sleep(ppu); return false; }); if (!sock) { return -SYS_NET_EBADF; } if (!sock.ret) { while (true) { const auto state = ppu.state.fetch_sub(cpu_flag::signal); if (is_stopped(state) || state & cpu_flag::signal) { break; } thread_ctrl::wait_on(ppu.state, state); } if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { return -SYS_NET_EINTR; } } if (result >= 0 || result == -SYS_NET_EWOULDBLOCK) { return not_an_error(result); } return sys_net_error{result}; } error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr buf, u32 len, s32 flags, vm::cptr addr, u32 addrlen) { ppu.state += cpu_flag::wait; sys_net.trace("sys_net_bnet_sendto(s=%d, buf=*0x%x, len=%u, flags=0x%x, addr=*0x%x, addrlen=%u)", s, buf, len, flags, addr, addrlen); if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) { fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags); } if (addr && addrlen < 8) { sys_net.error("sys_net_bnet_sendto(s=%d): bad addrlen (%u)", s, addrlen); return -SYS_NET_EINVAL; } if (addr && addr->sa_family != SYS_NET_AF_INET) { sys_net.error("sys_net_bnet_sendto(s=%d): unsupported sa_family (%d)", s, addr->sa_family); return -SYS_NET_EAFNOSUPPORT; } sys_net_dump_data("sendto", static_cast(buf.get_ptr()), len, addr ? addr.get_ptr() : nullptr); const std::optional sn_addr = addr ? std::optional(*addr) : std::nullopt; const std::vector buf_copy(vm::_ptr(buf.addr()), vm::_ptr(buf.addr()) + len); s32 result{}; const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) { auto success = sock.sendto(flags, buf_copy, sn_addr); if (success) { result = *success; return true; } auto lock = sock.lock(); // Enable write event sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::write, [&](bs_t events) -> bool { if (events & lv2_socket::poll_t::write) { auto success = sock.sendto(flags, buf_copy, sn_addr, false); if (success) { result = *success; lv2_obj::awake(&ppu); return true; } } if (sock.so_sendtimeo && get_guest_system_time() - ppu.start_time > sock.so_sendtimeo) { result = -SYS_NET_EWOULDBLOCK; lv2_obj::awake(&ppu); return true; } sock.set_poll_event(lv2_socket::poll_t::write); return false; }); lv2_obj::prepare_for_sleep(ppu); lv2_obj::sleep(ppu); return false; }); if (!sock) { return -SYS_NET_EBADF; } if (!sock.ret) { while (true) { const auto state = ppu.state.fetch_sub(cpu_flag::signal); if (is_stopped(state) || state & cpu_flag::signal) { break; } ppu.state.wait(state); } if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { return -SYS_NET_EINTR; } } if (result >= 0 || result == -SYS_NET_EWOULDBLOCK) { return not_an_error(result); } return sys_net_error{result}; } error_code sys_net_bnet_setsockopt(ppu_thread& ppu, s32 s, s32 level, s32 optname, vm::cptr optval, u32 optlen) { ppu.state += cpu_flag::wait; switch (level) { case SYS_NET_SOL_SOCKET: sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_SOL_SOCKET, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); break; case SYS_NET_IPPROTO_TCP: sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); break; case SYS_NET_IPPROTO_IP: sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_IP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); break; default: sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=0x%x, optname=0x%x, optval=*0x%x, optlen=%u)", s, level, optname, optval, optlen); break; } switch (optlen) { case 1: sys_net.warning("optval: 0x%02X", *static_cast(optval.get_ptr())); break; case 2: sys_net.warning("optval: 0x%04X", *static_cast*>(optval.get_ptr())); break; case 4: sys_net.warning("optval: 0x%08X", *static_cast*>(optval.get_ptr())); break; case 8: sys_net.warning("optval: 0x%016X", *static_cast*>(optval.get_ptr())); break; } if (optlen < sizeof(s32)) { return -SYS_NET_EINVAL; } std::vector optval_copy(vm::_ptr(optval.addr()), vm::_ptr(optval.addr() + optlen)); const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { return sock.setsockopt(level, optname, optval_copy); }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret < 0) { return sys_net_error{sock.ret}; } return not_an_error(sock.ret); } error_code sys_net_bnet_shutdown(ppu_thread& ppu, s32 s, s32 how) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_shutdown(s=%d, how=%d)", s, how); if (how < 0 || how > 2) { return -SYS_NET_EINVAL; } const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 { return sock.shutdown(how); }); if (!sock) { return -SYS_NET_EBADF; } if (sock.ret < 0) { return sys_net_error{sock.ret}; } return CELL_OK; } error_code sys_net_bnet_socket(ppu_thread& ppu, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_socket(family=%s, type=%s, protocol=%s)", family, type, protocol); if (family != SYS_NET_AF_INET) { sys_net.error("sys_net_bnet_socket(): unknown family (%d)", family); } if (type != SYS_NET_SOCK_STREAM && type != SYS_NET_SOCK_DGRAM && type != SYS_NET_SOCK_RAW && type != SYS_NET_SOCK_DGRAM_P2P && type != SYS_NET_SOCK_STREAM_P2P) { sys_net.error("sys_net_bnet_socket(): unsupported type (%d)", type); return -SYS_NET_EPROTONOSUPPORT; } std::shared_ptr sock_lv2; switch (type) { case SYS_NET_SOCK_STREAM: case SYS_NET_SOCK_DGRAM: { auto lv2_native = std::make_shared(family, type, protocol); if (s32 result = lv2_native->create_socket(); result < 0) { return sys_net_error{result}; } sock_lv2 = std::move(lv2_native); break; } case SYS_NET_SOCK_RAW: sock_lv2 = std::make_shared(family, type, protocol); break; case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = std::make_shared(family, type, protocol); break; case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = std::make_shared(family, type, protocol); break; } const s32 s = idm::import_existing(sock_lv2); // Can't allocate more than 1000 sockets if (s == id_manager::id_traits::invalid) { return -SYS_NET_EMFILE; } sock_lv2->set_lv2_id(s); return not_an_error(s); } error_code sys_net_bnet_close(ppu_thread& ppu, s32 s) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_bnet_close(s=%d)", s); auto sock = idm::withdraw(s); if (!sock) { return -SYS_NET_EBADF; } if (sock->get_queue_size()) { sock->abort_socket(0); } sock->close(); { // Ensures the socket has no lingering copy from the network thread std::lock_guard nw_lock(g_fxo->get().s_nw_mutex); sock.reset(); } return CELL_OK; } error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 nfds, s32 ms) { ppu.state += cpu_flag::wait; sys_net.trace("sys_net_bnet_poll(fds=*0x%x, nfds=%d, ms=%d)", fds, nfds, ms); if (nfds <= 0) { return not_an_error(0); } atomic_t signaled{0}; u64 timeout = ms < 0 ? 0 : ms * 1000ull; std::vector fds_buf; { fds_buf.assign(fds.get_ptr(), fds.get_ptr() + nfds); lv2_obj::prepare_for_sleep(ppu); std::unique_lock nw_lock(g_fxo->get().s_nw_mutex); std::shared_lock lock(id_manager::g_mutex); std::vector<::pollfd> _fds(nfds); #ifdef _WIN32 std::vector connecting(nfds); #endif for (s32 i = 0; i < nfds; i++) { _fds[i].fd = -1; fds_buf[i].revents = 0; if (fds_buf[i].fd < 0) { continue; } if (auto sock = idm::check_unlocked(fds_buf[i].fd)) { signaled += sock->poll(fds_buf[i], _fds[i]); #ifdef _WIN32 connecting[i] = sock->is_connecting(); #endif } else { fds_buf[i].revents |= SYS_NET_POLLNVAL; signaled++; } } #ifdef _WIN32 windows_poll(_fds, nfds, 0, connecting); #else ::poll(_fds.data(), nfds, 0); #endif for (s32 i = 0; i < nfds; i++) { if (_fds[i].revents & (POLLIN | POLLHUP)) fds_buf[i].revents |= SYS_NET_POLLIN; if (_fds[i].revents & POLLOUT) fds_buf[i].revents |= SYS_NET_POLLOUT; if (_fds[i].revents & POLLERR) fds_buf[i].revents |= SYS_NET_POLLERR; if (fds_buf[i].revents) { signaled++; } } if (ms == 0 || signaled) { lock.unlock(); nw_lock.unlock(); std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(sys_net_pollfd)); return not_an_error(signaled); } for (s32 i = 0; i < nfds; i++) { if (fds_buf[i].fd < 0) { continue; } if (auto sock = idm::check_unlocked(fds_buf[i].fd)) { auto lock = sock->lock(); #ifdef _WIN32 sock->set_connecting(connecting[i]); #endif bs_t selected = +lv2_socket::poll_t::error; if (fds_buf[i].events & SYS_NET_POLLIN) selected += lv2_socket::poll_t::read; if (fds_buf[i].events & SYS_NET_POLLOUT) selected += lv2_socket::poll_t::write; // if (fds_buf[i].events & SYS_NET_POLLPRI) // Unimplemented // selected += lv2_socket::poll::error; sock->poll_queue(idm::get_unlocked>(ppu.id), selected, [sock, selected, &fds_buf, i, &signaled, &ppu](bs_t events) { if (events & selected) { if (events & selected & lv2_socket::poll_t::read) fds_buf[i].revents |= SYS_NET_POLLIN; if (events & selected & lv2_socket::poll_t::write) fds_buf[i].revents |= SYS_NET_POLLOUT; if (events & selected & lv2_socket::poll_t::error) fds_buf[i].revents |= SYS_NET_POLLERR; signaled++; g_fxo->get().s_to_awake.emplace_back(&ppu); return true; } sock->set_poll_event(selected); return false; }); } } lv2_obj::sleep(ppu, timeout); } bool has_timedout = false; while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { if (is_stopped(state)) { return {}; } if (state & cpu_flag::signal) { break; } if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) { // Wait for rescheduling if (ppu.check_state()) { return {}; } std::lock_guard nw_lock(g_fxo->get().s_nw_mutex); if (signaled) { break; } has_timedout = network_clear_queue(ppu); break; } } else { ppu.state.wait(state); } } if (!has_timedout && !signaled) { return -SYS_NET_EINTR; } std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(fds[0])); return not_an_error(signaled); } error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptr readfds, vm::ptr writefds, vm::ptr exceptfds, vm::ptr _timeout) { ppu.state += cpu_flag::wait; sys_net.trace("sys_net_bnet_select(nfds=%d, readfds=*0x%x, writefds=*0x%x, exceptfds=*0x%x, timeout=*0x%x)", nfds, readfds, writefds, exceptfds, _timeout); atomic_t signaled{0}; if (exceptfds) { struct log_t { atomic_t logged = false; }; if (!g_fxo->get().logged.exchange(true)) { sys_net.error("sys_net_bnet_select(): exceptfds not implemented"); } } sys_net_fd_set rread{}, _readfds{}; sys_net_fd_set rwrite{}, _writefds{}; sys_net_fd_set rexcept{}, _exceptfds{}; u64 timeout = !_timeout ? 0 : _timeout->tv_sec * 1000000ull + _timeout->tv_usec; if (nfds > 0 && nfds <= 1024) { if (readfds) _readfds = *readfds; if (writefds) _writefds = *writefds; if (exceptfds) _exceptfds = *exceptfds; std::lock_guard nw_lock(g_fxo->get().s_nw_mutex); reader_lock lock(id_manager::g_mutex); std::vector<::pollfd> _fds(nfds); #ifdef _WIN32 std::vector connecting(nfds); #endif for (s32 i = 0; i < nfds; i++) { _fds[i].fd = -1; bs_t selected{}; if (readfds && _readfds.bit(i)) selected += lv2_socket::poll_t::read; if (writefds && _writefds.bit(i)) selected += lv2_socket::poll_t::write; // if (exceptfds && _exceptfds.bit(i)) // selected += lv2_socket::poll::error; if (selected) { selected += lv2_socket::poll_t::error; } else { continue; } if (auto sock = idm::check_unlocked((lv2_socket::id_base & -1024) + i)) { auto [read_set, write_set, except_set] = sock->select(selected, _fds[i]); if (read_set || write_set || except_set) { signaled++; } if (read_set) { rread.set(i); } if (write_set) { rwrite.set(i); } if (except_set) { rexcept.set(i); } #ifdef _WIN32 connecting[i] = sock->is_connecting(); #endif } else { return -SYS_NET_EBADF; } } #ifdef _WIN32 windows_poll(_fds, nfds, 0, connecting); #else ::poll(_fds.data(), nfds, 0); #endif for (s32 i = 0; i < nfds; i++) { bool sig = false; if (_fds[i].revents & (POLLIN | POLLHUP | POLLERR)) sig = true, rread.set(i); if (_fds[i].revents & (POLLOUT | POLLERR)) sig = true, rwrite.set(i); if (sig) { signaled++; } } if ((_timeout && !timeout) || signaled) { if (readfds) *readfds = rread; if (writefds) *writefds = rwrite; if (exceptfds) *exceptfds = rexcept; return not_an_error(signaled); } for (s32 i = 0; i < nfds; i++) { bs_t selected{}; if (readfds && _readfds.bit(i)) selected += lv2_socket::poll_t::read; if (writefds && _writefds.bit(i)) selected += lv2_socket::poll_t::write; // if (exceptfds && _exceptfds.bit(i)) // selected += lv2_socket::poll_t::error; if (selected) { selected += lv2_socket::poll_t::error; } else { continue; } if (auto sock = idm::check_unlocked((lv2_socket::id_base & -1024) + i)) { auto lock = sock->lock(); #ifdef _WIN32 sock->set_connecting(connecting[i]); #endif sock->poll_queue(idm::get_unlocked>(ppu.id), selected, [sock, selected, i, &rread, &rwrite, &rexcept, &signaled, &ppu](bs_t events) { if (events & selected) { if (selected & lv2_socket::poll_t::read && events & (lv2_socket::poll_t::read + lv2_socket::poll_t::error)) rread.set(i); if (selected & lv2_socket::poll_t::write && events & (lv2_socket::poll_t::write + lv2_socket::poll_t::error)) rwrite.set(i); // if (events & (selected & lv2_socket::poll::error)) // rexcept.set(i); signaled++; g_fxo->get().s_to_awake.emplace_back(&ppu); return true; } sock->set_poll_event(selected); return false; }); } else { return -SYS_NET_EBADF; } } lv2_obj::sleep(ppu, timeout); } else { return -SYS_NET_EINVAL; } bool has_timedout = false; while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { if (is_stopped(state)) { return {}; } if (state & cpu_flag::signal) { break; } if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) { // Wait for rescheduling if (ppu.check_state()) { return {}; } std::lock_guard nw_lock(g_fxo->get().s_nw_mutex); if (signaled) { break; } has_timedout = network_clear_queue(ppu); break; } } else { ppu.state.wait(state); } } if (!has_timedout && !signaled) { return -SYS_NET_EINTR; } if (readfds) *readfds = rread; if (writefds) *writefds = rwrite; if (exceptfds) *exceptfds = rexcept; return not_an_error(signaled); } error_code _sys_net_open_dump(ppu_thread& ppu, s32 len, s32 flags) { ppu.state += cpu_flag::wait; sys_net.todo("_sys_net_open_dump(len=%d, flags=0x%x)", len, flags); return CELL_OK; } error_code _sys_net_read_dump(ppu_thread& ppu, s32 id, vm::ptr buf, s32 len, vm::ptr pflags) { ppu.state += cpu_flag::wait; sys_net.todo("_sys_net_read_dump(id=0x%x, buf=*0x%x, len=%d, pflags=*0x%x)", id, buf, len, pflags); return CELL_OK; } error_code _sys_net_close_dump(ppu_thread& ppu, s32 id, vm::ptr pflags) { ppu.state += cpu_flag::wait; sys_net.todo("_sys_net_close_dump(id=0x%x, pflags=*0x%x)", id, pflags); return CELL_OK; } error_code _sys_net_write_dump(ppu_thread& ppu, s32 id, vm::cptr buf, s32 len, u32 unknown) { ppu.state += cpu_flag::wait; sys_net.todo("_sys_net_write_dump(id=0x%x, buf=*0x%x, len=%d, unk=0x%x)", id, buf, len, unknown); return CELL_OK; } error_code lv2_socket::abort_socket(s32 flags) { decltype(queue) qcopy; { std::lock_guard lock(mutex); if (queue.empty()) { if (flags & SYS_NET_ABORT_STRICT_CHECK) { // Strict error checking: ENOENT if nothing happened return -SYS_NET_ENOENT; } // TODO: Abort the subsequent function called on this socket (need to investigate correct behaviour) return CELL_OK; } qcopy = std::move(queue); events.store({}); } for (auto& [ppu, _] : qcopy) { if (!ppu) continue; sys_net.warning("lv2_socket::abort_socket(): waking up \"%s\": (func: %s, r3=0x%x, r4=0x%x, r5=0x%x, r6=0x%x)", ppu->get_name(), ppu->current_function, ppu->gpr[3], ppu->gpr[4], ppu->gpr[5], ppu->gpr[6]); ppu->gpr[3] = static_cast(-SYS_NET_EINTR); lv2_obj::append(ppu.get()); } lv2_obj::awake_all(); return CELL_OK; } error_code sys_net_abort(ppu_thread& ppu, s32 type, u64 arg, s32 flags) { ppu.state += cpu_flag::wait; sys_net.warning("sys_net_abort(type=%d, arg=0x%x, flags=0x%x)", type, arg, flags); enum abort_type : s32 { _socket, resolver, type_2, // ?? type_3, // ?? all, }; switch (type) { case _socket: { std::lock_guard nw_lock(g_fxo->get().s_nw_mutex); const auto sock = idm::get(static_cast(arg)); if (!sock) { return -SYS_NET_EBADF; } return sock->abort_socket(flags); } case all: { std::vector sockets; idm::select([&](u32 id, lv2_socket&) { sockets.emplace_back(id); }); s32 failed = 0; for (u32 id : sockets) { const auto sock = idm::withdraw(id); if (!sock) { failed++; continue; } if (sock->get_queue_size()) sys_net.error("ABORT 4"); sock->close(); sys_net.success("lv2_socket::handle_abort(): Closed socket %d", id); } // Ensures the socket has no lingering copy from the network thread g_fxo->get().s_nw_mutex.lock_unlock(); return not_an_error(::narrow(sockets.size()) - failed); } case resolver: case type_2: case type_3: { break; } default: return -SYS_NET_EINVAL; } return CELL_OK; } struct net_infoctl_cmd_9_t { be_t zero; vm::bptr server_name; // More (TODO) }; error_code sys_net_infoctl(ppu_thread& ppu, s32 cmd, vm::ptr arg) { ppu.state += cpu_flag::wait; sys_net.todo("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg); // TODO switch (cmd) { case 9: { constexpr auto nameserver = "nameserver \0"sv; char buffer[nameserver.size() + 80]{}; std::memcpy(buffer, nameserver.data(), nameserver.size()); auto& nph = g_fxo->get>(); const auto dns_str = np::ip_to_string(nph.get_dns_ip()); std::memcpy(buffer + nameserver.size() - 1, dns_str.data(), dns_str.size()); std::string_view name{buffer}; vm::static_ptr_cast(arg)->zero = 0; std::memcpy(vm::static_ptr_cast(arg)->server_name.get_ptr(), name.data(), name.size()); break; } default: break; } return CELL_OK; } error_code sys_net_control(ppu_thread& ppu, u32 arg1, s32 arg2, vm::ptr arg3, s32 arg4) { ppu.state += cpu_flag::wait; sys_net.todo("sys_net_control(0x%x, %d, *0x%x, %d)", arg1, arg2, arg3, arg4); return CELL_OK; } error_code sys_net_bnet_ioctl(ppu_thread& ppu, s32 arg1, u32 arg2, u32 arg3) { ppu.state += cpu_flag::wait; sys_net.todo("sys_net_bnet_ioctl(%d, 0x%x, 0x%x)", arg1, arg2, arg3); return CELL_OK; } error_code sys_net_bnet_sysctl(ppu_thread& ppu, u32 arg1, u32 arg2, u32 arg3, vm::ptr arg4, u32 arg5, u32 arg6) { ppu.state += cpu_flag::wait; sys_net.todo("sys_net_bnet_sysctl(0x%x, 0x%x, 0x%x, *0x%x, 0x%x, 0x%x)", arg1, arg2, arg3, arg4, arg5, arg6); return CELL_OK; } error_code sys_net_eurus_post_command(ppu_thread& ppu, s32 arg1, u32 arg2, u32 arg3) { ppu.state += cpu_flag::wait; sys_net.todo("sys_net_eurus_post_command(%d, 0x%x, 0x%x)", arg1, arg2, arg3); return CELL_OK; }