Refactor: extract Linux/macOS client shared code into common

This commit is contained in:
yuanyuanxiang
2026-05-10 09:49:07 +02:00
parent 70354e244c
commit d46176f4ef
9 changed files with 443 additions and 424 deletions

View File

@@ -32,6 +32,10 @@
#include "common/logger.h"
#define XXH_INLINE_ALL
#include "common/xxhash.h"
#include "common/rtt_estimator.h"
#include "common/client_auth_state.h"
#include "common/posix_net_helpers.h"
#include "common/sub_conn_thread.h"
#include "LinuxConfig.h"
int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength);
@@ -46,14 +50,7 @@ static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重
// 客户端 IDV2 文件传输需要)
uint64_t g_myClientID = 0;
// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验
// 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。
// 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。
// g_settingsVerified 在 DataProcessIO 线程写、心跳循环main 线程)和 DataProcess 自身读,
// 跨线程访问 → 用 atomic 保证可见性。
std::string g_loginMsg;
time_t g_loginTime = 0;
std::atomic<bool> g_settingsVerified{false};
// 服务端身份校验全局状态已抽到 common/client_auth_state.hnamespace ClientAuth
// ============== UTF-8 → GBK 编码转换(服务端为 Windows GBK 环境) ==============
@@ -311,142 +308,55 @@ private:
};
// ============== 心跳保活 & RTT 估算 ==============
// RTT 估算器(参考 RFC 6298 算法,与 Windows 端 KernelManager 一致)
struct RttEstimator {
double srtt = 0.0; // 平滑 RTT (秒)
double rttvar = 0.0; // RTT 波动 (秒)
double rto = 0.0; // 超时时间 (秒)
bool initialized = false;
void update_from_sample(double rtt_ms)
{
// 过滤异常值RTT应在合理范围内 (0, 30000] 毫秒
if (rtt_ms <= 0 || rtt_ms > 30000)
return;
const double alpha = 1.0 / 8;
const double beta = 1.0 / 4;
double rtt = rtt_ms / 1000.0;
if (!initialized) {
srtt = rtt;
rttvar = rtt / 2.0;
rto = srtt + 4.0 * rttvar;
initialized = true;
} else {
rttvar = (1.0 - beta) * rttvar + beta * std::fabs(srtt - rtt);
srtt = (1.0 - alpha) * srtt + alpha * rtt;
rto = srtt + 4.0 * rttvar;
}
// 限制最小 RTORFC 6298 推荐 1 秒)
if (rto < 1.0) rto = 1.0;
}
};
RttEstimator g_rttEstimator;
int g_heartbeatInterval = 5; // 默认心跳间隔(秒),可被服务端 CMD_MASTERSETTING 更新
// RttEstimator + g_rttEstimator + g_heartbeatInterval 已抽到 common/rtt_estimator.h
// PTYHandler moved to common/PTYHandler.h (shared between Linux and macOS)
void* ShellworkingThread(void* param)
void* ShellworkingThread(void* /*param*/)
{
try {
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
void* clientAddr = ClientObject.get();
Mprintf(">>> Enter ShellworkingThread [%p]\n", clientAddr);
// 子连接:开启 auth。Linux IOCPClient 不带 m_conn显式传入 g_myClientID。
ClientObject->EnableSubConnAuth(true, g_myClientID);
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
std::unique_ptr<PTYHandler> handler(new PTYHandler(ClientObject.get()));
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
RunSubConnThread<PTYHandler>(
"ShellworkingThread",
[](IOCPClient* c) { return std::unique_ptr<PTYHandler>(new PTYHandler(c)); },
[](IOCPClient* c, PTYHandler*) {
BYTE bToken = TOKEN_TERMINAL_START;
ClientObject->Send2Server((char*)&bToken, 1);
Mprintf(">>> ShellworkingThread [%p] Send: TOKEN_TERMINAL_START\n", clientAddr);
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(1000);
// 清除回调,防止重连线程访问已销毁的 handler
ClientObject->setManagerCallBack(nullptr, nullptr, nullptr);
}
Mprintf(">>> Leave ShellworkingThread [%p]\n", clientAddr);
} catch (const std::exception& e) {
Mprintf("*** ShellworkingThread exception: %s ***\n", e.what());
}
c->Send2Server((char*)&bToken, 1);
Mprintf(">>> ShellworkingThread [%p] Send: TOKEN_TERMINAL_START\n", c);
});
return NULL;
}
void* ScreenworkingThread(void* param)
void* ScreenworkingThread(void* /*param*/)
{
try {
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
void* clientAddr = ClientObject.get();
Mprintf(">>> Enter ScreenworkingThread [%p]\n", clientAddr);
// 子连接:开启 auth。Linux IOCPClient 不带 m_conn显式传入 g_myClientID。
ClientObject->EnableSubConnAuth(true, g_myClientID);
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
std::unique_ptr<ScreenHandler> handler(new ScreenHandler(ClientObject.get()));
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
RunSubConnThread<ScreenHandler>(
"ScreenworkingThread",
[](IOCPClient* c) { return std::unique_ptr<ScreenHandler>(new ScreenHandler(c)); },
[](IOCPClient* c, ScreenHandler* h) {
// 连接后立即发送完整的 BITMAPINFO 包(与 Windows 端 ScreenManager 流程一致)
handler->SendBitmapInfo();
Mprintf(">>> ScreenworkingThread [%p] Send: TOKEN_BITMAPINFO\n", clientAddr);
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(1000);
// 清除回调,防止重连线程访问已销毁的 handler
ClientObject->setManagerCallBack(nullptr, nullptr, nullptr);
}
Mprintf(">>> Leave ScreenworkingThread [%p]\n", clientAddr);
} catch (const std::exception& e) {
Mprintf("*** ScreenworkingThread exception: %s ***\n", e.what());
}
h->SendBitmapInfo();
Mprintf(">>> ScreenworkingThread [%p] Send: TOKEN_BITMAPINFO\n", c);
});
return NULL;
}
void* SystemManagerThread(void* param)
void* SystemManagerThread(void* /*param*/)
{
try {
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
void* clientAddr = ClientObject.get();
Mprintf(">>> Enter SystemManagerThread [%p]\n", clientAddr);
// 子连接:开启 auth。Linux IOCPClient 不带 m_conn显式传入 g_myClientID。
ClientObject->EnableSubConnAuth(true, g_myClientID);
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
std::unique_ptr<SystemManager> handler(new SystemManager(ClientObject.get()));
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
Mprintf(">>> SystemManagerThread [%p] Send: TOKEN_PSLIST\n", clientAddr);
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(1000);
// 清除回调,防止重连线程访问已销毁的 handler
ClientObject->setManagerCallBack(nullptr, nullptr, nullptr);
}
Mprintf(">>> Leave SystemManagerThread [%p]\n", clientAddr);
} catch (const std::exception& e) {
Mprintf("*** SystemManagerThread exception: %s ***\n", e.what());
}
RunSubConnThread<SystemManager>(
"SystemManagerThread",
[](IOCPClient* c) { return std::unique_ptr<SystemManager>(new SystemManager(c)); },
[](IOCPClient* c, SystemManager*) {
Mprintf(">>> SystemManagerThread [%p] Send: TOKEN_PSLIST\n", c);
});
return NULL;
}
void* FileManagerThread(void* param)
void* FileManagerThread(void* /*param*/)
{
try {
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
void* clientAddr = ClientObject.get();
Mprintf(">>> Enter FileManagerThread [%p]\n", clientAddr);
// 子连接:开启 auth。Linux IOCPClient 不带 m_conn显式传入 g_myClientID。
ClientObject->EnableSubConnAuth(true, g_myClientID);
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
std::unique_ptr<FileManager> handler(new FileManager(ClientObject.get()));
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
Mprintf(">>> FileManagerThread [%p] Send: TOKEN_DRIVE_LIST\n", clientAddr);
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
Sleep(1000);
// 清除回调,防止重连线程访问已销毁的 handler
ClientObject->setManagerCallBack(nullptr, nullptr, nullptr);
}
Mprintf(">>> Leave FileManagerThread [%p]\n", clientAddr);
} catch (const std::exception& e) {
Mprintf("*** FileManagerThread exception: %s ***\n", e.what());
}
RunSubConnThread<FileManager>(
"FileManagerThread",
[](IOCPClient* c) { return std::unique_ptr<FileManager>(new FileManager(c)); },
[](IOCPClient* c, FileManager*) {
Mprintf(">>> FileManagerThread [%p] Send: TOKEN_DRIVE_LIST\n", c);
});
return NULL;
}
@@ -455,11 +365,9 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
if (szBuffer == nullptr || ulLength == 0)
return TRUE;
// 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING校验本身
// 其它命令一律静默忽略——既防止未授权服务端 spawn 子连接线程做 DoS
// 也防止它发 COMMAND_BYE 之类把客户端进程关掉。
if (!g_settingsVerified.load(std::memory_order_acquire) &&
szBuffer[0] != CMD_MASTERSETTING) {
// 服务端身份未通过校验前,仅放行 CMD_MASTERSETTING校验本身详见
// common/client_auth_state.h ClientAuth::IsCommandAllowed 的注释。
if (!ClientAuth::IsCommandAllowed(szBuffer[0])) {
return TRUE;
}
@@ -494,25 +402,10 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
}
}
} else if (szBuffer[0] == CMD_MASTERSETTING) {
int settingSize = ulLength - 1;
// 强制要求完整 MasterSettings包含 Signature 字段)。包不完整 → 视为本次响应异常,
// 不更新 g_settingsVerified让心跳循环的 30s 超时自然把本次连接断开重连
if (settingSize < (int)sizeof(MasterSettings)) {
return TRUE;
MasterSettings settings;
if (!ClientAuth::HandleMasterSettings(szBuffer + 1, (int)ulLength - 1, &settings)) {
return TRUE; // 包不全或签名失败:让 30s 超时兜底重连
}
MasterSettings settings = {};
memcpy(&settings, szBuffer + 1, sizeof(MasterSettings));
// 服务端身份校验:用 g_loginMsg (= szStartTime + "|" + clientID) 与 settings.Signature
// 验证签名。失败 → 不立即退出,让超时兜底+重连逻辑处理(避免合法服务端临时
// 抖动导致进程退出)
extern bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature);
std::string sig((char*)settings.Signature, (char*)settings.Signature + sizeof(settings.Signature));
if (!verifyMessage("", (BYTE*)g_loginMsg.data(), (int)g_loginMsg.length(), sig)) {
return TRUE; // 同上,不立即退出
}
g_settingsVerified.store(true, std::memory_order_release);
if (settings.ReportInterval > 0)
g_heartbeatInterval = settings.ReportInterval;
Mprintf("** [%p] MasterSettings: ReportInterval=%ds ***\n", user, g_heartbeatInterval);
@@ -811,87 +704,14 @@ std::string getScreenResolution()
return "0:0*0";
}
// 执行命令并返回输出
static std::string execCmd(const std::string& cmd)
{
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe) return "";
char buf[4096];
std::string result;
while (fgets(buf, sizeof(buf), pipe.get())) {
result += buf;
}
// 去除尾部空白
while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' '))
result.pop_back();
return result;
}
// HTTP GET 请求(优先 curl备选 wget
static std::string httpGet(const std::string& url, int timeoutSec = 5)
{
std::string t = std::to_string(timeoutSec);
// 优先使用 curl
std::string r = execCmd("curl -s --max-time " + t + " \"" + url + "\" 2>/dev/null");
if (!r.empty()) return r;
// 备选 wgetUbuntu 默认自带)
r = execCmd("wget -qO- --timeout=" + t + " \"" + url + "\" 2>/dev/null");
return r;
}
// 获取公网 IP轮询多个查询源与 Windows 端一致)
std::string getPublicIP()
{
static const char* urls[] = {
"https://checkip.amazonaws.com",
"https://api.ipify.org",
"https://ipinfo.io/ip",
"https://icanhazip.com",
"https://ifconfig.me/ip",
};
for (auto& url : urls) {
std::string ip = httpGet(url, 3);
// 简单校验:非空且看起来像 IP含有点号长度合理
if (!ip.empty() && ip.find('.') != std::string::npos && ip.size() <= 45) {
Mprintf("getPublicIP: %s (from %s)\n", ip.c_str(), url);
return ip;
}
}
Mprintf("getPublicIP: all sources failed\n");
return "";
}
// 从 JSON 字符串中提取指定 key 的值(简易解析,不依赖 jsoncpp
// 支持格式: "key": "value" 或 "key":"value"
static std::string jsonExtract(const std::string& json, const std::string& key)
{
std::string needle = "\"" + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return "";
pos = json.find(':', pos + needle.size());
if (pos == std::string::npos) return "";
pos = json.find('"', pos + 1);
if (pos == std::string::npos) return "";
size_t end = json.find('"', pos + 1);
if (end == std::string::npos) return "";
return json.substr(pos + 1, end - pos - 1);
}
// 获取 IP 地理位置(通过 ipinfo.io与 Windows 端一致)
std::string getGeoLocation(const std::string& ip)
{
if (ip.empty()) return "";
std::string json = httpGet("https://ipinfo.io/" + ip + "/json", 5);
if (json.empty()) return "";
std::string country = jsonExtract(json, "country");
std::string city = jsonExtract(json, "city");
if (city.empty() && country.empty()) return "";
if (city.empty()) return country;
if (country.empty()) return city;
return city + ", " + country;
}
// execCmd / httpGet / getPublicIP / jsonExtract / getGeoLocation 已抽到
// common/posix_net_helpers.hnamespace PosixNet。下面保留同名 wrapper避免
// 改动调用点。Linux 历史调用风格保留:自由函数无 namespace。
static inline std::string execCmd(const std::string& cmd) { return PosixNet::execCmd(cmd); }
static inline std::string httpGet(const std::string& url, int timeoutSec = 5) { return PosixNet::httpGet(url, timeoutSec); }
static inline std::string jsonExtract(const std::string& json, const std::string& key) { return PosixNet::jsonExtract(json, key); }
inline std::string getPublicIP() { return PosixNet::getPublicIP(); }
inline std::string getGeoLocation(const std::string& ip){ return PosixNet::getGeoLocation(ip); }
// ============== 守护进程 ==============
@@ -1128,7 +948,7 @@ int main(int argc, char* argv[])
logInfo.AddReserved(getFileSize(exePath).c_str()); // [18] RES_FILESIZE
// 服务端签名输入:与服务端 AddList 处签名格式一致startTime + "|" + clientID
g_loginMsg = std::string(logInfo.szStartTime) + "|" + std::to_string(g_myClientID);
ClientAuth::g_loginMsg = std::string(logInfo.szStartTime) + "|" + std::to_string(g_myClientID);
// 初始化用户活动检测器(用于心跳包中的 ActiveWnd 字段)
ActivityChecker activityChecker;
@@ -1143,8 +963,7 @@ int main(int argc, char* argv[])
}
// 进入新连接,重置服务端身份校验状态
g_loginTime = time(nullptr);
g_settingsVerified.store(false, std::memory_order_release);
ClientAuth::OnNewConnection();
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
@@ -1174,10 +993,9 @@ int main(int argc, char* argv[])
if (!ClientObject->IsRunning() || !ClientObject->IsConnected() || g_bExit != S_CLIENT_NORMAL)
break;
// 登录后 30 秒内必须收到并通过 MasterSettings 校验。失败显式断开本连接
// 让外层重连。永不退出进程(客户端是常驻服务,服务端不可达不应该让其自杀)。
if (!g_settingsVerified.load(std::memory_order_acquire) && g_loginTime > 0 &&
time(nullptr) - g_loginTime > 30) {
// 30 秒内通过 MasterSettings 校验 → 断开本连接让外层重连,
// 永不退出进程(详见 ClientAuth::IsTimedOut 注释)。
if (ClientAuth::IsTimedOut()) {
ClientObject->Disconnect(); // 关闭 socket防止重连时 fd 泄漏
break;
}