Refactor: extract Linux/macOS client shared code into common
This commit is contained in:
230
macos/main.mm
230
macos/main.mm
@@ -19,6 +19,10 @@
|
||||
#import "../client/IOCPClient.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"
|
||||
#import "Permissions.h"
|
||||
#import "ScreenHandler.h"
|
||||
#import "InputHandler.h"
|
||||
@@ -36,13 +40,7 @@ static std::atomic<bool> g_needResendLogin(false); // 分组变更后需要重
|
||||
// Client ID (calculated from system info, used by ScreenHandler)
|
||||
uint64_t g_myClientID = 0;
|
||||
|
||||
// 服务端身份校验:登录消息(签名输入),登录时间,是否已通过校验
|
||||
// 客户端是常驻服务——服务端可能频繁重启 / 长期离线 / 临时不可达,这些都不应让进程退出。
|
||||
// 校验失败仅作"本次连接不可信"处理:断开本连接 + 让外层重连。
|
||||
// g_settingsVerified 跨线程访问 → 用 atomic 保证可见性。
|
||||
std::string g_loginMsg;
|
||||
time_t g_loginTime = 0;
|
||||
std::atomic<bool> g_settingsVerified{false};
|
||||
// 服务端身份校验全局状态已抽到 common/client_auth_state.h(namespace ClientAuth)
|
||||
|
||||
// 远程地址:当前为写死状态,如需调试,请按实际情况修改
|
||||
CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_MACOS };
|
||||
@@ -446,49 +444,12 @@ static bool hasCameraDevice()
|
||||
// ============== Public IP ==============
|
||||
|
||||
// Execute command and return output
|
||||
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;
|
||||
}
|
||||
// Trim trailing whitespace
|
||||
while (!result.empty() && (result.back() == '\n' || result.back() == '\r' || result.back() == ' '))
|
||||
result.pop_back();
|
||||
return result;
|
||||
}
|
||||
|
||||
// HTTP GET using curl (macOS has curl built-in)
|
||||
static std::string httpGet(const std::string& url, int timeoutSec = 5)
|
||||
{
|
||||
std::string t = std::to_string(timeoutSec);
|
||||
return execCmd("curl -s --max-time " + t + " \"" + url + "\" 2>/dev/null");
|
||||
}
|
||||
|
||||
// Get public IP (try multiple sources)
|
||||
static 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);
|
||||
// Validate: non-empty, contains dot, reasonable length
|
||||
if (!ip.empty() && ip.find('.') != std::string::npos && ip.size() <= 45) {
|
||||
NSLog(@"getPublicIP: %s (from %s)", ip.c_str(), url);
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
NSLog(@"getPublicIP: all sources failed");
|
||||
return "";
|
||||
}
|
||||
// execCmd / httpGet / getPublicIP 已抽到 common/posix_net_helpers.h(namespace PosixNet)。
|
||||
// 这里保留同名 wrapper 避免改动调用点。Linux 端额外的 jsonExtract / getGeoLocation
|
||||
// macOS 暂未使用,需要时直接用 PosixNet:: 命名空间访问。
|
||||
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 getPublicIP() { return PosixNet::getPublicIP(); }
|
||||
|
||||
// ============== Install Time (persistent storage) ==============
|
||||
|
||||
@@ -635,7 +596,7 @@ static void fillLoginInfo(LOGIN_INFOR& info)
|
||||
info.AddReserved(std::to_string(g_myClientID).c_str());
|
||||
|
||||
// 服务端签名输入:与服务端 AddList 处签名格式一致(startTime + "|" + clientID)
|
||||
g_loginMsg = std::string(info.szStartTime) + "|" + std::to_string(g_myClientID);
|
||||
ClientAuth::g_loginMsg = std::string(info.szStartTime) + "|" + std::to_string(g_myClientID);
|
||||
|
||||
NSLog(@"LOGIN_INFOR filled: OS=%s, Host=%s, CPU=%dMHz, PubIP=%s, ClientID=%llu",
|
||||
osVer.c_str(), hostname.c_str(), info.dwCPUMHz, pubIP.c_str(), g_myClientID);
|
||||
@@ -681,120 +642,51 @@ static void daemonize()
|
||||
}
|
||||
|
||||
// ============== Main Entry Point ==============
|
||||
// RttEstimator + g_rttEstimator + g_heartbeatInterval 已抽到 common/rtt_estimator.h
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 限制最小 RTO(RFC 6298 推荐 1 秒)
|
||||
if (rto < 1.0) rto = 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
RttEstimator g_rttEstimator;
|
||||
int g_heartbeatInterval = 5; // 心跳间隔(秒),默认 5 秒,后续可由服务端动态调整
|
||||
|
||||
void* ShellworkingThread(void* param)
|
||||
void* ShellworkingThread(void* /*param*/)
|
||||
{
|
||||
try {
|
||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||
void* clientAddr = ClientObject.get();
|
||||
NSLog(@">>> Enter ShellworkingThread [%p]", clientAddr);
|
||||
// 子连接:开启 auth。macOS 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);
|
||||
NSLog(@">>> ShellworkingThread [%p] Send: TOKEN_TERMINAL_START", clientAddr);
|
||||
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
|
||||
Sleep(1000);
|
||||
// 清除回调,防止重连线程访问已销毁的 handler
|
||||
ClientObject->setManagerCallBack(nullptr, nullptr, nullptr);
|
||||
}
|
||||
NSLog(@">>> Leave ShellworkingThread [%p]", clientAddr);
|
||||
} catch (const std::exception& e) {
|
||||
NSLog(@"*** ShellworkingThread exception: %s ***", 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。macOS 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()));
|
||||
if (!handler->init()) {
|
||||
RunSubConnThread<ScreenHandler>(
|
||||
"ScreenworkingThread",
|
||||
[](IOCPClient* c) -> std::unique_ptr<ScreenHandler> {
|
||||
// macOS ScreenHandler 需要先 init() 申请录屏权限/抓屏 stream,失败 → 返 nullptr
|
||||
// 让骨架直接 leave,跳过 callback 安装
|
||||
auto h = std::unique_ptr<ScreenHandler>(new ScreenHandler(c));
|
||||
if (!h->init()) {
|
||||
Mprintf("*** ScreenHandler initialization failed (no permission?) ***\n");
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||
return h;
|
||||
},
|
||||
[](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* FileManagerworkingThread(void* param)
|
||||
void* FileManagerworkingThread(void* /*param*/)
|
||||
{
|
||||
try {
|
||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||
void* clientAddr = ClientObject.get();
|
||||
Mprintf(">>> Enter FileManagerworkingThread [%p]\n", clientAddr);
|
||||
// 子连接:开启 auth。macOS 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(">>> FileManagerworkingThread [%p] initialized\n", clientAddr);
|
||||
while (ClientObject->IsRunning() && ClientObject->IsConnected() && S_CLIENT_NORMAL == g_bExit)
|
||||
Sleep(1000);
|
||||
// 清除回调,防止重连线程访问已销毁的 handler
|
||||
ClientObject->setManagerCallBack(nullptr, nullptr, nullptr);
|
||||
}
|
||||
Mprintf(">>> Leave FileManagerworkingThread [%p]\n", clientAddr);
|
||||
} catch (const std::exception& e) {
|
||||
Mprintf("*** FileManagerworkingThread exception: %s ***\n", e.what());
|
||||
}
|
||||
RunSubConnThread<FileManager>(
|
||||
"FileManagerworkingThread",
|
||||
[](IOCPClient* c) { return std::unique_ptr<FileManager>(new FileManager(c)); },
|
||||
[](IOCPClient* c, FileManager*) {
|
||||
Mprintf(">>> FileManagerworkingThread [%p] initialized\n", c);
|
||||
});
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -803,11 +695,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;
|
||||
}
|
||||
|
||||
@@ -867,22 +757,10 @@ int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
|
||||
}
|
||||
}
|
||||
} else if (szBuffer[0] == CMD_MASTERSETTING) {
|
||||
int settingSize = ulLength - 1;
|
||||
// 包不完整 → 不立即退出,让心跳循环 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));
|
||||
|
||||
// 签名校验失败 → 同上,让超时兜底+重连处理
|
||||
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);
|
||||
@@ -1012,8 +890,7 @@ int main(int argc, const char* argv[])
|
||||
}
|
||||
|
||||
// 进入新连接,重置服务端身份校验状态
|
||||
g_loginTime = time(nullptr);
|
||||
g_settingsVerified.store(false, std::memory_order_release);
|
||||
ClientAuth::OnNewConnection();
|
||||
ClientObject->SendLoginInfo(logInfo.Speed(clock() - c));
|
||||
|
||||
// 心跳保活循环:定时发送心跳包,服务端回复后动态更新 RTT
|
||||
@@ -1035,10 +912,9 @@ int main(int argc, const 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();
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user