Improve: client/server - stable client ID via MachineGuid+path (V2)

This commit is contained in:
yuanyuanxiang
2026-05-06 21:32:06 +02:00
parent 0aa75882d1
commit 2c5b5ad628
5 changed files with 282 additions and 30 deletions

View File

@@ -690,6 +690,49 @@ std::string getUsername()
return u ? u : "?";
}
// 读取 systemd / dbus 维护的 machine-id与 Windows MachineGuid 等价)
// /etc/machine-id 在系统首次启动时生成的随机 32 字符 hex GUID。
// 对应 Windows: HKLM\Software\Microsoft\Cryptography\MachineGuid。
// 重装系统才会变;同一镜像 dd 出来的多机会撞——但规范的批量部署
// 工具 (cloud-init / kickstart) 会重置它。
static std::string getMachineId()
{
// 优先 /etc/machine-id某些精简系统可能放在 /var/lib/dbus/machine-id
const char* paths[] = { "/etc/machine-id", "/var/lib/dbus/machine-id" };
for (const char* p : paths) {
std::ifstream f(p);
if (!f.is_open()) continue;
std::string id;
std::getline(f, id);
// 去掉尾部空白和换行
while (!id.empty() && (id.back() == '\n' || id.back() == '\r' ||
id.back() == ' ' || id.back() == '\t')) {
id.pop_back();
}
if (!id.empty()) return id;
}
return std::string();
}
// 路径归一化Linux 版):解析符号链接 + 转小写
// realpath 等价于 Windows 的 GetLongPathName把 /usr/local/bin/../foo 这种
// 折回到规范形式;小写化避免大小写差异引起 ID 不同Linux 文件系统本身大小写
// 敏感,但保持与 Windows V2 算法一致的归一化策略,跨端一致性优先)。
static std::string normalizeExePathLower(const std::string& path)
{
char resolved[PATH_MAX] = {};
std::string out;
if (realpath(path.c_str(), resolved) != nullptr) {
out = resolved;
} else {
out = path; // 解析失败(罕见):用原值
}
for (auto& c : out) {
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
}
return out;
}
// 获取屏幕分辨率字符串(格式 "显示器数:宽*高"
std::string getScreenResolution()
{
@@ -1010,17 +1053,30 @@ int main(int argc, char* argv[])
logInfo.AddReserved(getUsername().c_str()); // [13] RES_USERNAME
logInfo.AddReserved(getuid() == 0 ? 1 : 0); // [14] RES_ISADMIN
logInfo.AddReserved(getScreenResolution().c_str()); // [15] RES_RESOLUTION
// 计算客户端 ID与服务端 CONTEXT_OBJECT::CalculateID 相同算法)
// 格式: pubIP|hostname|os|cpu|path
char cpuStr[32];
snprintf(cpuStr, sizeof(cpuStr), "%uMHz", logInfo.dwCPUMHz);
std::string idInput = (pubIP.empty() ? "?" : pubIP) + "|" +
hostname + "|" +
distro + "|" +
cpuStr + "|" +
exePath;
g_myClientID = XXH64(idInput.c_str(), idInput.length(), 0);
Mprintf("Calculated clientID: %llu (from: %s)\n", g_myClientID, idInput.c_str());
// V2 ID 算法machine-id + 归一化路径
// - 同机同程序路径永远同 ID不依赖 IP/hostname/distro/CPU 漂移)
// - 局域网多机即便同镜像cloud-init/kickstart 会让 machine-id 各不同
// - machine-id 读取失败时退化到老算法pubIP|hostname|distro|cpu|path保兼容
std::string machineId = getMachineId();
if (!machineId.empty()) {
std::string normPath = normalizeExePathLower(exePath);
std::string idInput = machineId + "|" + normPath;
g_myClientID = XXH64(idInput.c_str(), idInput.length(), 0);
Mprintf("Calculated clientID(v2): %llu (machineId=%s, path=%s)\n",
g_myClientID, machineId.c_str(), normPath.c_str());
} else {
// 老算法兜底(与服务端 CONTEXT_OBJECT::CalculateID 相同算法)
// 格式: pubIP|hostname|os|cpu|path
char cpuStr[32];
snprintf(cpuStr, sizeof(cpuStr), "%uMHz", logInfo.dwCPUMHz);
std::string idInput = (pubIP.empty() ? "?" : pubIP) + "|" +
hostname + "|" +
distro + "|" +
cpuStr + "|" +
exePath;
g_myClientID = XXH64(idInput.c_str(), idInput.length(), 0);
Mprintf("Calculated clientID(v1 fallback): %llu (machine-id 读取失败)\n", g_myClientID);
}
logInfo.AddReserved(std::to_string(g_myClientID).c_str()); // [16] RES_CLIENT_ID
logInfo.AddReserved((int)getpid()); // [17] RES_PID