#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();
}
