network: NIC spoof matching tunnel (#912)

## Link to GitHub Issue or related Pull Request, if one exists
#911

## Description of change
Add tunnelhost/tunnelclient modes to -nicspoof so two cabs can do
LAN-style BPL matching over a real UDP path (including Wine), while
keeping the fake overlay NIC. Offline spoof remains available as before.

## Testing
DDR
This commit is contained in:
drmext
2026-09-11 07:37:45 +00:00
committed by GitHub
parent 531e7a54b3
commit 5e4afecd90
10 changed files with 2447 additions and 73 deletions
+1
View File
@@ -617,6 +617,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/icmphook_net.cpp hooks/icmphook_net.cpp
hooks/icmphook_iphlpapi.cpp hooks/icmphook_iphlpapi.cpp
hooks/nicspoof.cpp hooks/nicspoof.cpp
hooks/nicspoof_tunnel.cpp
hooks/powrprof.cpp hooks/powrprof.cpp
#hooks/rom.cpp #hooks/rom.cpp
hooks/setupapihook.cpp hooks/setupapihook.cpp
+109 -26
View File
@@ -1,5 +1,6 @@
#include "icmphook_net.h" #include "icmphook_net.h"
#include "hooks/nicspoof.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
@@ -353,7 +354,11 @@ int WINAPI bind_hook_ws2(SOCKET s, const sockaddr *name, int namelen) {
{ {
std::lock_guard<std::recursive_mutex> lock(g_mu); std::lock_guard<std::recursive_mutex> lock(g_mu);
auto it = g_socks.find(s); auto it = g_socks.find(s);
if (it != g_socks.end() && name && namelen >= (int) sizeof(sockaddr_in)) { if (it != g_socks.end()) {
if (!name || namelen < (int) sizeof(sockaddr_in)) {
WSASetLastError(WSAEFAULT);
return SOCKET_ERROR;
}
auto *in = reinterpret_cast<const sockaddr_in *>(name); auto *in = reinterpret_cast<const sockaddr_in *>(name);
if (in->sin_family != AF_INET) { if (in->sin_family != AF_INET) {
WSASetLastError(WSAEAFNOSUPPORT); WSASetLastError(WSAEAFNOSUPPORT);
@@ -618,7 +623,10 @@ void install_icmphook_hooks() {
} }
done = true; done = true;
const bool defer_divert = nicspoof_tunnel_enabled();
bool ok = true; bool ok = true;
// Always own socket creation so raw ICMP sockets become emulated.
ok &= detour::trampoline_try( ok &= detour::trampoline_try(
"ws2_32.dll", "socket", "ws2_32.dll", "socket",
(void *) socket_hook, (void **) &socket_orig); (void *) socket_hook, (void **) &socket_orig);
@@ -628,37 +636,52 @@ void install_icmphook_hooks() {
ok &= detour::trampoline_try( ok &= detour::trampoline_try(
"ws2_32.dll", "WSASocketA", "ws2_32.dll", "WSASocketA",
(void *) WSASocketA_hook, (void **) &WSASocketA_orig); (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( ok &= detour::trampoline_try(
"ws2_32.dll", "setsockopt", "ws2_32.dll", "setsockopt",
(void *) setsockopt_hook, (void **) &setsockopt_orig); (void *) setsockopt_hook, (void **) &setsockopt_orig);
if (!ok) { if (defer_divert) {
// NIC tunnel already hooked bind/sendto/recvfrom/closesocket/ioctlsocket.
// Tunnel hooks call icmphook_try_* for emulated ICMP sockets.
log_info("network",
"ICMP emulation: socket hooks installed; divert deferred to "
"NIC tunnel (icmphook_try_*)");
} else {
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);
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)");
}
}
if (defer_divert && !ok) {
log_warning( log_warning(
"network", "network",
"ICMP emulation: one or more ws2_32 hooks failed to install"); "ICMP emulation: one or more socket-creation hooks failed");
} else {
log_info("network", "ICMP emulation hooks installed (raw ICMP sockets)");
} }
g_installed.store(true, std::memory_order_release); g_installed.store(true, std::memory_order_release);
@@ -686,6 +709,66 @@ bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *
return true; return true;
} }
bool icmphook_try_sendto(SOCKET s, const char *buf, int len, int flags,
const sockaddr *to, int tolen, int *out_result) {
if (!icmphook_is_emulated_socket(s)) {
return false;
}
*out_result = icmphook_internal::sendto_hook(s, buf, len, flags, to, tolen);
return true;
}
bool icmphook_try_recvfrom(SOCKET s, char *buf, int len, int flags,
sockaddr *from, int *fromlen, int *out_result) {
if (!icmphook_is_emulated_socket(s)) {
return false;
}
*out_result = icmphook_internal::recvfrom_hook(s, buf, len, flags, from, fromlen);
return true;
}
bool icmphook_try_WSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const sockaddr *lpTo,
int iTolen, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, int *out_result) {
if (!icmphook_is_emulated_socket(s)) {
return false;
}
*out_result = icmphook_internal::WSASendTo_hook(
s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpTo,
iTolen, lpOverlapped, lpCompletionRoutine);
return true;
}
bool icmphook_try_WSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr *lpFrom,
LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, int *out_result) {
if (!icmphook_is_emulated_socket(s)) {
return false;
}
*out_result = icmphook_internal::WSARecvFrom_hook(
s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpFrom,
lpFromlen, lpOverlapped, lpCompletionRoutine);
return true;
}
bool icmphook_try_ioctlsocket(SOCKET s, long cmd, u_long *argp, int *out_result) {
if (!icmphook_is_emulated_socket(s)) {
return false;
}
*out_result = icmphook_internal::ioctlsocket_hook(s, cmd, argp);
return true;
}
bool icmphook_try_closesocket(SOCKET s, int *out_result) {
if (!icmphook_is_emulated_socket(s)) {
return false;
}
*out_result = icmphook_internal::closesocket_hook(s);
return true;
}
void icmphook_net_init() { void icmphook_net_init() {
icmphook_internal::install_icmphook_hooks(); icmphook_internal::install_icmphook_hooks();
icmphook_iphlpapi_install(); icmphook_iphlpapi_install();
+20 -1
View File
@@ -6,8 +6,27 @@ bool icmphook_is_emulated_socket(SOCKET s);
/*! /*!
* Handle bind() for emulated ICMP sockets: records interface address and succeeds without kernel bind. * 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). * Returns true if this socket was handled (caller should return *out_result).
*/ */
bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *out_result); bool icmphook_try_bind(SOCKET s, const struct sockaddr *name, int namelen, int *out_result);
/*!
* Divert-path helpers for when NIC tunnel owns the MinHook slots on these APIs.
* Return true if the socket is an emulated ICMP socket and was fully handled.
*/
bool icmphook_try_sendto(SOCKET s, const char *buf, int len, int flags,
const sockaddr *to, int tolen, int *out_result);
bool icmphook_try_recvfrom(SOCKET s, char *buf, int len, int flags,
sockaddr *from, int *fromlen, int *out_result);
bool icmphook_try_WSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const sockaddr *lpTo,
int iTolen, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, int *out_result);
bool icmphook_try_WSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr *lpFrom,
LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, int *out_result);
bool icmphook_try_ioctlsocket(SOCKET s, long cmd, u_long *argp, int *out_result);
bool icmphook_try_closesocket(SOCKET s, int *out_result);
void icmphook_net_init(); void icmphook_net_init();
+131 -32
View File
@@ -14,26 +14,32 @@
#include "util/detour.h" #include "util/detour.h"
#include "util/logging.h" #include "util/logging.h"
#include "hooks/nicspoof_tunnel.h"
namespace { namespace nicspoof_detail {
constexpr uint32_t k_local_ip = 0x0A390302u; /* 10.57.3.2 */ NicSpoofConfig g_cfg;
constexpr uint32_t k_gateway = 0x0A390301u; /* 10.57.3.1 */
constexpr uint32_t k_dns = 0x0A390301u; /* 10.57.3.1 */ constexpr uint32_t k_default_local_ip = 0x0A64640Au; /* 10.100.100.10 */
constexpr uint32_t k_dhcp = 0x0A390301u; /* 10.57.3.1 */ constexpr uint32_t k_gateway = 0x0A646401u; /* 10.100.100.1 */
constexpr uint32_t k_mask = 0xFFFFFF00u; /* 255.255.255.0 */ constexpr uint32_t k_dns1 = 0x0A02010Au; /* 10.2.1.10 */
constexpr uint32_t k_subnet = 0x0A390300u; constexpr uint32_t k_dns2 = 0x0A02011Eu; /* 10.2.1.30 */
constexpr int k_prefix_len = 24; constexpr uint32_t k_dhcp = 0xC0A80001u; /* 192.168.0.1 */
constexpr uint32_t k_mask = 0xFF000000u; /* 255.0.0.0 */
constexpr int k_prefix_len = 8;
constexpr DWORD k_ifindex = 77; constexpr DWORD k_ifindex = 77;
constexpr uint8_t k_fake_mac[6] = {0x12, 0x37, 0x13, 0x37, 0x13, 0x37}; constexpr uint8_t k_fake_mac[6] = {0x12, 0x37, 0x13, 0x37, 0x13, 0x37};
constexpr char k_hostname[] = "N1CSP00F"; constexpr char k_hostname[] = "N1C5P00F";
constexpr char k_domain[] = "local"; constexpr char k_domain[] = "sp2x";
constexpr char k_hostname_fqdn[] = "N1CSP00F.local"; constexpr char k_hostname_fqdn[] = "N1C5P00F.sp2x";
constexpr wchar_t k_domain_w[] = L"local"; constexpr wchar_t k_domain_w[] = L"sp2x";
constexpr char k_adapter_name[] = "{NICSPOOF-0880-0001-4250-4E6963537066}"; constexpr char k_adapter_name[] = "{NICSPOOF-0880-0001-4250-4E6963537066}";
constexpr char k_adapter_desc[] = "NicSpoof Virtual Ethernet"; constexpr char k_adapter_desc[] = "NicSpoof Virtual Ethernet";
constexpr wchar_t k_friendly_name[] = L"NicSpoof"; constexpr wchar_t k_friendly_name[] = L"NicSpoof";
constexpr wchar_t k_adapter_desc_w[] = L"NicSpoof Virtual Ethernet"; constexpr wchar_t k_adapter_desc_w[] = L"NicSpoof Virtual Ethernet";
uint32_t g_local_ip = k_default_local_ip;
uint32_t g_subnet = k_default_local_ip & k_mask;
#define ALIGN_UP_PTR(p, a) \ #define ALIGN_UP_PTR(p, a) \
((BYTE *)(((ULONG_PTR)(p) + ((ULONG_PTR)(a) - 1)) & ~((ULONG_PTR)(a) - 1))) ((BYTE *)(((ULONG_PTR)(p) + ((ULONG_PTR)(a) - 1)) & ~((ULONG_PTR)(a) - 1)))
@@ -125,7 +131,7 @@ DWORD WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO p, PULONG s) {
memcpy(p->Address, k_fake_mac, 6); memcpy(p->Address, k_fake_mac, 6);
p->Type = MIB_IF_TYPE_ETHERNET; p->Type = MIB_IF_TYPE_ETHERNET;
p->DhcpEnabled = 1; p->DhcpEnabled = 1;
ip_to_str(k_local_ip, ip, sizeof(ip)); ip_to_str(g_local_ip, ip, sizeof(ip));
ip_to_str(k_gateway, gw, sizeof(gw)); ip_to_str(k_gateway, gw, sizeof(gw));
ip_to_str(k_dhcp, dhcp, sizeof(dhcp)); ip_to_str(k_dhcp, dhcp, sizeof(dhcp));
strcpy(p->IpAddressList.IpAddress.String, ip); strcpy(p->IpAddressList.IpAddress.String, ip);
@@ -151,8 +157,11 @@ DWORD WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO p, PULONG s) {
} }
DWORD WINAPI GetNetworkParams_hook(PFIXED_INFO p, PULONG s) { DWORD WINAPI GetNetworkParams_hook(PFIXED_INFO p, PULONG s) {
ULONG need = static_cast<ULONG>(sizeof(FIXED_INFO)); /* FIXED_INFO embeds the first IP_ADDR_STRING; second follows in the buffer. */
char dns[16]; ULONG need = static_cast<ULONG>(sizeof(FIXED_INFO) + sizeof(IP_ADDR_STRING));
char dns1[16];
char dns2[16];
PIP_ADDR_STRING dns_second = nullptr;
if (!s) { if (!s) {
return ERROR_INVALID_PARAMETER; return ERROR_INVALID_PARAMETER;
@@ -164,18 +173,27 @@ DWORD WINAPI GetNetworkParams_hook(PFIXED_INFO p, PULONG s) {
memset(p, 0, need); memset(p, 0, need);
strncpy(p->HostName, k_hostname, sizeof(p->HostName) - 1); strncpy(p->HostName, k_hostname, sizeof(p->HostName) - 1);
strncpy(p->DomainName, k_domain, sizeof(p->DomainName) - 1); strncpy(p->DomainName, k_domain, sizeof(p->DomainName) - 1);
ip_to_str(k_dns, dns, sizeof(dns)); ip_to_str(k_dns1, dns1, sizeof(dns1));
strcpy(p->DnsServerList.IpAddress.String, dns); ip_to_str(k_dns2, dns2, sizeof(dns2));
strcpy(p->DnsServerList.IpAddress.String, dns1);
p->DnsServerList.IpMask.String[0] = 0; p->DnsServerList.IpMask.String[0] = 0;
p->DnsServerList.Context = 0; p->DnsServerList.Context = 0;
p->DnsServerList.Next = nullptr; dns_second = reinterpret_cast<PIP_ADDR_STRING>(
reinterpret_cast<BYTE *>(p) + sizeof(FIXED_INFO));
memset(dns_second, 0, sizeof(*dns_second));
strcpy(dns_second->IpAddress.String, dns2);
dns_second->IpMask.String[0] = 0;
dns_second->Context = 0;
dns_second->Next = nullptr;
p->DnsServerList.Next = dns_second;
p->CurrentDnsServer = &p->DnsServerList; p->CurrentDnsServer = &p->DnsServerList;
p->NodeType = 1; p->NodeType = 1;
p->EnableDns = 1; p->EnableDns = 1;
*s = need; *s = need;
if (g_log_params) { if (g_log_params) {
log_misc("network", "NIC spoof GetNetworkParams host={} domain={} dns={}", log_misc("network",
k_hostname, k_domain, dns); "NIC spoof GetNetworkParams host={} domain={} dns={} dns2={}",
k_hostname, k_domain, dns1, dns2);
g_log_params = false; g_log_params = false;
} }
return ERROR_SUCCESS; return ERROR_SUCCESS;
@@ -197,19 +215,21 @@ ULONG WINAPI GetAdaptersAddresses_hook(
sizeof(void *) * 8 + sizeof(void *) * 8 +
sizeof(IP_ADAPTER_UNICAST_ADDRESS) + sizeof(IP_ADAPTER_UNICAST_ADDRESS) +
sizeof(IP_ADAPTER_PREFIX) + sizeof(IP_ADAPTER_PREFIX) +
sizeof(IP_ADAPTER_DNS_SERVER_ADDRESS) + sizeof(IP_ADAPTER_DNS_SERVER_ADDRESS) * 2 +
sizeof(IP_ADAPTER_GATEWAY_ADDRESS) + sizeof(IP_ADAPTER_GATEWAY_ADDRESS) +
sizeof(sockaddr_in) * 4 + 64); sizeof(sockaddr_in) * 5 + 64);
BYTE *blob = nullptr; BYTE *blob = nullptr;
PIP_ADAPTER_ADDRESSES a = nullptr; PIP_ADAPTER_ADDRESSES a = nullptr;
PIP_ADAPTER_UNICAST_ADDRESS u = nullptr; PIP_ADAPTER_UNICAST_ADDRESS u = nullptr;
PIP_ADAPTER_PREFIX pref = nullptr; PIP_ADAPTER_PREFIX pref = nullptr;
PIP_ADAPTER_GATEWAY_ADDRESS gw = nullptr; PIP_ADAPTER_GATEWAY_ADDRESS gw = nullptr;
PIP_ADAPTER_DNS_SERVER_ADDRESS dns = nullptr; PIP_ADAPTER_DNS_SERVER_ADDRESS dns = nullptr;
PIP_ADAPTER_DNS_SERVER_ADDRESS dns2 = nullptr;
sockaddr_in *sa = nullptr; sockaddr_in *sa = nullptr;
sockaddr_in *sm = nullptr; sockaddr_in *sm = nullptr;
sockaddr_in *sg = nullptr; sockaddr_in *sg = nullptr;
sockaddr_in *ds = nullptr; sockaddr_in *ds = nullptr;
sockaddr_in *ds2 = nullptr;
(void)Reserved; (void)Reserved;
(void)Flags; (void)Flags;
@@ -267,7 +287,7 @@ ULONG WINAPI GetAdaptersAddresses_hook(
blob += sizeof(*sa); blob += sizeof(*sa);
memset(sa, 0, sizeof(*sa)); memset(sa, 0, sizeof(*sa));
sa->sin_family = AF_INET; sa->sin_family = AF_INET;
sa->sin_addr.s_addr = htonl(k_local_ip); sa->sin_addr.s_addr = htonl(g_local_ip);
u->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(sa); u->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(sa);
u->Address.iSockaddrLength = sizeof(*sa); u->Address.iSockaddrLength = sizeof(*sa);
a->FirstUnicastAddress = u; a->FirstUnicastAddress = u;
@@ -282,7 +302,7 @@ ULONG WINAPI GetAdaptersAddresses_hook(
blob += sizeof(*sm); blob += sizeof(*sm);
memset(sm, 0, sizeof(*sm)); memset(sm, 0, sizeof(*sm));
sm->sin_family = AF_INET; sm->sin_family = AF_INET;
sm->sin_addr.s_addr = htonl(k_subnet); sm->sin_addr.s_addr = htonl(g_subnet);
pref->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(sm); pref->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(sm);
pref->Address.iSockaddrLength = sizeof(*sm); pref->Address.iSockaddrLength = sizeof(*sm);
a->FirstPrefix = pref; a->FirstPrefix = pref;
@@ -309,10 +329,23 @@ ULONG WINAPI GetAdaptersAddresses_hook(
blob += sizeof(*ds); blob += sizeof(*ds);
memset(ds, 0, sizeof(*ds)); memset(ds, 0, sizeof(*ds));
ds->sin_family = AF_INET; ds->sin_family = AF_INET;
ds->sin_addr.s_addr = htonl(k_dns); ds->sin_addr.s_addr = htonl(k_dns1);
dns->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(ds); dns->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(ds);
dns->Address.iSockaddrLength = sizeof(*ds); dns->Address.iSockaddrLength = sizeof(*ds);
dns->Next = nullptr;
blob = ALIGN_UP_PTR(blob, sizeof(void *));
dns2 = reinterpret_cast<PIP_ADAPTER_DNS_SERVER_ADDRESS>(blob);
blob += sizeof(*dns2);
memset(dns2, 0, sizeof(*dns2));
ds2 = reinterpret_cast<sockaddr_in *>(blob);
blob += sizeof(*ds2);
memset(ds2, 0, sizeof(*ds2));
ds2->sin_family = AF_INET;
ds2->sin_addr.s_addr = htonl(k_dns2);
dns2->Address.lpSockaddr = reinterpret_cast<LPSOCKADDR>(ds2);
dns2->Address.iSockaddrLength = sizeof(*ds2);
dns2->Next = nullptr;
dns->Next = dns2;
a->FirstDnsServerAddress = dns; a->FirstDnsServerAddress = dns;
a->Next = nullptr; a->Next = nullptr;
@@ -404,7 +437,7 @@ INT WSAAPI getaddrinfo_hook(
if (!pNodeName || !pNodeName[0] || name_is_local(pNodeName)) { if (!pNodeName || !pNodeName[0] || name_is_local(pNodeName)) {
if (pNodeName && (_stricmp(pNodeName, k_hostname) == 0 || if (pNodeName && (_stricmp(pNodeName, k_hostname) == 0 ||
_stricmp(pNodeName, k_hostname_fqdn) == 0)) { _stricmp(pNodeName, k_hostname_fqdn) == 0)) {
ip = k_local_ip; ip = g_local_ip;
} else { } else {
ip = 0x7F000001u; ip = 0x7F000001u;
} }
@@ -412,7 +445,7 @@ INT WSAAPI getaddrinfo_hook(
ip = parse_ipv4(pNodeName); ip = parse_ipv4(pNodeName);
} else { } else {
/* offline=1: map external names to local_ip */ /* offline=1: map external names to local_ip */
ip = k_local_ip; ip = g_local_ip;
} }
if (pServiceName && pServiceName[0]) { if (pServiceName && pServiceName[0]) {
@@ -471,7 +504,7 @@ void install_nicspoof_hooks() {
char ipstr[16]; char ipstr[16];
char maskstr[16]; char maskstr[16];
char gwstr[16]; char gwstr[16];
ip_to_str(k_local_ip, ipstr, sizeof(ipstr)); ip_to_str(g_local_ip, ipstr, sizeof(ipstr));
ip_to_str(k_mask, maskstr, sizeof(maskstr)); ip_to_str(k_mask, maskstr, sizeof(maskstr));
ip_to_str(k_gateway, gwstr, sizeof(gwstr)); ip_to_str(k_gateway, gwstr, sizeof(gwstr));
log_info("network", log_info("network",
@@ -508,7 +541,6 @@ void install_nicspoof_hooks() {
(void *) freeaddrinfo_hook, (void *) freeaddrinfo_hook,
(void **) &freeaddrinfo_orig); (void **) &freeaddrinfo_orig);
/* FreeAddrInfoA is often the same address; only hook if distinct. */
{ {
HMODULE ws = GetModuleHandleA("ws2_32.dll"); HMODULE ws = GetModuleHandleA("ws2_32.dll");
void *faa = nullptr; void *faa = nullptr;
@@ -539,8 +571,75 @@ void install_nicspoof_hooks() {
} }
} }
} // namespace bool ip_in_overlay_range(uint32_t ip) {
const uint32_t net = k_gateway & k_mask;
const uint32_t bcast = net | ~k_mask;
if ((ip & k_mask) != net) {
return false;
}
if (ip == net || ip == bcast || ip == k_gateway ||
ip == k_dns1 || ip == k_dns2) {
return false;
}
return true;
}
void init_impl() {
if (g_cfg.mode == NicSpoofMode::Off) {
return;
}
g_local_ip = g_cfg.local_ip ? g_cfg.local_ip : k_default_local_ip;
if (!ip_in_overlay_range(g_local_ip)) {
char bad[16];
ip_to_str(g_local_ip, bad, sizeof(bad));
log_warning("network",
"NIC spoof: IP {} is outside 10.0.0.0/8 usable range; "
"using 10.100.100.10",
bad);
g_local_ip = k_default_local_ip;
}
g_subnet = g_local_ip & k_mask;
const char *mode_str = "offline";
if (g_cfg.mode == NicSpoofMode::TunnelHost) {
mode_str = "tunnelhost";
} else if (g_cfg.mode == NicSpoofMode::TunnelClient) {
mode_str = "tunnelclient";
}
log_info("network", "NIC spoof mode={}", mode_str);
install_nicspoof_hooks();
if (g_cfg.mode == NicSpoofMode::TunnelHost ||
g_cfg.mode == NicSpoofMode::TunnelClient) {
nicspoof_tunnel_init(g_cfg);
}
}
} // namespace nicspoof_detail
void nicspoof_configure(const NicSpoofConfig &cfg) {
nicspoof_detail::g_cfg = cfg;
}
bool nicspoof_tunnel_enabled() {
return nicspoof_detail::g_cfg.mode == NicSpoofMode::TunnelHost ||
nicspoof_detail::g_cfg.mode == NicSpoofMode::TunnelClient;
}
uint32_t nicspoof_local_ip() {
return nicspoof_detail::g_local_ip;
}
uint32_t nicspoof_mask() {
return nicspoof_detail::k_mask;
}
uint32_t nicspoof_subnet() {
return nicspoof_detail::g_subnet;
}
void nicspoof_init() { void nicspoof_init() {
install_nicspoof_hooks(); nicspoof_detail::init_impl();
} }
+25
View File
@@ -1,3 +1,28 @@
#pragma once #pragma once
#include <cstdint>
#include <string>
enum class NicSpoofMode {
Off,
Offline,
TunnelHost,
TunnelClient,
};
struct NicSpoofConfig {
NicSpoofMode mode = NicSpoofMode::Off;
uint32_t local_ip = 0x0A64640Au; /* 10.100.100.10 */
uint16_t tunnel_port = 51820;
std::string hub_host;
};
void nicspoof_configure(const NicSpoofConfig &cfg);
void nicspoof_init(); void nicspoof_init();
/*! True when mode is TunnelHost or TunnelClient (divert hooks own ws2 sendto/etc.). */
bool nicspoof_tunnel_enabled();
uint32_t nicspoof_local_ip();
uint32_t nicspoof_mask();
uint32_t nicspoof_subnet();
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "nicspoof.h"
void nicspoof_tunnel_init(const NicSpoofConfig &cfg);
+48 -9
View File
@@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include <cmath> #include <cmath>
#include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <shlwapi.h> #include <shlwapi.h>
#include <windows.h> #include <windows.h>
@@ -258,7 +259,7 @@ int main_implementation(int argc, char *argv[]) {
bool load_stubs = false; bool load_stubs = false;
bool netfix_disable = false; bool netfix_disable = false;
bool icmphook_enable = false; bool icmphook_enable = false;
bool nicspoof_enable = false; NicSpoofConfig nicspoof_cfg;
bool lang_disable = false; bool lang_disable = false;
std::string process_priority_str = "high"; std::string process_priority_str = "high";
bool cardio_enabled = false; bool cardio_enabled = false;
@@ -801,8 +802,43 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::EnableICMPHook].value_bool()) { if (options[launcher::Options::EnableICMPHook].value_bool()) {
icmphook_enable = true; icmphook_enable = true;
} }
if (options[launcher::Options::EnableNICSpoof].value_bool()) { if (options[launcher::Options::EnableNICSpoof].is_active()) {
nicspoof_enable = true; const auto &mode = options[launcher::Options::EnableNICSpoof].value_text();
if (mode == "offline" || mode == "/ENABLED") {
nicspoof_cfg.mode = NicSpoofMode::Offline;
} else if (mode == "tunnelhost") {
nicspoof_cfg.mode = NicSpoofMode::TunnelHost;
} else if (mode == "tunnelclient") {
nicspoof_cfg.mode = NicSpoofMode::TunnelClient;
} else {
log_warning("launcher", "unknown -nicspoof value '{}', ignoring", mode);
}
}
if (nicspoof_cfg.mode != NicSpoofMode::Off) {
if (options[launcher::Options::NICSpoofIP].is_active()) {
unsigned a = 0, b = 0, c = 0, d = 0;
char trail = 0;
const auto &ip = options[launcher::Options::NICSpoofIP].value_text();
if (sscanf(ip.c_str(), "%u.%u.%u.%u%c", &a, &b, &c, &d, &trail) == 4 &&
a <= 255 && b <= 255 && c <= 255 && d <= 255) {
nicspoof_cfg.local_ip = (a << 24) | (b << 16) | (c << 8) | d;
} else {
log_warning("launcher", "invalid -nicspoofip '{}'", ip);
}
}
if (options[launcher::Options::NICSpoofHostRealIP].is_active()) {
nicspoof_cfg.hub_host =
options[launcher::Options::NICSpoofHostRealIP].value_text();
}
if (options[launcher::Options::NICSpoofPort].is_active()) {
const uint32_t p =
options[launcher::Options::NICSpoofPort].value_uint32();
if (p == 0 || p > 65535) {
log_warning("launcher", "invalid -nicspoofport {}", p);
} else {
nicspoof_cfg.tunnel_port = static_cast<uint16_t>(p);
}
}
} }
if (options[launcher::Options::DisableACPHook].value_bool()) { if (options[launcher::Options::DisableACPHook].value_bool()) {
lang_disable = true; lang_disable = true;
@@ -2558,14 +2594,17 @@ int main_implementation(int argc, char *argv[]) {
avs::core::load_dll(); avs::core::load_dll();
avs::ea3::load_dll(); avs::ea3::load_dll();
// ICMP emulation (opt-in; before games open raw ICMP sockets) // NIC spoof / matching tunnel first so divert owns overlapping ws2_32
if (icmphook_enable) { // MinHook slots (bind/sendto/recvfrom/...). ICMP then installs only the
icmphook_net_init(); // non-overlapping socket-creation hooks and is reached via icmphook_try_*.
if (nicspoof_cfg.mode != NicSpoofMode::Off) {
nicspoof_configure(nicspoof_cfg);
nicspoof_init();
} }
// NIC spoof (opt-in; fake Ethernet with no real NIC) // ICMP emulation (opt-in; after tunnel so hooks do not collide)
if (nicspoof_enable) { if (icmphook_enable) {
nicspoof_init(); icmphook_net_init();
} }
// net fix // net fix
+50 -5
View File
@@ -3188,12 +3188,57 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = std::invoke([]()
.category = "Network Dev", .category = "Network Dev",
}; };
c[launcher::Options::EnableNICSpoof] = { c[launcher::Options::EnableNICSpoof] = {
.title = "Enable NIC Spoof", .title = "NIC Spoof",
.name = "nicspoof", .name = "nicspoof",
.desc = "Report a fake Ethernet IPv4 adapter (10.57.3.2) so the game can " .desc =
"boot when no real NIC is present. DNS names are mapped to that " "Use with -icmphook.\n\n"
"address. Does not create a real adapter.", "offline: report a fake Ethernet IPv4 adapter and map DNS names to "
.type = OptionType::Bool, "that address so the game can boot when no real NIC is present. "
"Does not create a real adapter.\n\n"
"tunnelhost: same fake NIC, plus listen for a matching tunnel "
"from a peer.\n\n"
"tunnelclient: same fake NIC, plus connect to a tunnel host. "
"Set -nicspoofhostrealip.",
.type = OptionType::Enum,
.category = "Network Dev",
.elements = {
{"offline", ""},
{"tunnelhost", ""},
{"tunnelclient", ""},
},
};
c[launcher::Options::NICSpoofIP] = {
.title = "NIC Spoof IP",
.name = "nicspoofip",
.desc =
"Overlay IPv4 for the fake adapter. Used when NIC Spoof is not Default. "
"If unset, 10.100.100.10 is used.\n\n"
"Must be on the same subnet as gateway 10.100.100.1 with mask "
"255.0.0.0 (10.0.0.0/8). Examples: 10.100.100.10, 10.0.0.10. "
"Do not use 10.0.0.0, 10.255.255.255, the gateway 10.100.100.1, "
"or DNS 10.2.1.10 / 10.2.1.30. Host and client must use different IPs.",
.type = OptionType::Text,
.setting_name = "10.100.100.10",
.category = "Network Dev",
};
c[launcher::Options::NICSpoofHostRealIP] = {
.title = "NIC Spoof Tunnel Host",
.name = "nicspoofhostrealip",
.desc =
"Real IP address or hostname of the tunnel host. Required for "
"tunnel client mode. Not used for offline or tunnel host.",
.type = OptionType::Text,
.setting_name = "192.168.1.10",
.category = "Network Dev",
};
c[launcher::Options::NICSpoofPort] = {
.title = "NIC Spoof Tunnel Port",
.name = "nicspoofport",
.desc =
"UDP port for the matching tunnel. Host listens on this port; "
"client connects to it. If unset, 51820 is used.",
.type = OptionType::Integer,
.setting_name = "51820",
.category = "Network Dev", .category = "Network Dev",
}; };
c[launcher::Options::AutoElevate] = { c[launcher::Options::AutoElevate] = {
+3
View File
@@ -317,6 +317,9 @@ namespace launcher {
DisableHighResTimer, DisableHighResTimer,
EnableICMPHook, EnableICMPHook,
EnableNICSpoof, EnableNICSpoof,
NICSpoofIP,
NICSpoofHostRealIP,
NICSpoofPort,
AutoElevate, AutoElevate,
CfgForceSoftwareRender, CfgForceSoftwareRender,
OBSWebSocketEnabled, OBSWebSocketEnabled,