|
|
|
@ -1,15 +1,23 @@ |
|
|
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cerrno>
|
|
|
|
#include <chrono>
|
|
|
|
#include <mutex>
|
|
|
|
#include <thread>
|
|
|
|
#ifdef __unix__
|
|
|
|
#include <spawn.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
#include "common/assert.h"
|
|
|
|
#include "common/logging.h"
|
|
|
|
#include "core/internal_network/socket_icmp.h"
|
|
|
|
|
|
|
|
#ifdef __unix__
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#endif
|
|
|
|
extern "C" { |
|
|
|
extern char **environ; |
|
|
|
} |
|
|
|
|
|
|
|
namespace Network { |
|
|
|
|
|
|
|
@ -31,8 +39,6 @@ u16 ComputeChecksum(std::span<const u8> data) { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
IcmpSocket::IcmpSocket() noexcept {} |
|
|
|
|
|
|
|
IcmpSocket::~IcmpSocket() { |
|
|
|
if (fd == INVALID_SOCKET) { |
|
|
|
return; |
|
|
|
@ -61,6 +67,7 @@ std::pair<IcmpSocket::AcceptResult, Errno> 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<s32, Errno> IcmpSocket::Recv(int flags, std::span<u8> 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<s32, Errno> IcmpSocket::RecvFrom(int flags, std::span<u8> message, Network::SockAddrIn* addr) { |
|
|
|
LOG_DEBUG(Network, "(stubbed) called"); |
|
|
|
ASSERT(flags == 0); |
|
|
|
ASSERT(message.size() < std::size_t((std::numeric_limits<int>::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<u64>(rcv_timeo.tv_sec, 600)) + f64(std::min<u64>(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<u8> 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<const u8>{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<u8, 8> 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<const u8>{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<std::chrono::milliseconds>(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<s32, Errno> IcmpSocket::RecvFrom(int flags, std::span<u8> message, Net |
|
|
|
|
|
|
|
std::pair<s32, Errno> IcmpSocket::Send(std::span<const u8> 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<s32, Errno> IcmpSocket::SendTo(u32 flags, std::span<const u8> 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<char*> argv = { |
|
|
|
const_cast<char*>("ping"), |
|
|
|
const_cast<char*>("-c"), |
|
|
|
const_cast<char*>("1"), |
|
|
|
const_cast<char*>("-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<char*> argv = { |
|
|
|
const_cast<char*>("ping"), |
|
|
|
const_cast<char*>("-c"), |
|
|
|
const_cast<char*>("1"), |
|
|
|
const_cast<char*>("-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; |
|
|
|
} |
|
|
|
|
|
|
|
|