diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index b24dad35ae..9281044a9b 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -35,8 +35,8 @@ namespace { switch (type) { case Network::Type::STREAM: case Network::Type::SEQPACKET: - case Network::Type::RAW: return true; + case Network::Type::RAW: case Network::Type::DGRAM: case Network::Type::RDM: case Network::Type::Unspecified: @@ -562,6 +562,7 @@ std::pair BSD::SocketImpl(Network::Domain domain, Network:: if ((protocol != Network::Protocol::ICMP && protocol != Network::Protocol::ICMPV6) && (room_member && room_member->IsConnected())) { descriptor.socket = std::make_shared(); + descriptor.socket->fd = fd; } else { descriptor.socket = std::make_shared(); } @@ -573,9 +574,9 @@ std::pair BSD::SocketImpl(Network::Domain domain, Network:: && (protocol == Network::Protocol::ICMP || protocol == Network::Protocol::ICMPV6)) { LOG_WARNING(Network, "Using ICMP emulated socket"); descriptor.socket = std::make_shared(); + descriptor.socket->fd = fd; } #endif - descriptor.is_connection_based = IsConnectionBased(type); #ifdef _WIN32 if (descriptor.is_connection_based && descriptor.socket->fd == INVALID_SOCKET) { diff --git a/src/core/internal_network/socket_icmp.cpp b/src/core/internal_network/socket_icmp.cpp index b74c555ce6..7a162a434d 100644 --- a/src/core/internal_network/socket_icmp.cpp +++ b/src/core/internal_network/socket_icmp.cpp @@ -1,15 +1,23 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include +#include +#include +#include +#include +#include +#ifdef __unix__ +#include +#include +#include +#endif #include "common/assert.h" #include "common/logging.h" #include "core/internal_network/socket_icmp.h" -#ifdef __unix__ -#include -#include -#endif +extern "C" { +extern char **environ; +} namespace Network { @@ -31,8 +39,6 @@ u16 ComputeChecksum(std::span data) { } -IcmpSocket::IcmpSocket() noexcept {} - IcmpSocket::~IcmpSocket() { if (fd == INVALID_SOCKET) { return; @@ -61,6 +67,7 @@ std::pair IcmpSocket::Accept() { Errno IcmpSocket::Connect(Network::SockAddrIn addr_in) { LOG_WARNING(Network, "(stubbed) called"); + connected_addr = addr_in; return Errno::E_SUCCESS; } @@ -91,47 +98,69 @@ Errno IcmpSocket::Shutdown(ShutdownHow how) { std::pair IcmpSocket::Recv(int flags, std::span message) { LOG_DEBUG(Network, "(stubbed) called"); - return {s32(0), Errno::E_NOTCONN}; + return connected_addr.has_value() + ? RecvFrom(flags, message, nullptr) + : std::make_pair(s32(0), Errno::E_NOTCONN); } std::pair IcmpSocket::RecvFrom(int flags, std::span message, Network::SockAddrIn* addr) { LOG_DEBUG(Network, "(stubbed) called"); ASSERT(flags == 0); ASSERT(message.size() < std::size_t((std::numeric_limits::max)())); + #if !defined(__OPENORBIS__) && (defined(__FreeBSD__) || defined(__linux__)) - if (addr) { - if (seq_ident.empty()) - return {0, Errno::E_SUCCESS}; - // PLEASE DON'T KILL ME, I SWEAR THIS IS LEGITIMATELY THE BEST WAY TO DO IT - // IF YOU OPEN socket() GOOGLE WILL STRAIGHT UP IP BAN YOU AFTER 2 HOURS - auto rcv_time = f64(std::min(rcv_timeo.tv_sec, 600)) + f64(std::min(rcv_timeo.tv_usec, 1000000)) * f64(1.0 / 1000000.0); - if (!std::isfinite(rcv_time) || std::isnan(rcv_time)) - rcv_time = 1.0; -#ifdef __FreeBSD__ - auto const cmd = fmt::format("ping -W {} -o {}.{}.{}.{}", rcv_time, addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]); -#elif defined(__linux__) - auto const cmd = fmt::format("ping -W {} -c 1 {}.{}.{}.{}", rcv_time, addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3]); -#endif - if (::system(cmd.c_str()) == 0) { - std::vector data{ - 8, - 0, - 0, //checksum - 0, - u8(seq_ident.front() >> 24), //ident - u8(seq_ident.front() >> 16), - u8(seq_ident.front() >> 8), // seq - u8(seq_ident.front() >> 0), - }; - seq_ident.pop_back(); - auto const csum = ComputeChecksum(std::span{data.begin(), data.end()}); - data[2] = u8(csum >> 8); //hi - data[3] = u8(csum); //lo - auto const n = (std::max)(data.size(), message.size()); - std::copy(data.begin(), data.begin() + n, message.begin()); - return {n, Errno::E_SUCCESS}; + const auto rcv_timeout_ms = (s64(rcv_timeo.tv_sec) * 1000) + (s64(rcv_timeo.tv_usec) / 1000); + const auto timestamp = std::chrono::steady_clock::now(); + while (true) { + { + std::lock_guard guard(pings_mutex); + // find ping process that is finished running + for (auto it = pings.begin(); it != pings.end();) { + pid_t result = waitpid(it->ping_pid, &it->ping_status, WNOHANG); + // ping process is still running, go to next + if (result != it->ping_pid) { + ++it; + continue; + } + // ping process is finished, remove and handle it + it = pings.erase(it); + if (it->ping_status == 0) { + if (addr) { + addr->family = it->family; + addr->ip = it->ip; + addr->portno = it->portno; + addr->len = 16; + addr->zeroes = {}; + } + std::array data{ + 0, + 0, + 0, //checksum + 0, + it->seq_ident[0], + it->seq_ident[1], + it->seq_ident[2], + it->seq_ident[3] + }; + auto const csum = ComputeChecksum(std::span{data.begin(), data.end()}); + data[2] = u8(csum >> 8); //hi + data[3] = u8(csum); //lo + auto const n = std::min(data.size(), message.size()); + std::copy(data.begin(), data.begin() + n, message.begin()); + return {s32(n), Errno::E_SUCCESS}; + } + } } - return {-1, Errno::E_TIMEDOUT}; + + if (!blocking) + return {-1, Errno::E_AGAIN}; + + const auto time_diff = std::chrono::steady_clock::now() - timestamp; + const auto time_diff_ms = std::chrono::duration_cast(time_diff).count(); + if (time_diff_ms > rcv_timeout_ms) + return {-1, Errno::E_TIMEDOUT}; + + std::this_thread::yield(); } #endif return {-1, Errno::E_INVAL}; @@ -139,12 +168,8 @@ std::pair IcmpSocket::RecvFrom(int flags, std::span message, Net std::pair IcmpSocket::Send(std::span message, int flags) { LOG_DEBUG(Network, "(stubbed) called"); - seq_ident.push_back( - (u32(message[4]) << 24) - | (u32(message[5]) << 16) - | (u32(message[6]) << 8) - | (u32(message[7]) << 0) - ); + if (connected_addr.has_value()) + return SendTo(flags, message, std::addressof(connected_addr.value())); return {s32(0), Errno::E_NOTCONN}; } @@ -156,8 +181,73 @@ std::pair IcmpSocket::SendTo(u32 flags, std::span message, // 2..4 -> checksum // 4..6 -> ident // 6..8 -> seq - if (!message.empty()) + + // PLEASE DON'T KILL ME, I SWEAR THIS IS LEGITIMATELY THE BEST WAY TO DO IT + // IF YOU OPEN socket() GOOGLE WILL STRAIGHT UP IP BAN YOU AFTER 2 HOURS +#if !defined(__OPENORBIS__) && (defined(__FreeBSD__) || defined(__linux__)) + const auto rcv_timeout_ms = (s64(rcv_timeo.tv_sec) * 1000) + (s64(rcv_timeo.tv_usec) / 1000); + if (!addr) + return {-1, Errno::E_DESTADDRREQ}; + + if (message.size() >= 8) { + std::string ip_str = fmt::format( + "{}.{}.{}.{}", + addr->ip[0], + addr->ip[1], + addr->ip[2], + addr->ip[3] + ); +#ifdef __FreeBSD__ + // ping -W option is a nonfractional int (milliseconds) + std::string timeout_str = fmt::format("{}", rcv_timeout_ms); + std::vector argv = { + const_cast("ping"), + const_cast("-c"), + const_cast("1"), + const_cast("-W"), + timeout_str.data(), + ip_str.data(), + nullptr + }; +#elif defined(__linux__) + // ping -W option is a fractional float (seconds) + auto const rcv_timeout_s = f64(rcv_timeout_ms) / 1000.0; + std::string timeout_str = fmt::format("{}", rcv_timeout_s); + std::vector argv = { + const_cast("ping"), + const_cast("-c"), + const_cast("1"), + const_cast("-W"), + timeout_str.data(), + ip_str.data(), + nullptr + }; +#endif + pid_t ping_pid; + // we should pass in attributes to stop stdout spam, but im too lazy to figure that out + if (posix_spawnp(&ping_pid, "ping", nullptr, nullptr, argv.data(), environ) != 0) { + LOG_ERROR(Network, "Unable to start ping process for emulated ICMP socket"); + return {-1, Errno::E_INVAL}; + } + std::lock_guard guard(pings_mutex); + if (pings.size() >= pings.max_size()) + pings.erase(pings.begin()); + pings.push_back(PingProcessData{ + .ip = addr->ip, + .portno = addr->portno, + .ping_pid = ping_pid, + .ping_status = 0, + .seq_ident = { + message[4], + message[5], + message[6], + message[7] + }, + .family = addr->family, + }); return {s32(message.size()), Errno::E_SUCCESS}; + } +#endif return {-1, Errno::E_INVAL}; } @@ -180,6 +270,7 @@ void IcmpSocket::HandleProxyPacket(const ProxyPacket& packet) { LOG_WARNING(Network, "(stubbed) called"); } Errno IcmpSocket::SetNonBlock(bool enable) { + blocking = !enable; return Errno::E_SUCCESS; } diff --git a/src/core/internal_network/socket_icmp.h b/src/core/internal_network/socket_icmp.h index 0906b1f8cc..23743605c0 100644 --- a/src/core/internal_network/socket_icmp.h +++ b/src/core/internal_network/socket_icmp.h @@ -3,15 +3,29 @@ #pragma once +#include #include +#include +#include +#include +#include #include "core/internal_network/socket_types.h" #include "core/internal_network/sockets.h" namespace Network { +struct PingProcessData { + IPv4Address ip; + u16 portno; + pid_t ping_pid; + pid_t ping_status; + std::array seq_ident; + u8 family; +}; + class IcmpSocket : public Network::SocketBase { public: - explicit IcmpSocket() noexcept; + explicit IcmpSocket() noexcept = default; ~IcmpSocket() override; Errno Initialize(Domain domain, Type type, Protocol socket_protocol) override; Errno Close() override; @@ -32,8 +46,11 @@ public: void HandleProxyPacket(const ProxyPacket& packet) override; Errno SetNonBlock(bool enable) override; - std::vector seq_ident; + boost::container::static_vector pings; + std::optional connected_addr; + std::mutex pings_mutex; Network::Timeval rcv_timeo; + bool blocking = true; }; } // namespace Network