mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
network: icmp emulation (#694)
## Link to GitHub Issue or related Pull Request, if one exists #0 ## Description of change Add `-icmphook` option under spicecfg Development/Network for emulating keepalive ping replies in user mode, so games do not need to open privileged raw ICMP sockets. This makes connecting to networks possible on macOS under Whisky, and on Windows with ICMP firewalled or spice running without Administrator privileges. ## Testing Tested DDR and SDVX on Windows 10 and macOS Tahoe. Network check status in test menu shows `CONNECTED` when -icmphook is enabled, and `NOT CONNECTED` when it is disabled. Actual network functionality works as expected (under previously impossible conditions).
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <iptypes.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <icmpapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct IcmpHdr {
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t checksum_be;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
decltype(IcmpSendEcho) *IcmpSendEcho_orig = nullptr;
|
||||
|
||||
DWORD WINAPI IcmpSendEcho_hook(
|
||||
HANDLE IcmpHandle,
|
||||
IPAddr DestinationAddress,
|
||||
LPVOID RequestData,
|
||||
WORD RequestSize,
|
||||
PIP_OPTION_INFORMATION RequestOptions,
|
||||
LPVOID ReplyBuffer,
|
||||
DWORD ReplySize,
|
||||
DWORD Timeout) {
|
||||
if (!IcmpSendEcho_orig) {
|
||||
SetLastError(ERROR_INVALID_FUNCTION);
|
||||
return 0;
|
||||
}
|
||||
DWORD n = IcmpSendEcho_orig(
|
||||
IcmpHandle,
|
||||
DestinationAddress,
|
||||
RequestData,
|
||||
RequestSize,
|
||||
RequestOptions,
|
||||
ReplyBuffer,
|
||||
ReplySize,
|
||||
Timeout);
|
||||
if (n != 0) {
|
||||
return n;
|
||||
}
|
||||
if (ReplyBuffer == nullptr || ReplySize < sizeof(ICMP_ECHO_REPLY) || RequestData == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
if (RequestSize < sizeof(IcmpHdr)) {
|
||||
return 0;
|
||||
}
|
||||
const auto *req = reinterpret_cast<const uint8_t *>(RequestData);
|
||||
const auto *ih = reinterpret_cast<const IcmpHdr *>(req);
|
||||
if (ih->type != 8 || ih->code != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(ReplyBuffer, 0, ReplySize);
|
||||
auto *reply = reinterpret_cast<ICMP_ECHO_REPLY *>(ReplyBuffer);
|
||||
const size_t data_off = offsetof(ICMP_ECHO_REPLY, Data);
|
||||
const size_t avail = ReplySize > data_off ? ReplySize - data_off : 0;
|
||||
const size_t copy_len = (std::min)(static_cast<size_t>(RequestSize), avail);
|
||||
reply->Address = DestinationAddress;
|
||||
reply->Status = IP_SUCCESS;
|
||||
reply->RoundTripTime = 1;
|
||||
reply->DataSize = static_cast<USHORT>(copy_len);
|
||||
if (copy_len > 0) {
|
||||
memcpy(reinterpret_cast<uint8_t *>(ReplyBuffer) + data_off, RequestData, copy_len);
|
||||
}
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void icmphook_iphlpapi_install() {
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
if (!detour::trampoline_try(
|
||||
"iphlpapi.dll",
|
||||
"IcmpSendEcho",
|
||||
(void *) IcmpSendEcho_hook,
|
||||
(void **) &IcmpSendEcho_orig)) {
|
||||
log_warning("network", "ICMP emulation: IcmpSendEcho hook was not installed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
#include "icmphook_net.h"
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
#include <iphlpapi.h>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
|
||||
void icmphook_iphlpapi_install();
|
||||
|
||||
#ifndef SPICE64
|
||||
using socket_uint = unsigned int;
|
||||
#else
|
||||
using socket_uint = unsigned long long;
|
||||
#endif
|
||||
|
||||
namespace icmphook_internal {
|
||||
|
||||
constexpr uint32_t k_loopback_host = 0x7f000001u;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct IcmpHeaderView {
|
||||
uint8_t type;
|
||||
uint8_t code;
|
||||
uint16_t checksum_be;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
bool icmp_packet_valid(const uint8_t *icmp, size_t len) {
|
||||
if (len < sizeof(IcmpHeaderView)) {
|
||||
return false;
|
||||
}
|
||||
unsigned sum = 0;
|
||||
for (size_t i = 0; i + 1 < len; i += 2) {
|
||||
sum += (static_cast<unsigned>(icmp[i]) << 8) | icmp[i + 1];
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
if (len & 1) {
|
||||
sum += static_cast<unsigned>(icmp[len - 1]) << 8;
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
return sum == 0xFFFFu || sum == 0;
|
||||
}
|
||||
|
||||
bool icmp_build_echo_reply(std::vector<uint8_t> &out, const uint8_t *req, size_t req_len) {
|
||||
if (req_len < sizeof(IcmpHeaderView)) {
|
||||
return false;
|
||||
}
|
||||
out.resize(req_len);
|
||||
memcpy(out.data(), req, req_len);
|
||||
auto *hdr = reinterpret_cast<IcmpHeaderView *>(out.data());
|
||||
hdr->type = 0;
|
||||
hdr->code = 0;
|
||||
hdr->checksum_be = 0;
|
||||
unsigned sum = 0;
|
||||
for (size_t i = 0; i + 1 < out.size(); i += 2) {
|
||||
sum += (static_cast<unsigned>(out[i]) << 8) | out[i + 1];
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
if (out.size() & 1) {
|
||||
sum += static_cast<unsigned>(out[out.size() - 1]) << 8;
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
}
|
||||
sum = (sum >> 16) + (sum & 0xFFFF);
|
||||
uint16_t csum = static_cast<uint16_t>(~sum);
|
||||
hdr->checksum_be = htons(csum);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool icmp_fix_pointer(
|
||||
const uint8_t *base,
|
||||
size_t total_len,
|
||||
const uint8_t **icmp_start,
|
||||
size_t *icmp_len) {
|
||||
if (total_len >= 20 && (base[0] >> 4) == 4) {
|
||||
size_t ihl = (base[0] & 0x0Fu) * 4;
|
||||
if (ihl < 20 || ihl > total_len || base[9] != IPPROTO_ICMP) {
|
||||
return false;
|
||||
}
|
||||
*icmp_start = base + ihl;
|
||||
*icmp_len = total_len - ihl;
|
||||
return true;
|
||||
}
|
||||
*icmp_start = base;
|
||||
*icmp_len = total_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ipv4_encode_datagram(
|
||||
std::vector<uint8_t> &out,
|
||||
uint32_t src_host_order,
|
||||
uint32_t dst_host_order,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len) {
|
||||
const size_t hdr_len = 20;
|
||||
if (payload_len > 0xFFFFu - hdr_len) {
|
||||
return false;
|
||||
}
|
||||
const auto total_len = static_cast<uint16_t>(hdr_len + payload_len);
|
||||
out.resize(hdr_len + payload_len);
|
||||
uint8_t *p = out.data();
|
||||
memset(p, 0, hdr_len);
|
||||
p[0] = 0x45;
|
||||
p[1] = 0;
|
||||
p[2] = static_cast<uint8_t>(total_len >> 8);
|
||||
p[3] = static_cast<uint8_t>(total_len & 0xFF);
|
||||
p[6] = 0x40;
|
||||
p[8] = 1;
|
||||
p[9] = IPPROTO_ICMP;
|
||||
uint32_t src_wire = htonl(src_host_order);
|
||||
uint32_t dst_wire = htonl(dst_host_order);
|
||||
memcpy(p + 12, &src_wire, 4);
|
||||
memcpy(p + 16, &dst_wire, 4);
|
||||
unsigned sum = 0;
|
||||
for (size_t i = 0; i < hdr_len; i += 2) {
|
||||
sum += (static_cast<unsigned>(p[i]) << 8) | p[i + 1];
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
}
|
||||
uint16_t hdr_csum = static_cast<uint16_t>(~sum);
|
||||
p[10] = static_cast<uint8_t>(hdr_csum >> 8);
|
||||
p[11] = static_cast<uint8_t>(hdr_csum & 0xFF);
|
||||
memcpy(p + hdr_len, payload, payload_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cached adapter list. Populated once per process: adapter changes after the
|
||||
// first ICMP send are intentionally not picked up, in exchange for avoiding
|
||||
// repeated IP Helper calls on every emulated send.
|
||||
std::once_flag g_adapters_cache_init;
|
||||
std::vector<uint8_t> g_adapters_cache;
|
||||
|
||||
void fill_adapters_cache() {
|
||||
std::call_once(g_adapters_cache_init, []() {
|
||||
ULONG buf_len = 0;
|
||||
DWORD r = GetAdaptersInfo(nullptr, &buf_len);
|
||||
if (r != ERROR_BUFFER_OVERFLOW && r != ERROR_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
if (buf_len == 0) {
|
||||
return;
|
||||
}
|
||||
g_adapters_cache.resize(buf_len);
|
||||
auto *info = reinterpret_cast<PIP_ADAPTER_INFO>(g_adapters_cache.data());
|
||||
if (GetAdaptersInfo(info, &buf_len) != ERROR_SUCCESS) {
|
||||
g_adapters_cache.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t local_ipv4_for_peer(
|
||||
uint32_t peer_host_order,
|
||||
uint32_t bind_pref_host_order,
|
||||
bool has_bind) {
|
||||
if (peer_host_order == k_loopback_host) {
|
||||
return k_loopback_host;
|
||||
}
|
||||
if (has_bind && bind_pref_host_order != 0) {
|
||||
return bind_pref_host_order;
|
||||
}
|
||||
fill_adapters_cache();
|
||||
if (g_adapters_cache.empty()) {
|
||||
return peer_host_order;
|
||||
}
|
||||
auto *info = reinterpret_cast<PIP_ADAPTER_INFO>(g_adapters_cache.data());
|
||||
for (PIP_ADAPTER_INFO a = info; a != nullptr; a = a->Next) {
|
||||
uint32_t ip = ntohl(inet_addr(a->IpAddressList.IpAddress.String));
|
||||
uint32_t mask = ntohl(inet_addr(a->IpAddressList.IpMask.String));
|
||||
if (mask == 0) {
|
||||
continue;
|
||||
}
|
||||
if ((peer_host_order & mask) == (ip & mask)) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
return peer_host_order;
|
||||
}
|
||||
|
||||
struct EmuSock {
|
||||
std::deque<std::vector<uint8_t>> queue;
|
||||
std::condition_variable_any cv;
|
||||
DWORD rx_timeout_ms = INFINITE;
|
||||
bool nonblocking = false;
|
||||
bool has_bind = false;
|
||||
uint32_t bind_ip_host = 0;
|
||||
};
|
||||
|
||||
std::recursive_mutex g_mu;
|
||||
std::unordered_map<SOCKET, std::unique_ptr<EmuSock>> g_socks;
|
||||
std::atomic<uint32_t> g_seq{1};
|
||||
|
||||
// Fast-path flag so the bind() hook in networkhook.cpp can short-circuit
|
||||
// without taking g_mu when the ICMP feature was never enabled.
|
||||
std::atomic<bool> g_installed{false};
|
||||
|
||||
decltype(socket) *socket_orig = nullptr;
|
||||
decltype(WSASocketW) *WSASocketW_orig = nullptr;
|
||||
decltype(WSASocketA) *WSASocketA_orig = nullptr;
|
||||
decltype(closesocket) *closesocket_orig = nullptr;
|
||||
decltype(bind) *bind_trampoline_orig = nullptr;
|
||||
decltype(sendto) *sendto_orig = nullptr;
|
||||
decltype(recvfrom) *recvfrom_orig = nullptr;
|
||||
decltype(WSASendTo) *WSASendTo_orig = nullptr;
|
||||
decltype(WSARecvFrom) *WSARecvFrom_orig = nullptr;
|
||||
decltype(ioctlsocket) *ioctlsocket_orig = nullptr;
|
||||
decltype(setsockopt) *setsockopt_orig = nullptr;
|
||||
|
||||
EmuSock *lookup(SOCKET s) {
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
SOCKET alloc_icmp_socket() {
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
for (;;) {
|
||||
uint32_t x = g_seq.fetch_add(1, std::memory_order_relaxed);
|
||||
SOCKET s = (SOCKET)(socket_uint)(0xE0000000u | (x & 0x0FFFFFFFu));
|
||||
if (s == INVALID_SOCKET) {
|
||||
continue;
|
||||
}
|
||||
if (g_socks.find(s) == g_socks.end()) {
|
||||
g_socks[s] = std::make_unique<EmuSock>();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void release_icmp_socket(SOCKET s) {
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
it->second->cv.notify_all();
|
||||
g_socks.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool process_icmp_send(SOCKET s, const uint8_t *buf, int len, const sockaddr *to, int tolen) {
|
||||
if (len <= 0 || tolen < (int) sizeof(sockaddr_in) || !buf || !to) {
|
||||
return true;
|
||||
}
|
||||
auto *sin = reinterpret_cast<const sockaddr_in *>(to);
|
||||
if (sin->sin_family != AF_INET) {
|
||||
return true;
|
||||
}
|
||||
uint32_t peer_host = ntohl(sin->sin_addr.s_addr);
|
||||
|
||||
const uint8_t *icmp_ptr = nullptr;
|
||||
size_t icmp_len = 0;
|
||||
const auto *raw = reinterpret_cast<const uint8_t *>(buf);
|
||||
if (!icmp_fix_pointer(raw, static_cast<size_t>(len), &icmp_ptr, &icmp_len)) {
|
||||
return true;
|
||||
}
|
||||
if (!icmp_packet_valid(icmp_ptr, icmp_len)) {
|
||||
return true;
|
||||
}
|
||||
if (icmp_len < sizeof(IcmpHeaderView)) {
|
||||
return true;
|
||||
}
|
||||
const auto *ih = reinterpret_cast<const IcmpHeaderView *>(icmp_ptr);
|
||||
if (ih->type != 8 || ih->code != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> icmp_reply;
|
||||
if (!icmp_build_echo_reply(icmp_reply, icmp_ptr, icmp_len)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> packet;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
return true;
|
||||
}
|
||||
EmuSock *es = it->second.get();
|
||||
uint32_t local_host = local_ipv4_for_peer(peer_host, es->bind_ip_host, es->has_bind);
|
||||
if (!ipv4_encode_datagram(
|
||||
packet, peer_host, local_host, icmp_reply.data(), icmp_reply.size())) {
|
||||
return true;
|
||||
}
|
||||
es->queue.push_back(std::move(packet));
|
||||
es->cv.notify_all();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SOCKET WINAPI socket_hook(int af, int type, int protocol) {
|
||||
if (af == AF_INET && type == SOCK_RAW && protocol == IPPROTO_ICMP) {
|
||||
SOCKET s = alloc_icmp_socket();
|
||||
log_info("network", "ICMP emulation: allocated raw socket {}", (socket_uint) s);
|
||||
return s;
|
||||
}
|
||||
return socket_orig(af, type, protocol);
|
||||
}
|
||||
|
||||
SOCKET WINAPI WSASocketW_hook(
|
||||
int af,
|
||||
int type,
|
||||
int protocol,
|
||||
LPWSAPROTOCOL_INFOW lpProtocolInfo,
|
||||
GROUP g,
|
||||
DWORD dwFlags) {
|
||||
const bool is_raw_icmp =
|
||||
af == AF_INET && type == SOCK_RAW && protocol == IPPROTO_ICMP;
|
||||
if (lpProtocolInfo == nullptr && is_raw_icmp) {
|
||||
SOCKET s = alloc_icmp_socket();
|
||||
log_info("network", "ICMP emulation: allocated raw WSASocketW {}", (socket_uint) s);
|
||||
return s;
|
||||
}
|
||||
return WSASocketW_orig(af, type, protocol, lpProtocolInfo, g, dwFlags);
|
||||
}
|
||||
|
||||
SOCKET WINAPI WSASocketA_hook(
|
||||
int af,
|
||||
int type,
|
||||
int protocol,
|
||||
LPWSAPROTOCOL_INFOA lpProtocolInfo,
|
||||
GROUP g,
|
||||
DWORD dwFlags) {
|
||||
const bool is_raw_icmp =
|
||||
af == AF_INET && type == SOCK_RAW && protocol == IPPROTO_ICMP;
|
||||
if (lpProtocolInfo == nullptr && is_raw_icmp) {
|
||||
SOCKET s = alloc_icmp_socket();
|
||||
log_info("network", "ICMP emulation: allocated raw WSASocketA {}", (socket_uint) s);
|
||||
return s;
|
||||
}
|
||||
return WSASocketA_orig(af, type, protocol, lpProtocolInfo, g, dwFlags);
|
||||
}
|
||||
|
||||
int WINAPI closesocket_hook(SOCKET s) {
|
||||
if (lookup(s)) {
|
||||
release_icmp_socket(s);
|
||||
return 0;
|
||||
}
|
||||
return closesocket_orig(s);
|
||||
}
|
||||
|
||||
int WINAPI bind_hook_ws2(SOCKET s, const sockaddr *name, int namelen) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end() && name && namelen >= (int) sizeof(sockaddr_in)) {
|
||||
auto *in = reinterpret_cast<const sockaddr_in *>(name);
|
||||
if (in->sin_family != AF_INET) {
|
||||
WSASetLastError(WSAEAFNOSUPPORT);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
it->second->has_bind = true;
|
||||
it->second->bind_ip_host = ntohl(in->sin_addr.s_addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return bind_trampoline_orig(s, name, namelen);
|
||||
}
|
||||
|
||||
int WINAPI sendto_hook(
|
||||
SOCKET s, const char *buf, int len, int flags, const sockaddr *to, int tolen) {
|
||||
EmuSock *es = lookup(s);
|
||||
if (!es) {
|
||||
return sendto_orig(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
if (!buf || !to) {
|
||||
WSASetLastError(WSAEFAULT);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
process_icmp_send(s, reinterpret_cast<const uint8_t *>(buf), len, to, tolen);
|
||||
return len;
|
||||
}
|
||||
|
||||
int WINAPI WSASendTo_hook(
|
||||
SOCKET s,
|
||||
LPWSABUF lpBuffers,
|
||||
DWORD dwBufferCount,
|
||||
LPDWORD lpNumberOfBytesSent,
|
||||
DWORD dwFlags,
|
||||
const sockaddr *lpTo,
|
||||
int iTolen,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) {
|
||||
EmuSock *es = lookup(s);
|
||||
if (!es) {
|
||||
return WSASendTo_orig(
|
||||
s,
|
||||
lpBuffers,
|
||||
dwBufferCount,
|
||||
lpNumberOfBytesSent,
|
||||
dwFlags,
|
||||
lpTo,
|
||||
iTolen,
|
||||
lpOverlapped,
|
||||
lpCompletionRoutine);
|
||||
}
|
||||
if (lpOverlapped != nullptr) {
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (dwBufferCount != 1 || !lpBuffers || !lpNumberOfBytesSent) {
|
||||
WSASetLastError(WSAEINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
int len = (int) lpBuffers[0].len;
|
||||
process_icmp_send(
|
||||
s,
|
||||
reinterpret_cast<const uint8_t *>(lpBuffers[0].buf),
|
||||
len,
|
||||
lpTo,
|
||||
iTolen);
|
||||
*lpNumberOfBytesSent = static_cast<DWORD>(len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Dequeue one datagram for an emulated ICMP socket into the caller-provided
|
||||
// buffer. If out_full_dgram_bytes is non-null, it receives the full packet
|
||||
// size (before truncation to len) so callers can signal WSAEMSGSIZE.
|
||||
int emu_recv_dequeue(
|
||||
SOCKET s, char *buf, int len, sockaddr *from, int *fromlen,
|
||||
size_t *out_full_dgram_bytes) {
|
||||
if (!buf || len <= 0) {
|
||||
WSASetLastError(WSAEINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> pkt;
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
auto wait_pred = [&] {
|
||||
auto j = g_socks.find(s);
|
||||
return j == g_socks.end() || !j->second->queue.empty();
|
||||
};
|
||||
|
||||
EmuSock *es = it->second.get();
|
||||
if (es->nonblocking) {
|
||||
if (es->queue.empty()) {
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
} else if (es->rx_timeout_ms == INFINITE || es->rx_timeout_ms == 0) {
|
||||
it->second->cv.wait(lock, wait_pred);
|
||||
} else {
|
||||
const auto timeout = std::chrono::milliseconds(es->rx_timeout_ms);
|
||||
if (!it->second->cv.wait_for(lock, timeout, wait_pred)) {
|
||||
WSASetLastError(WSAETIMEDOUT);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
it = g_socks.find(s);
|
||||
if (it == g_socks.end()) {
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
es = it->second.get();
|
||||
if (es->queue.empty()) {
|
||||
WSASetLastError(WSAENOTSOCK);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
pkt = std::move(es->queue.front());
|
||||
es->queue.pop_front();
|
||||
}
|
||||
|
||||
if (out_full_dgram_bytes) {
|
||||
*out_full_dgram_bytes = pkt.size();
|
||||
}
|
||||
const int take = (len < (int) pkt.size()) ? len : (int) pkt.size();
|
||||
memcpy(buf, pkt.data(), static_cast<size_t>(take));
|
||||
if (from && fromlen && *fromlen >= (int) sizeof(sockaddr_in)) {
|
||||
auto *out = reinterpret_cast<sockaddr_in *>(from);
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->sin_family = AF_INET;
|
||||
if (pkt.size() >= 20) {
|
||||
memcpy(&out->sin_addr, pkt.data() + 12, 4);
|
||||
} else {
|
||||
out->sin_addr.s_addr = htonl(k_loopback_host);
|
||||
}
|
||||
*fromlen = sizeof(sockaddr_in);
|
||||
}
|
||||
return take;
|
||||
}
|
||||
|
||||
int WINAPI recvfrom_hook(
|
||||
SOCKET s, char *buf, int len, int flags, sockaddr *from, int *fromlen) {
|
||||
if (!lookup(s)) {
|
||||
return recvfrom_orig(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
return emu_recv_dequeue(s, buf, len, from, fromlen, nullptr);
|
||||
}
|
||||
|
||||
int WINAPI WSARecvFrom_hook(
|
||||
SOCKET s,
|
||||
LPWSABUF lpBuffers,
|
||||
DWORD dwBufferCount,
|
||||
LPDWORD lpNumberOfBytesRecvd,
|
||||
LPDWORD lpFlags,
|
||||
sockaddr *lpFrom,
|
||||
LPINT lpFromlen,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) {
|
||||
if (!lookup(s)) {
|
||||
return WSARecvFrom_orig(
|
||||
s,
|
||||
lpBuffers,
|
||||
dwBufferCount,
|
||||
lpNumberOfBytesRecvd,
|
||||
lpFlags,
|
||||
lpFrom,
|
||||
lpFromlen,
|
||||
lpOverlapped,
|
||||
lpCompletionRoutine);
|
||||
}
|
||||
if (lpOverlapped != nullptr) {
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (dwBufferCount != 1 || !lpBuffers || !lpNumberOfBytesRecvd) {
|
||||
WSASetLastError(WSAEINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
sockaddr_in from_tmp{};
|
||||
int fromlen_tmp = sizeof(from_tmp);
|
||||
size_t full_bytes = 0;
|
||||
int r = emu_recv_dequeue(
|
||||
s,
|
||||
lpBuffers[0].buf,
|
||||
(int) lpBuffers[0].len,
|
||||
reinterpret_cast<sockaddr *>(&from_tmp),
|
||||
&fromlen_tmp,
|
||||
&full_bytes);
|
||||
if (r == SOCKET_ERROR) {
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (full_bytes > lpBuffers[0].len) {
|
||||
WSASetLastError(WSAEMSGSIZE);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
*lpNumberOfBytesRecvd = static_cast<DWORD>(r);
|
||||
if (lpFlags) {
|
||||
*lpFlags = 0;
|
||||
}
|
||||
if (lpFrom && lpFromlen && *lpFromlen >= (int) sizeof(sockaddr_in)) {
|
||||
*reinterpret_cast<sockaddr_in *>(lpFrom) = from_tmp;
|
||||
*lpFromlen = sizeof(sockaddr_in);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WINAPI ioctlsocket_hook(SOCKET s, long cmd, u_long *argp) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
EmuSock *es = it->second.get();
|
||||
if (argp && static_cast<u_long>(cmd) == FIONBIO) {
|
||||
es->nonblocking = (*argp != 0);
|
||||
return 0;
|
||||
}
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
return ioctlsocket_orig(s, cmd, argp);
|
||||
}
|
||||
|
||||
int WINAPI setsockopt_hook(
|
||||
SOCKET s, int level, int optname, const char *optval, int optlen) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(g_mu);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
const bool deref_optval = level == SOL_SOCKET && optlen >= (int) sizeof(DWORD);
|
||||
if (deref_optval && optval) {
|
||||
EmuSock *es = it->second.get();
|
||||
auto v = reinterpret_cast<const DWORD *>(optval);
|
||||
if (optname == SO_RCVTIMEO) {
|
||||
DWORD ms = *v;
|
||||
es->rx_timeout_ms = (ms == 0) ? INFINITE : ms;
|
||||
return 0;
|
||||
}
|
||||
if (optname == SO_SNDTIMEO) {
|
||||
return 0;
|
||||
}
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
if (!deref_optval) {
|
||||
WSASetLastError(WSAEOPNOTSUPP);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return setsockopt_orig(s, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
void install_icmphook_hooks() {
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
|
||||
bool ok = true;
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "socket",
|
||||
(void *) socket_hook, (void **) &socket_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSASocketW",
|
||||
(void *) WSASocketW_hook, (void **) &WSASocketW_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSASocketA",
|
||||
(void *) WSASocketA_hook, (void **) &WSASocketA_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "closesocket",
|
||||
(void *) closesocket_hook, (void **) &closesocket_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "bind",
|
||||
(void *) bind_hook_ws2, (void **) &bind_trampoline_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "sendto",
|
||||
(void *) sendto_hook, (void **) &sendto_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "recvfrom",
|
||||
(void *) recvfrom_hook, (void **) &recvfrom_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSASendTo",
|
||||
(void *) WSASendTo_hook, (void **) &WSASendTo_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "WSARecvFrom",
|
||||
(void *) WSARecvFrom_hook, (void **) &WSARecvFrom_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "ioctlsocket",
|
||||
(void *) ioctlsocket_hook, (void **) &ioctlsocket_orig);
|
||||
ok &= detour::trampoline_try(
|
||||
"ws2_32.dll", "setsockopt",
|
||||
(void *) setsockopt_hook, (void **) &setsockopt_orig);
|
||||
|
||||
if (!ok) {
|
||||
log_warning(
|
||||
"network",
|
||||
"ICMP emulation: one or more ws2_32 hooks failed to install");
|
||||
} else {
|
||||
log_info("network", "ICMP emulation hooks installed (raw ICMP sockets)");
|
||||
}
|
||||
|
||||
g_installed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
} // namespace icmphook_internal
|
||||
|
||||
bool icmphook_is_emulated_socket(SOCKET s) {
|
||||
if (!icmphook_internal::g_installed.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock(icmphook_internal::g_mu);
|
||||
return icmphook_internal::g_socks.find(s) != icmphook_internal::g_socks.end();
|
||||
}
|
||||
|
||||
bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *out_result) {
|
||||
if (!icmphook_internal::g_installed.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> lock(icmphook_internal::g_mu);
|
||||
if (icmphook_internal::g_socks.find(s) == icmphook_internal::g_socks.end()) {
|
||||
return false;
|
||||
}
|
||||
*out_result = icmphook_internal::bind_hook_ws2(s, name, namelen);
|
||||
return true;
|
||||
}
|
||||
|
||||
void icmphook_net_init() {
|
||||
icmphook_internal::install_icmphook_hooks();
|
||||
icmphook_iphlpapi_install();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <winsock2.h>
|
||||
|
||||
bool icmphook_is_emulated_socket(SOCKET s);
|
||||
|
||||
/*!
|
||||
* Handle bind() for emulated ICMP sockets: records interface address and succeeds without kernel bind.
|
||||
* Returns true if this socket was handled (caller should return 0).
|
||||
*/
|
||||
bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *out_result);
|
||||
|
||||
void icmphook_net_init();
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "hooks/icmphook_net.h"
|
||||
|
||||
// hooking related stuff
|
||||
static decltype(GetAdaptersInfo) *GetAdaptersInfo_orig = nullptr;
|
||||
@@ -189,6 +190,11 @@ static int WINAPI bind_hook(SOCKET s, const struct sockaddr *name, int namelen)
|
||||
#pragma ide diagnostic ignored "OCDFAInspection"
|
||||
#endif
|
||||
|
||||
int icmp_bind_result = 0;
|
||||
if (icmphook_try_bind(s, name, namelen, &icmp_bind_result)) {
|
||||
return icmp_bind_result;
|
||||
}
|
||||
|
||||
// cast to sockaddr_in
|
||||
struct sockaddr_in *in_name = (struct sockaddr_in *) name;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user