2026-01-02 20:00:18 +01:00
|
|
|
|
#pragma once
|
2025-06-28 16:50:01 +08:00
|
|
|
|
#include <string>
|
|
|
|
|
|
#include <winsock2.h>
|
|
|
|
|
|
#include <iphlpapi.h>
|
|
|
|
|
|
#include <ws2tcpip.h>
|
|
|
|
|
|
#include <WinInet.h>
|
|
|
|
|
|
#include "logger.h"
|
|
|
|
|
|
#include "jsoncpp/json.h"
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef _WIN64
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
|
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
|
|
|
|
|
|
#else
|
|
|
|
|
|
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#else
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
|
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
|
|
|
|
|
|
#else
|
|
|
|
|
|
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#pragma comment(lib, "wininet.lib")
|
|
|
|
|
|
#pragma comment(lib, "Iphlpapi.lib")
|
|
|
|
|
|
|
2025-10-15 04:32:59 +08:00
|
|
|
|
inline void splitIpPort(const std::string& input, std::string& ip, std::string& port)
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t pos = input.find(':');
|
|
|
|
|
|
if (pos != std::string::npos) {
|
|
|
|
|
|
ip = input.substr(0, pos);
|
|
|
|
|
|
port = input.substr(pos + 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ip = input;
|
|
|
|
|
|
port = "";
|
|
|
|
|
|
}
|
2025-06-28 16:50:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-02 20:00:18 +01:00
|
|
|
|
* IPConverter: IP 操作类,用于获取公网IP,获取IP对应的地理位置等.
|
|
|
|
|
|
* 目前是通过调用公开的互联网API完成,假如该查询网站不可访问则需要重新适配.
|
2025-06-28 16:50:01 +08:00
|
|
|
|
*/
|
|
|
|
|
|
class IPConverter
|
|
|
|
|
|
{
|
|
|
|
|
|
public:
|
2025-10-15 04:32:59 +08:00
|
|
|
|
std::string IPtoAddress(const std::string& ip)
|
|
|
|
|
|
{
|
|
|
|
|
|
return "implement me";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-02 20:00:18 +01:00
|
|
|
|
* 判断给定的 IP 地址是否是局域网(内网)IP
|
|
|
|
|
|
* @param ipAddress IP 地址字符串(如 "192.168.1.1")
|
|
|
|
|
|
* @return 如果是局域网 IP,返回 true; 否则返回 false
|
2025-10-15 04:32:59 +08:00
|
|
|
|
*/
|
|
|
|
|
|
bool IsPrivateIP(const std::string& ipAddress)
|
|
|
|
|
|
{
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 将 IP 地址字符串转换为二进制格式
|
2025-10-15 04:32:59 +08:00
|
|
|
|
in_addr addr;
|
|
|
|
|
|
if (inet_pton(AF_INET, ipAddress.c_str(), &addr) != 1) {
|
|
|
|
|
|
Mprintf("Invalid IP address: %s\n", ipAddress.c_str());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 将二进制 IP 地址转换为无符号整数
|
2025-10-15 04:32:59 +08:00
|
|
|
|
unsigned long ip = ntohl(addr.s_addr);
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 检查 IP 地址是否在局域网范围内
|
2025-10-15 04:32:59 +08:00
|
|
|
|
if ((ip >= 0x0A000000 && ip <= 0x0AFFFFFF) || // 10.0.0.0/8
|
|
|
|
|
|
(ip >= 0xAC100000 && ip <= 0xAC1FFFFF) || // 172.16.0.0/12
|
|
|
|
|
|
(ip >= 0xC0A80000 && ip <= 0xC0A8FFFF) || // 192.168.0.0/16
|
|
|
|
|
|
(ip >= 0x7F000000 && ip <= 0x7FFFFFFF)) { // 127.0.0.0/8
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 获取本机地理位置
|
2025-10-15 04:32:59 +08:00
|
|
|
|
std::string GetLocalLocation()
|
|
|
|
|
|
{
|
|
|
|
|
|
return GetGeoLocation(getPublicIP());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 获取 IP 地址地理位置(基于[ipinfo.io])
|
2025-10-15 04:32:59 +08:00
|
|
|
|
std::string GetGeoLocation(const std::string& IP)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IP.empty()) return "";
|
|
|
|
|
|
|
|
|
|
|
|
std::string ip = IP;
|
|
|
|
|
|
if (isLocalIP(ip)) {
|
|
|
|
|
|
ip = getPublicIP();
|
|
|
|
|
|
if (ip.empty())
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HINTERNET hInternet, hConnect;
|
|
|
|
|
|
DWORD bytesRead;
|
|
|
|
|
|
std::string readBuffer;
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 初始化 WinINet
|
2025-10-15 04:32:59 +08:00
|
|
|
|
hInternet = InternetOpen("IP Geolocation", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
|
|
|
|
|
if (hInternet == NULL) {
|
|
|
|
|
|
Mprintf("InternetOpen failed! %d\n", GetLastError());
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 创建 HTTP 请求句柄
|
2025-10-15 04:32:59 +08:00
|
|
|
|
std::string url = "http://ipinfo.io/" + ip + "/json";
|
|
|
|
|
|
hConnect = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_RELOAD, 0);
|
|
|
|
|
|
if (hConnect == NULL) {
|
|
|
|
|
|
Mprintf("InternetOpenUrlA failed! %d\n", GetLastError());
|
|
|
|
|
|
InternetCloseHandle(hInternet);
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 读取返回的内容
|
2025-10-15 04:32:59 +08:00
|
|
|
|
char buffer[4096];
|
|
|
|
|
|
while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
|
|
|
|
|
|
readBuffer.append(buffer, bytesRead);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 解析 JSON 响应
|
2025-10-15 04:32:59 +08:00
|
|
|
|
Json::Value jsonData;
|
|
|
|
|
|
Json::Reader jsonReader;
|
|
|
|
|
|
std::string location;
|
|
|
|
|
|
|
|
|
|
|
|
if (jsonReader.parse(readBuffer, jsonData)) {
|
|
|
|
|
|
std::string country = jsonData["country"].asString();
|
|
|
|
|
|
std::string city = jsonData["city"].asString();
|
2026-01-02 20:00:18 +01:00
|
|
|
|
std::string loc = jsonData["loc"].asString(); // 经纬度信息
|
2025-10-15 04:32:59 +08:00
|
|
|
|
if (city.empty() && country.empty()) {
|
|
|
|
|
|
} else if (city.empty()) {
|
|
|
|
|
|
location = country;
|
|
|
|
|
|
} else if (country.empty()) {
|
|
|
|
|
|
location = city;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
location = city + ", " + country;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (location.empty() && IsPrivateIP(ip)) {
|
|
|
|
|
|
location = "Local Area Network";
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Mprintf("Failed to parse JSON response: %s.\n", readBuffer.c_str());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 关闭句柄
|
2025-10-15 04:32:59 +08:00
|
|
|
|
InternetCloseHandle(hConnect);
|
|
|
|
|
|
InternetCloseHandle(hInternet);
|
|
|
|
|
|
|
|
|
|
|
|
return location;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isLoopbackAddress(const std::string& ip)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (ip == "127.0.0.1" || ip == "::1");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool isLocalIP(const std::string& ip)
|
|
|
|
|
|
{
|
2026-01-02 20:00:18 +01:00
|
|
|
|
if (isLoopbackAddress(ip)) return true; // 先检查回环地址
|
2025-10-15 04:32:59 +08:00
|
|
|
|
|
|
|
|
|
|
ULONG outBufLen = 15000;
|
|
|
|
|
|
IP_ADAPTER_ADDRESSES* pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
|
|
|
|
|
|
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
|
|
|
|
|
|
free(pAddresses);
|
|
|
|
|
|
pAddresses = (IP_ADAPTER_ADDRESSES*)malloc(outBufLen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == NO_ERROR) {
|
|
|
|
|
|
for (IP_ADAPTER_ADDRESSES* pCurrAddresses = pAddresses; pCurrAddresses; pCurrAddresses = pCurrAddresses->Next) {
|
|
|
|
|
|
for (IP_ADAPTER_UNICAST_ADDRESS* pUnicast = pCurrAddresses->FirstUnicastAddress; pUnicast; pUnicast = pUnicast->Next) {
|
|
|
|
|
|
char addressBuffer[INET6_ADDRSTRLEN] = { 0 };
|
|
|
|
|
|
getnameinfo(pUnicast->Address.lpSockaddr, pUnicast->Address.iSockaddrLength, addressBuffer, sizeof(addressBuffer), nullptr, 0, NI_NUMERICHOST);
|
|
|
|
|
|
|
|
|
|
|
|
if (ip == addressBuffer) {
|
|
|
|
|
|
free(pAddresses);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
free(pAddresses);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 获取公网IP, 获取失败返回空
|
2025-10-15 04:32:59 +08:00
|
|
|
|
std::string getPublicIP()
|
|
|
|
|
|
{
|
|
|
|
|
|
clock_t t = clock();
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 多个候选查询源
|
2025-10-15 04:32:59 +08:00
|
|
|
|
static const std::vector<std::string> urls = {
|
2026-01-02 20:00:18 +01:00
|
|
|
|
"https://checkip.amazonaws.com", // 全球最稳
|
|
|
|
|
|
"https://api.ipify.org", // 主流高可用
|
|
|
|
|
|
"https://ipinfo.io/ip", // 备用方案
|
|
|
|
|
|
"https://icanhazip.com", // 轻量快速
|
|
|
|
|
|
"https://ifconfig.me/ip" // 末位兜底
|
2025-10-15 04:32:59 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 打开 WinINet 会话
|
2025-10-15 04:32:59 +08:00
|
|
|
|
HINTERNET hInternet = InternetOpenA("Mozilla/5.0", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
|
|
|
|
|
if (!hInternet) {
|
|
|
|
|
|
Mprintf("InternetOpen failed. cost %d ms.\n", clock() - t);
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 设置超时 (毫秒)
|
|
|
|
|
|
DWORD timeout = 3000; // 3 秒
|
2025-10-15 04:32:59 +08:00
|
|
|
|
InternetSetOptionA(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
|
|
|
|
|
|
InternetSetOptionA(hInternet, INTERNET_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
|
|
|
|
|
|
InternetSetOptionA(hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
|
|
|
|
|
|
|
|
|
|
|
|
std::string result;
|
|
|
|
|
|
char buffer[2048];
|
|
|
|
|
|
DWORD bytesRead = 0;
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 轮询不同 IP 查询源
|
2025-10-15 04:32:59 +08:00
|
|
|
|
for (const auto& url : urls) {
|
|
|
|
|
|
HINTERNET hConnect = InternetOpenUrlA(
|
|
|
|
|
|
hInternet, url.c_str(), NULL, 0,
|
|
|
|
|
|
INTERNET_FLAG_RELOAD | INTERNET_FLAG_SECURE | INTERNET_FLAG_NO_CACHE_WRITE,
|
|
|
|
|
|
0
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!hConnect) {
|
2026-01-02 20:00:18 +01:00
|
|
|
|
continue; // 当前源失败,尝试下一个
|
2025-10-15 04:32:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
memset(buffer, 0, sizeof(buffer));
|
|
|
|
|
|
if (InternetReadFile(hConnect, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) {
|
|
|
|
|
|
result.assign(buffer, bytesRead);
|
|
|
|
|
|
|
2026-01-02 20:00:18 +01:00
|
|
|
|
// 去除换行符和空格
|
2025-10-15 04:32:59 +08:00
|
|
|
|
while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' '))
|
|
|
|
|
|
result.pop_back();
|
|
|
|
|
|
|
|
|
|
|
|
InternetCloseHandle(hConnect);
|
2026-01-02 20:00:18 +01:00
|
|
|
|
break; // 成功获取,停止尝试
|
2025-10-15 04:32:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
InternetCloseHandle(hConnect);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
InternetCloseHandle(hInternet);
|
|
|
|
|
|
|
|
|
|
|
|
Mprintf("getPublicIP %s cost %d ms.\n", result.empty() ? "failed" : "succeed", clock() - t);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2025-06-28 16:50:01 +08:00
|
|
|
|
};
|